<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-CN"><generator uri="https://jekyllrb.com/" version="4.3.3">Jekyll</generator><link href="https://ifuryst.github.io/feed.xml" rel="self" type="application/atom+xml"/><link href="https://ifuryst.github.io/" rel="alternate" type="text/html" hreflang="zh-CN"/><updated>2026-08-03T13:57:15+00:00</updated><id>https://ifuryst.github.io/feed.xml</id><title type="html">ifuryst</title><subtitle>📝 &amp; 💭 </subtitle><entry xml:lang="zh"><title type="html">GPT 5.6发布会让我意识到一个趋势</title><link href="https://ifuryst.github.io/blog/2026/one-trend-the-gpt-5-6-launch-made-me-realize/" rel="alternate" type="text/html" title="GPT 5.6发布会让我意识到一个趋势"/><published>2026-07-13T00:00:00+00:00</published><updated>2026-07-13T00:00:00+00:00</updated><id>https://ifuryst.github.io/blog/2026/one-trend-the-gpt-5-6-launch-made-me-realize</id><content type="html" xml:base="https://ifuryst.github.io/blog/2026/one-trend-the-gpt-5-6-launch-made-me-realize/"><![CDATA[<p><a href="https://www.youtube.com/watch?v=Wq45rvPGNHs">https://www.youtube.com/watch?v=Wq45rvPGNHs</a></p> <p><a href="https://www.youtube.com/watch?v=GphgJjaKKhw">https://www.youtube.com/watch?v=GphgJjaKKhw</a></p> <p>周四OpenAI的发布会，发了GPT 5.6，以及传说中的SuperAPP（Codex和ChatGPT整合后集中在ChatGPT下），以及ChatGPT Work。OAI还是OAI，不过这些东西都不是我留意到的点，我留意到的是另外一个很重要的趋势！</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844704_35-480.webp 480w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844704_35-800.webp 800w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844704_35-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844704_35.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844705_36-480.webp 480w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844705_36-800.webp 800w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844705_36-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844705_36.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844705_37-480.webp 480w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844705_37-800.webp 800w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844705_37-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844705_37.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844706_38-480.webp 480w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844706_38-800.webp 800w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844706_38-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844706_38.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>视频中演示部分的输入，已经全面从文本输入转向语音输入了，这个我相信应该是今年以来最大的一个变化吧。那这个变化是从哪里开始呢？它是Typeles流行带来的一个趋势，几乎改变了现在很多公司和个人对于输入的范式思考和实践</p> <p><a href="https://www.typeless.com/">Typeless</a>这个东西，它本身所涉及的技术并不是什么新的东西，之前也有<a href="https://wisprflow.ai/">WisprFlow</a>的存在，但Typeless用一种简洁、丝滑的产品体验以及它marketing的能力，把这个东西从硅谷圈子里推了出来，目前开始影响各个公司关于语音输入的再思考</p> <p>在背景之下，我也收到很多朋友的反馈，包括他们的一些日常使用感受：</p> <ul> <li> <p>朋友A：他其实日常就会比较大量的去使用，就会觉得这个语音输入，和Vibe Coding结合起来是非常丝滑、非常高效的一种方式</p> </li> <li> <p>朋友B：他们公司的 CEO 到硅谷考察逛了一圈之后回来，直接付费给全公司的人订阅了，强制所有人都用Typeless，平时会统计一下使用情况。体感上会有点狗，但是对于初创来说，也不失为一种强制Push大家去接受新事物的一种方式</p> </li> </ul> <p>我自己也用了一段时间，这个东西确实是可以很大地提升我们的输入能力。像WisperFlow它的 onboarding引导页其实也有一个这样的说明。它说键盘输入的效率是比语音输入低几十倍，大部分人看到一定是嗤之以鼻，但是它下面马上show了一个可交互的页面，你不信的话你可以自己试。我试了一下，可以达到的效果确实是会比文本输入更快，快的不是一倍两倍，而是几十倍。这是可以量化的</p> <p>就如我一开始认为的，觉得做一个这个东西非常简单，但是做完之后还是感叹，哪怕是这种看似简单单一的产品，打磨到丝滑体验的程度，会被无数的细节给压死，不仅联想起，在大厂“很难做出好产品”这句话背后隐含着无数的痛苦面具</p> <h1 id="heyyo">HeyYo</h1> <p>接下去会聊一下我做<a href="https://hey-yo.app/">HeyYo</a>背后的故事</p> <p>首先是分析和学习，因为在此之前，对于语音识别只是简单的了解，也知道他们做这些背后靠STT（Speech-to-Text）的技术，基本上就是这样</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844706_39-480.webp 480w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844706_39-800.webp 800w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844706_39-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844706_39.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>这个是最裸的形态了，我们进一步包装一下，会是这样的</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844706_40-480.webp 480w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844706_40-800.webp 800w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844706_40-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844706_40.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>可以了，有第一版了，相关的工作基本都是客户端和服务端开发，到这一步可以有第一版了</p> <p>这里首先就面临了语言的处理了，基于此就稍微分析了一下现有的语音处理相关的知识，首先是音频的处理，是这样的一个流程</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844706_41-480.webp 480w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844706_41-800.webp 800w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844706_41-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844706_41.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>因此经常会看到的一些参数比如<code class="language-plaintext highlighter-rouge">16kHz 16bit mono</code>，这种其实就是代表了：</p> <ul> <li> <p>采样率是16kHz，每秒采16000次声音</p> </li> <li> <p>位深16bit，就是用16位整数来存储</p> </li> <li> <p>mono是单声道，Stereo是双声道，大部分模型是用单声道训练的</p> </li> </ul> <p><em>为什么这个场景下，基本用16kHz呢，因为根据奈奎斯特采样定理（Nyquist-Shannon Sampling Theorem），采样率≥2</em>最高频率，所以16kHz采样率可以覆盖最高8kHz的频率，而人说话大部分有效信息（元音、大部分辅音、共振峰等）都集中在8kHz以下，所以足够了*</p> <p><em>同理位深16bit的也是一个道理，16bit按照量化噪声公式可以算出理论信噪比是98dB（分贝），人类正常说话是60-70分贝，大喊差不多是80-90分贝，所以98分贝可以完整覆盖日常说话场景了</em></p> <p>这里我们可以关注两个重要的东西，就是<strong>编码格式</strong>和<strong>文件格式（容器）</strong>：</p> <table> <thead> <tr> <th>编码格式</th> <th>文件格式</th> <th>文件大小</th> <th>备注</th> </tr> </thead> <tbody> <tr> <td>PCM</td> <td>WAV</td> <td>大</td> <td>最原始的采样数据，无需解码</td> </tr> <tr> <td>Opus</td> <td>Ogg</td> <td>小</td> <td>有损压缩，需要解码（可CPU）</td> </tr> </tbody> </table> <p>为什么要了解这个，因为这关乎网络传输和存储成本，看下这个：</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844707_42-480.webp 480w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844707_42-800.webp 800w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844707_42-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844707_42.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844707_43-480.webp 480w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844707_43-800.webp 800w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844707_43-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844707_43.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844707_44-480.webp 480w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844707_44-800.webp 800w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844707_44-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844707_44.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>列个表格对比一下：</p> <table> <thead> <tr> <th>音频时长</th> <th>采样率</th> <th>编码格式/文件格式</th> <th>文件大小</th> </tr> </thead> <tbody> <tr> <td>4s</td> <td>48kHz</td> <td>PCM/WAV</td> <td>416KB</td> </tr> <tr> <td>9min5s</td> <td>48kHz</td> <td>PCM/WAV</td> <td>52.3MB</td> </tr> <tr> <td>4s</td> <td>16kHz</td> <td>PCM/WAV</td> <td>141KB</td> </tr> <tr> <td>9min5s</td> <td>16kHz</td> <td>PCM/WAV</td> <td>17.4MB</td> </tr> <tr> <td>4s</td> <td>16kHz</td> <td>Opus/Ogg</td> <td>10KB</td> </tr> <tr> <td>9min5s</td> <td>16kHz</td> <td>Opus/Ogg</td> <td>1.1MB</td> </tr> </tbody> </table> <p>可以很明显的看出差别，Opus/Ogg带来的收益特别大，一个是传输的数据变少了，能得到更多的时延，一个是存储的成本也成顿的减少了。人耳听完全听不出差异的，解码的损耗也可以忽略。这个也是目前主流的方案了</p> <p>到这里就解决音频相关的问题了，现在就需要来看识别的模型了。这个有很多选择，最早的自然是OpenAI的<a href="https://huggingface.co/openai/whisper-large-v3-turbo">whisper-large-v3-turbo</a>，算是比较有名的了，截止目前也还是有人在用，虽然已经好多年了，但是底子还可以，就是长尾效果差，时延不稳定</p> <p>另一个选择就是走目前的多模态大语言模型，现在的多模态模型很多都支持图片、语音、文本的混合输入，然后得到文本，在这个场景下就很合适。因此在这个情况下，就可以针对这边STT使用什么模型做一个对比了，我自己录了3个Case：</p> <ul> <li> <p>英文</p> </li> <li> <p>中文</p> </li> <li> <p>中英混</p> </li> </ul> <p>跑了一个简单的bench，每个模型针对每个case各跑5次，用这个汇总数据感受一下：</p> <table> <thead> <tr> <th>Provider/Model</th> <th>avg时延</th> <th>中位时延</th> <th>avg评分</th> </tr> </thead> <tbody> <tr> <td>Gemini / 2.5 Flash</td> <td>3.11s</td> <td>2.86s</td> <td>0.829</td> </tr> <tr> <td>Soniox / stt-rt-v4</td> <td>19.09s</td> <td>17.99s</td> <td>0.974</td> </tr> <tr> <td>Deepgram / nova-3</td> <td>18.16s</td> <td>17.52s</td> <td>0.553</td> </tr> <tr> <td>Cloudflare / Whisper large v3 turbo</td> <td>3.15s</td> <td>2.44s</td> <td>0.888</td> </tr> <tr> <td>SiliconFlow / SenseVoiceSmall</td> <td>0.76s</td> <td>0.66s</td> <td>0.558</td> </tr> </tbody> </table> <p>不是完全的严肃严谨，提供一个基础的感知，因为这里面还是有一些空间去调优的，包括多模态大模型是可以有Prompt和Reasoning Effort的调整，后面会讲。原始数据抽一些列：</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844707_45-480.webp 480w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844707_45-800.webp 800w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844707_45-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844707_45.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>可以直观看到速度，和识别的效果，有些诸如deepgram和soniox还是挺多人用的，但是我敢肯定的是，他们不适合这个场景，完败到没话说</p> <p>从这里，我们初见杀至少是相对明确的，我们能选择的模型就是耗时最短的那几个（<em>FYI，这边举例说明而已，实际上还有特别的SOTA模型我没有bench结果上来，他们的表现也各不相同，这边暂时不列出完整的结果，后续考虑开一个完整的榜单列出语音相关的这些数据</em>）</p> <p>但是到这里就可以用了么？是可以用，但还可以再进一步。比如这边如果我们选择类似Whisper或SenseVoice之类的，很验证的问题是不稳定，一个是输出效果的不稳定，一个是时延不稳定。</p> <p>因此更好的选择是选择大模型，但是<strong>大</strong>模型，会带来时延的上升，尤其现在整个行业已经都自带思考了，这个东西对于STT来说，没啥用，或者说收益远远小于速度的牺牲。因此这里不管用什么多模态大模型，最终都是走向关闭思考，或者最小化思考Effort</p> <p>到这里其实第一阶段的探索和实施结束了。到这里基本上就可以做出一个可用的产品了，但是进一步推进的话，会发现，架构上或许还需要再调整一下，从一阶段拆成两阶段，应该是很多成熟产品的路线，因此关于架构（或者说pipeline）演变成这样：</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844709_46-480.webp 480w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844709_46-800.webp 800w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844709_46-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844709_46.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>一切都是双刃剑，有双阶段的好处是：</p> <ol> <li> <p>可以多路并行识别，在Refine阶段可互相补充印证识别情况，避免单一模型短板</p> </li> <li> <p>可以进一步扩展功能，比如从文本识别扩展到翻译、搜索等更多的功能面，这个在单阶段是比较难做到的</p> </li> </ol> <p>但是相应的，一个很明显的问题就是增加了时延，我做了一些简单的bench得出的一个对比：</p> <table> <thead> <tr> <th>指标</th> <th>2阶段</th> <th>1阶段</th> </tr> </thead> <tbody> <tr> <td>中位时延</td> <td>2608ms</td> <td>1537ms</td> </tr> </tbody> </table> <p>依然是不是严肃严谨的测试，但是整体是这个情况，多一次Roundtrip自然多一程时延，是个取舍的问题。在这个架构调整之后，产品的整个使用已经打磨的相对舒服了，但是这也只是一个Baseline版本，这期间其实也有一些我印象比较深刻的点有助于速度的提升：</p> <ul> <li> <p>关掉或者最小化思考，前面也有提到，这个对于我们这种分秒必争的场景很重要</p> </li> <li> <p>默认走WebSocket，Fallback走HTTP，这样可以边录边传输，可以节省掉大部分传输耗时。服务端也可以优化出ws陆续传输到模型侧</p> </li> <li> <p>端侧转码，因为这期间也试过PCM到云端，但是会有转码瓶颈。通常在端侧做好Opus的编码，然后chunk流上传</p> </li> <li> <p>System Prompt的调优，也会有影响，prompt长度也会有影响，和Coding Agent这类有着本着的差别！</p> </li> </ul> <p>接着要做一些配套的机制了，重要的是这么几个：</p> <ol> <li> <p>环境上下文采集</p> </li> <li> <p>字典：公共/领域字典+个人字典</p> </li> </ol> <p>环境上下文采集就是针对输入框所在的地方采集相应的上下文信息，比如：</p> <ul> <li> <p>所在APP：Notion和微信里的期望风格是不同的，这就可以通过这个信息来客制化</p> </li> <li> <p>前后的文本，是否选中文本等这类信息：有选中文本的话，通常就会被结果替代。光标前后的一些相对距离较近的文本，也能帮助我们去输出更好的结果</p> </li> </ul> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844709_47-480.webp 480w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844709_47-800.webp 800w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844709_47-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844709_47.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>这些相关的就统称为环境上下文，这个也是通常这类软件需要Accessibility权限的原因，有了这个就可以针对APP优化了，体验感会极大拉升</p> <p>其次是字典，没有字段，针对一些专有名词、人名地名等一定没办法准确输出，公共/领域字典不展开，八仙过海属于。重要的是个人字典，这个是润物细无声的技术，关键流程：</p> <ol> <li> <p>识别后得到文本后，用户发现有些地方识别错了，手动改了</p> </li> <li> <p>一定周期内会采集对应的改动和相关的上下文信息上报</p> </li> <li> <p>离线Pipeline会去做词语的提取流程，然后返哺到个人词典</p> </li> <li> <p>下次用户在语音输入时说到这个词就能自动匹配上</p> </li> </ol> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844709_48-480.webp 480w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844709_48-800.webp 800w,/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844709_48-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-13-one-trend-the-gpt-5-6-launch-made-me-realize/1783844709_48.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>这就是非常典型的润物细无声，在用户使用的过程中持续的优化提取词典，在后续使用过程中无缝提升用户体验</p> <p>到这里基本上可以达到一个不错的产品体验了，至于其他的，很看细节了，比如波形绘制看起来是否舒服，热键处理和稳定劫持，UIUX的打磨等</p> <p>再往下推进到移动端后，反推的就是需要输入法了，没有输入法在手机端用起来就没那么丝滑了，关于输入法部分的暂不展开，回头再专门聊</p> <h1 id="写在最后">写在最后</h1> <p>通过技术的角度去聊一个产品的诞生过程，表述简单，但是实际的过程中还是充满了很多的探索、尝试、使用和反馈修正的过程，没有一个好产品是预先规划出来的，一切好的产品都是要经历不断Dogfooding反复打磨后才能达到的</p> <p>现在有了AI的赋能，很多问题已经不再是问题了，不管是coding、design，还是学习研究，甚至是bench、eval等，快速的时代更应该反其道而行之，沉下心来去沉淀出一些能长期复利的东西</p>]]></content><author><name></name></author><category term="AI"/><category term="AI"/><category term="Product"/><summary type="html"><![CDATA[https://www.youtube.com/watch?v=Wq45rvPGNHs]]></summary></entry><entry xml:lang="zh"><title type="html">Anthropic Managed Agents管中窥豹</title><link href="https://ifuryst.github.io/blog/2026/inside-anthropic-managed-agents/" rel="alternate" type="text/html" title="Anthropic Managed Agents管中窥豹"/><published>2026-07-08T00:00:00+00:00</published><updated>2026-07-08T00:00:00+00:00</updated><id>https://ifuryst.github.io/blog/2026/inside-anthropic-managed-agents</id><content type="html" xml:base="https://ifuryst.github.io/blog/2026/inside-anthropic-managed-agents/"><![CDATA[<p>Anthropic的Managed Agents出来了几个月了，这波刚好分析一下，从技术触发，分析Anthropic在未来的布局，我个人是觉得很值得AI相关的公司留意这个方向的，很值得深思</p> <h1 id="just-try-it-yourself">Just try it yourself!</h1> <p>学习一个东西最好的方式永远是亲自吃一下，自己使用试用一下，自己挖掘一下，远比叫AI给你deep research来得更加有深度！</p> <p>（题外话，不得不说很多原来不会写文档的人，用了AI更加不会写，但是看到几句话几分钟十几分钟AI就能给出一篇满满的文章，那种膨胀的感觉应该很享受？然后拉其他人对着AI写的文档照念一通。非常讽刺了，笑死了。或许丢ai gen的文档给别人的ai去汇总提取阅读更好。或者说别人也有自己的AI，没人愿意看Document Slop。刚好今天看杂志看到王俊煜写的，截图如下：</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517110_1-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517110_1-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517110_1-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517110_1.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>）</p> <p>扯远了，回到正题，一般这种看看官方文档后就可以直接自己去试试了</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517111_2-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517111_2-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517111_2-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517111_2.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>基本上就这几个核心的东西：</p> <table> <thead> <tr> <th><strong>概念</strong></th> <th><strong>作用</strong></th> <th>备注</th> </tr> </thead> <tbody> <tr> <td><strong>Agents</strong></td> <td>核心定义，Agent定义本身。包含<code class="language-plaintext highlighter-rouge">name</code>、<code class="language-plaintext highlighter-rouge">desc</code>、<code class="language-plaintext highlighter-rouge">sys prompt</code>、<code class="language-plaintext highlighter-rouge">tools</code>、<code class="language-plaintext highlighter-rouge">mcp_servers</code>、<code class="language-plaintext highlighter-rouge">skills</code>、<code class="language-plaintext highlighter-rouge">metadata</code></td> <td>很标准的一个Agent定义，底层坐落在Claude Code上的</td> </tr> <tr> <td><strong>Sessions</strong></td> <td>实际的调度运行的单元和<code class="language-plaintext highlighter-rouge">计费</code>项目</td> <td>Session通过API/SDK能发消息触发，需要主动发送才能触发一个Session运作</td> </tr> <tr> <td><strong>Deployments</strong></td> <td>周期调度器，负责按时间触发 Session</td> <td>可以理解就是定时任务，Deploy可以定时执行（落到一个一个Session）</td> </tr> <tr> <td><strong>Environments</strong></td> <td>沙盒运行环境，~=Sandbox定义，可定义预装的三方库、软件、出网策略</td> <td>预定的云场景+self host场景，兼顾云+B端</td> </tr> <tr> <td><strong>Credential Vaults</strong></td> <td>auth/api key管理注入</td> <td>注入沙盒的环境变量，或者在Egress出网的时候针对HTTP或者MCP调用去自动attach/replace对应的Secret</td> </tr> <tr> <td><strong>Memory Stores</strong></td> <td>Agent记忆存储</td> <td>理解为llm-wiki就好了。一个mem store会挂载到一个目录，比如/mnt/mem/test1，底层和filestore都是基于rclone+OSS支撑只读或者读写</td> </tr> <tr> <td><strong>Files</strong></td> <td>Session启动时附带的文件输入</td> <td>顶上Build下的Files，这里是可以在Session开始的时候关联具体的文件给到这个会话的Agent，也是挂载进去到对应路径的，比如<code class="language-plaintext highlighter-rouge">/mnt/session/uploads</code>，只读</td> </tr> <tr> <td><strong>Skills</strong></td> <td>Skills管理</td> <td>有预置的和自己上传的skil都在这里，也是只读挂载到沙盒<code class="language-plaintext highlighter-rouge">/mnt/skills</code></td> </tr> </tbody> </table> <h2 id="agents">Agents</h2> <p>看下这个是一个标准的创建Agents的定义，YAML或JSON都可以</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517111_3-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517111_3-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517111_3-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517111_3.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>也可以Quickstart里通过chat去完成创建，很AI Native的方式。</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517112_4-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517112_4-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517112_4-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517112_4.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517113_5-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517113_5-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517113_5-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517113_5.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>看看生成后的：</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517114_6-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517114_6-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517114_6-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517114_6.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>可以编辑，也可以从这个Agent开始一个Session</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517114_7-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517114_7-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517114_7-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517114_7.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>上传的文件有三种：Github Repo，File，Memory Store三种。</p> <h2 id="sessions">Sessions</h2> <p>开始会话后就是一个chat界面，此时Sesion是Idle的（不计费）</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517114_8-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517114_8-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517114_8-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517114_8.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>看一个实际跑之后的：</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517115_9-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517115_9-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517115_9-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517115_9.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517115_10-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517115_10-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517115_10-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517115_10.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>能看到分了Transcript和Debug，前者就可以理解是Events，但是现在Anthropic在Claude Code里都叫Transcript了。Debug里更加详细，看着更接近Trace的感觉了。这个界面没有太多功能，就是看事件，继续chat这种。</p> <h2 id="environments"><strong>Environments</strong></h2> <p>可以创建2种环境（沙盒），默认Cloud云端的，另一种Self-hosted</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517116_11-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517116_11-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517116_11-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517116_11.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>云端的可以预先配置出网策略，预装的软件和三方库等</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517116_12-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517116_12-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517116_12-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517116_12.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517116_13-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517116_13-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517116_13-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517116_13.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>Self-hosted就是可以在自己的机器上跑worker</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517117_14-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517117_14-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517117_14-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517117_14.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ant</span> <span class="n">beta</span><span class="p">:</span><span class="n">worker</span> <span class="n">poll</span> \
  <span class="o">--</span><span class="n">environment</span><span class="o">-</span><span class="nb">id</span> <span class="sh">"</span><span class="s">env_01AzQWp3SXQEATgdCFUNwteR</span><span class="sh">"</span> \
  <span class="o">--</span><span class="n">workdir</span> <span class="sh">"</span><span class="s">/path/to/workspace</span><span class="sh">"</span>
</code></pre></div></div> <p>这种方式跑，ant的代码在<a href="https://github.com/anthropics/anthropic-cli">这里</a>，就是官方的anthropic-cli，实际上这个只是一个tool的执行器而已，基本上可以理解上面下发events下来，本地执行（比如执行一个命令），结果回报而已，相对还是简陋了一些，更何况自定义工具还是比较困难了，基本就是skill+shell（这也不是Anthropic toB的手段，后续会说关键的）</p> <p>Cloud沙盒里预置的软件和依赖包根据template不同会有差异的，比如claude.ai里直接chat的环境和Managed Agents的环境是有差异的，这里记录一下两侧的差异</p> <h3 id="chat">Chat</h3> <p>Anthropic的沙盒里预置了这些运行时：</p> <table> <thead> <tr> <th>语言</th> <th>版本</th> </tr> </thead> <tbody> <tr> <td>Python</td> <td>3.12.3</td> </tr> <tr> <td>Node.js</td> <td>22.22.2</td> </tr> <tr> <td>npm</td> <td>10.9.7</td> </tr> <tr> <td>Java (OpenJDK)</td> <td>21.0.10</td> </tr> </tbody> </table> <p>可通过apt，cargo，gem，go，npm，pip去装</p> <p>构建工具：</p> <table> <thead> <tr> <th>工具</th> <th>版本</th> </tr> </thead> <tbody> <tr> <td>gcc / g++</td> <td>13.3.0</td> </tr> <tr> <td>GNU Make</td> <td>4.3</td> </tr> <tr> <td>git</td> <td>2.43.0</td> </tr> <tr> <td>curl</td> <td>8.5.0</td> </tr> <tr> <td>wget</td> <td>1.21.4</td> </tr> </tbody> </table> <p>Python内置包：</p> <table> <thead> <tr> <th>类别</th> <th>包</th> <th>版本</th> </tr> </thead> <tbody> <tr> <td>数值计算</td> <td>numpy</td> <td>2.4.4</td> </tr> <tr> <td>数据处理</td> <td>pandas</td> <td>3.0.2</td> </tr> <tr> <td>科学计算</td> <td>scipy</td> <td>1.17.1</td> </tr> <tr> <td>机器学习</td> <td>scikit-learn</td> <td>1.8.0</td> </tr> <tr> <td>可视化</td> <td>matplotlib</td> <td>3.10.8</td> </tr> <tr> <td>可视化</td> <td>seaborn</td> <td>0.13.2</td> </tr> <tr> <td>图像处理</td> <td>Pillow</td> <td>12.1.1</td> </tr> <tr> <td>图像处理</td> <td>opencv</td> <td>4.13.0</td> </tr> <tr> <td>图像处理</td> <td>ImageMagick（Wand）</td> <td>0.7.0</td> </tr> <tr> <td>视频/音频</td> <td>imageio</td> <td>2.37.3</td> </tr> <tr> <td>视频/音频</td> <td>imageio-ffmpeg</td> <td>0.6.0</td> </tr> <tr> <td>视频/音频</td> <td>sounddevice</td> <td>0.5.5</td> </tr> <tr> <td>AI/ML</td> <td>mediapipe</td> <td>0.10.33</td> </tr> <tr> <td>AI/ML</td> <td>onnxruntime</td> <td>1.24.4</td> </tr> <tr> <td>AI/ML</td> <td>magika</td> <td>0.6.3</td> </tr> <tr> <td>PDF</td> <td>pypdf</td> <td>5.9.0</td> </tr> <tr> <td>PDF</td> <td>pdfplumber</td> <td>0.11.9</td> </tr> <tr> <td>PDF</td> <td>camelot-py</td> <td>1.0.9</td> </tr> <tr> <td>PDF</td> <td>pikepdf</td> <td>10.5.1</td> </tr> <tr> <td>PDF</td> <td>img2pdf</td> <td>0.6.3</td> </tr> <tr> <td>PDF</td> <td>reportlab</td> <td>4.4.10</td> </tr> <tr> <td>Office 文档</td> <td>python-docx</td> <td>1.2.0</td> </tr> <tr> <td>Office 文档</td> <td>python-pptx</td> <td>1.0.2</td> </tr> <tr> <td>Office 文档</td> <td>openpyxl</td> <td>3.1.5</td> </tr> <tr> <td>Office 文档</td> <td>xlsxwriter</td> <td>3.2.9</td> </tr> <tr> <td>Web</td> <td>requests</td> <td>2.33.1</td> </tr> <tr> <td>Web</td> <td>Flask</td> <td>3.1.3</td> </tr> <tr> <td>Web</td> <td>BeautifulSoup4</td> <td>4.14.3</td> </tr> <tr> <td>Web</td> <td>playwright</td> <td>1.56.0</td> </tr> <tr> <td>数学</td> <td>sympy</td> <td>1.14.0</td> </tr> <tr> <td>数学</td> <td>mpmath</td> <td>1.3.0</td> </tr> <tr> <td>数学</td> <td>networkx</td> <td>3.6.1</td> </tr> <tr> <td>OCR</td> <td>pytesseract（调用 tesseract）</td> <td>5.3.4</td> </tr> <tr> <td>文档转换</td> <td>markdownify</td> <td>1.2.2</td> </tr> <tr> <td>文档转换</td> <td>pandoc（系统安装）</td> <td>3.1.3</td> </tr> </tbody> </table> <p>Node.js 全局包</p> <table> <thead> <tr> <th>包</th> <th>版本</th> </tr> </thead> <tbody> <tr> <td>@mermaid-js/mermaid-cli</td> <td>11.12.0</td> </tr> <tr> <td>docx</td> <td>9.6.1</td> </tr> <tr> <td>graphviz</td> <td>0.0.9</td> </tr> <tr> <td>markdown-pdf</td> <td>11.0.0</td> </tr> <tr> <td>markdown-toc</td> <td>1.2.0</td> </tr> <tr> <td>markdownlint-cli</td> <td>0.48.0</td> </tr> <tr> <td>markdownlint-cli2</td> <td>0.22.0</td> </tr> <tr> <td>marked</td> <td>18.0.2</td> </tr> <tr> <td>pdf-lib</td> <td>1.17.1</td> </tr> <tr> <td>pdfjs-dist</td> <td>5.6.205</td> </tr> <tr> <td>playwright</td> <td>1.56.0</td> </tr> <tr> <td>pptxgenjs</td> <td>4.0.1</td> </tr> <tr> <td>react</td> <td>19.2.5</td> </tr> <tr> <td>react-dom</td> <td>19.2.5</td> </tr> <tr> <td>react-icons</td> <td>5.6.0</td> </tr> <tr> <td>remark-cli</td> <td>12.0.1</td> </tr> <tr> <td>remark-preset-lint-recommended</td> <td>7.0.1</td> </tr> <tr> <td>sharp</td> <td>0.34.5</td> </tr> <tr> <td>ts-node</td> <td>10.9.2</td> </tr> <tr> <td>tsx</td> <td>4.21.0</td> </tr> <tr> <td>typescript</td> <td>6.0.3</td> </tr> </tbody> </table> <p>系统工具</p> <table> <thead> <tr> <th>工具</th> <th>版本</th> <th>用途</th> </tr> </thead> <tbody> <tr> <td>ffmpeg</td> <td>6.1.1</td> <td>音视频处理</td> </tr> <tr> <td>ImageMagick</td> <td>6.9.12</td> <td>图像处理</td> </tr> <tr> <td>tesseract</td> <td>5.3.4</td> <td>OCR</td> </tr> <tr> <td>pandoc</td> <td>3.1.3</td> <td>文档格式转换</td> </tr> <tr> <td>LibreOffice</td> <td>24.2.7.2</td> <td>Office 文档处理</td> </tr> <tr> <td>unoserver</td> <td>3.6</td> <td>LibreOffice 服务化</td> </tr> <tr> <td>Playwright Chromium</td> <td>1194</td> <td>无头浏览器</td> </tr> </tbody> </table> <h3 id="managed-agents">Managed Agents</h3> <p>语言与形式：</p> <table> <thead> <tr> <th>Runtime</th> <th>Observed value</th> <th>Notes</th> </tr> </thead> <tbody> <tr> <td>Python</td> <td>3.11.15</td> <td><code class="language-plaintext highlighter-rouge">/usr/local/bin/python3</code>, <code class="language-plaintext highlighter-rouge">/usr/bin/python3</code>, <code class="language-plaintext highlighter-rouge">/usr/bin/python</code></td> </tr> <tr> <td>pip</td> <td>24.0</td> <td>Python 3.11</td> </tr> <tr> <td>Node.js</td> <td>v20.20.2</td> <td><code class="language-plaintext highlighter-rouge">/usr/local/bin/node</code></td> </tr> <tr> <td>npm / npx</td> <td>10.8.2</td> <td><code class="language-plaintext highlighter-rouge">/usr/local/bin/npm</code>, <code class="language-plaintext highlighter-rouge">/usr/local/bin/npx</code></td> </tr> <tr> <td>Java</td> <td>OpenJDK 21.0.10</td> <td><code class="language-plaintext highlighter-rouge">java</code> and <code class="language-plaintext highlighter-rouge">javac</code> present</td> </tr> <tr> <td>Ruby</td> <td>3.3.6</td> <td><code class="language-plaintext highlighter-rouge">gem</code> 3.5.22</td> </tr> <tr> <td>PHP</td> <td>8.4.20</td> <td>Composer 2.8.12</td> </tr> </tbody> </table> <p>命令or工具</p> <table> <thead> <tr> <th>Tool</th> <th>Observed value</th> </tr> </thead> <tbody> <tr> <td>gcc / g++</td> <td>13.3.0</td> </tr> <tr> <td>GNU Make</td> <td>4.3</td> </tr> <tr> <td>cmake</td> <td>3.28.3</td> </tr> <tr> <td>git</td> <td>2.43.0</td> </tr> <tr> <td>curl</td> <td>8.5.0</td> </tr> <tr> <td>wget</td> <td>1.21.4</td> </tr> <tr> <td>jq</td> <td>1.7</td> </tr> <tr> <td>ripgrep</td> <td>14.1.0</td> </tr> <tr> <td>tmux</td> <td>3.4 (3.4-1ubuntu0.1)</td> </tr> <tr> <td>psql</td> <td>16.13 client</td> </tr> <tr> <td>redis-cli</td> <td>7.0.15</td> </tr> <tr> <td>ffmpeg</td> <td>6.1.1 system package</td> </tr> <tr> <td>ImageMagick</td> <td>6.9.12-98 (<code class="language-plaintext highlighter-rouge">convert</code>, <code class="language-plaintext highlighter-rouge">identify</code>)</td> </tr> <tr> <td>tesseract</td> <td>5.3.4</td> </tr> <tr> <td>pandoc</td> <td>3.1.3</td> </tr> <tr> <td>LibreOffice / soffice</td> <td>24.2.7.2</td> </tr> <tr> <td>unoserver</td> <td>3.6</td> </tr> <tr> <td>Playwright Python package/CLI</td> <td>1.56.0</td> </tr> <tr> <td>Playwright Chromium</td> <td>build 1194, Chromium 141.0.7390.37</td> </tr> <tr> <td>Playwright headless shell</td> <td>build 1194, Chromium 141.0.7390.37</td> </tr> <tr> <td>Google Chrome path</td> <td><code class="language-plaintext highlighter-rouge">/opt/google/chrome/chrome</code>, reports Chromium 141.0.7390.37</td> </tr> <tr> <td>Playwright ffmpeg</td> <td>build 1011, ffmpeg <code class="language-plaintext highlighter-rouge">n7.0.1-playwright-build-1011</code></td> </tr> </tbody> </table> <p>Python库</p> <table> <thead> <tr> <th>Package</th> <th>Observed value</th> </tr> </thead> <tbody> <tr> <td>numpy</td> <td>2.4.4</td> </tr> <tr> <td>pandas</td> <td>3.0.2</td> </tr> <tr> <td>scipy</td> <td>1.17.1</td> </tr> <tr> <td>scikit-learn</td> <td>1.8.0</td> </tr> <tr> <td>matplotlib</td> <td>3.10.8</td> </tr> <tr> <td>seaborn</td> <td>0.13.2</td> </tr> <tr> <td>Pillow</td> <td>12.2.0</td> </tr> <tr> <td>opencv-python</td> <td>4.13.0.92</td> </tr> <tr> <td>Wand</td> <td>0.7.0</td> </tr> <tr> <td>imageio</td> <td>2.37.3</td> </tr> <tr> <td>imageio-ffmpeg</td> <td>0.6.0</td> </tr> <tr> <td>sounddevice</td> <td>0.5.5 installed, import failed because PortAudio library was missing</td> </tr> <tr> <td>mediapipe</td> <td>0.10.32</td> </tr> <tr> <td>onnxruntime</td> <td>1.24.4</td> </tr> <tr> <td>magika</td> <td>0.6.3</td> </tr> <tr> <td>pypdf</td> <td>3.17.4</td> </tr> <tr> <td>pdfplumber</td> <td>0.11.9</td> </tr> <tr> <td>camelot-py</td> <td>1.0.9</td> </tr> <tr> <td>pikepdf</td> <td>10.5.1</td> </tr> <tr> <td>img2pdf</td> <td>0.6.3</td> </tr> <tr> <td>reportlab</td> <td>4.4.10</td> </tr> <tr> <td>python-docx</td> <td>1.2.0</td> </tr> <tr> <td>python-pptx</td> <td>1.0.2</td> </tr> <tr> <td>openpyxl</td> <td>3.1.5</td> </tr> <tr> <td>XlsxWriter</td> <td>3.2.9</td> </tr> <tr> <td>requests</td> <td>2.33.1</td> </tr> <tr> <td>Flask</td> <td>3.1.3</td> </tr> <tr> <td>beautifulsoup4</td> <td>4.14.3</td> </tr> <tr> <td>playwright</td> <td>1.56.0</td> </tr> <tr> <td>sympy</td> <td>1.14.0</td> </tr> <tr> <td>mpmath</td> <td>1.3.0</td> </tr> <tr> <td>networkx</td> <td>3.6.1</td> </tr> <tr> <td>pytesseract</td> <td>0.3.13</td> </tr> <tr> <td>markdownify</td> <td>1.2.2</td> </tr> </tbody> </table> <p>nodejs全局包</p> <table> <thead> <tr> <th>Package</th> <th>Observed value</th> </tr> </thead> <tbody> <tr> <td>corepack</td> <td>0.34.6</td> </tr> <tr> <td>npm</td> <td>10.8.2</td> </tr> </tbody> </table> <h2 id="deployments"><strong>Deployments</strong></h2> <p>定时任务，没什么magic，定义一下关联和挂载的东西。</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517117_15-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517117_15-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517117_15-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517117_15.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>跑起来是Runs，每个Run直接对应于一个Session</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517117_16-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517117_16-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517117_16-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517117_16.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517118_17-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517118_17-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517118_17-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517118_17.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <h2 id="credential-vaults"><strong>Credential Vaults</strong></h2> <p>三种方式注入：</p> <ol> <li> <p>针对MCP做OAuth，配置的时候就会在web这边做好登陆，授权是直接给到Anthropic的，后续在请求MCP的时候自动注入</p> </li> <li> <p>针对MCP做Bearer Token的配置，后续MCP请求的时候自动注入</p> </li> <li> <p>针对请求直接注入，比如在对外的HTTP请求里注入api key到HTTP Header里</p> </li> </ol> <p>这三种方式在沙盒里都是无法感知到这些secret内容的，不像那种环境变量直接注入到沙盒的env里可以被获取到</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517118_18-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517118_18-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517118_18-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517118_18.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517118_19-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517118_19-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517118_19-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517118_19.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517118_20-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517118_20-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517118_20-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517118_20.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>比如这里我让其请求我的api.ifuryst.com</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517119_21-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517119_21-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517119_21-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517119_21.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>里面执行的是</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">curl</span> <span class="o">-</span><span class="n">s</span> <span class="o">--</span><span class="n">http1</span><span class="p">.</span><span class="mi">1</span> <span class="o">-</span><span class="n">X</span> <span class="n">POST</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">api</span><span class="p">.</span><span class="n">ifuryst</span><span class="p">.</span><span class="n">com</span>
  <span class="o">-</span><span class="n">H</span> <span class="sh">"</span><span class="s">Authorization: Bearer $TEST</span><span class="sh">"</span>
  <span class="o">-</span><span class="n">H</span> <span class="sh">"</span><span class="s">Content-Type: application/json</span><span class="sh">"</span>
  <span class="o">-</span><span class="n">d</span> <span class="sh">'</span><span class="s">{</span><span class="sh">"</span><span class="s">status</span><span class="sh">"</span><span class="s">:</span><span class="sh">"</span><span class="s">completed</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">email_success</span><span class="sh">"</span><span class="s">:true,</span><span class="sh">"</span><span class="s">email_id</span><span class="sh">"</span><span class="s">:</span><span class="sh">"</span><span class="s">629d4d14-399e-43dd-b77a-bb2caa913dc6</span><span class="sh">"</span><span class="s">}</span><span class="sh">'</span>
</code></pre></div></div> <p>实际收到的是123，也就是$TEST在出口的时候有个出口网关服务做了这个替换动作</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517119_22-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517119_22-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517119_22-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517119_22.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <h2 id="memory-stores"><strong>Memory Stores</strong></h2> <p>这个很简单，就是LLM Wiki，或者说类似OpenClaw，Hermes这种以文件系统存放记忆的方式来管理</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517119_23-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517119_23-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517119_23-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517119_23.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517119_24-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517119_24-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517119_24-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517119_24.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517120_25-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517120_25-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517120_25-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517120_25.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517120_26-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517120_26-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517120_26-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517120_26.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <h2 id="files"><strong>Files</strong></h2> <p>这个没啥好说的</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517120_27-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517120_27-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517120_27-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517120_27.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <h2 id="skills"><strong>Skills</strong></h2> <p>这个没啥好说的</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517120_28-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517120_28-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517120_28-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517120_28.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517121_29-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517121_29-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517121_29-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517121_29.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517121_30-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517121_30-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517121_30-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517121_30.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <h1 id="re">RE</h1> <p>实际自己体验了一波，这个Managed Agents这种东西，其实是可以类比成云时代的AWS的，就好像最早我们需要自己搞物理机器到机房/IDC，后来只需要到AWS，GCP，阿里云这些地方去开一个ECS，或者其他配套的如DB，S3等等，也就是不care底下到底是什么硬件机器了，只在乎开出什么配置，按时间付费。</p> <p>那现在这个Managed Agents就是Agent时代的AWS，我们不需要关心底下是什么写的Harness，不需要关系沙盒的安全性隔离性需要怎么做才好，不需要有一台一直开着的机器只为了给你Agent跑，我们只需要付费就可以用了，顺带一起这边的付费项：</p> <ul> <li> <p>LLMs，模型API调用，或者说Token费用，好理解</p> </li> <li> <p>web_search这种工具，$10可以请求1000次</p> </li> <li> <p>session时，$0.8/session时，running的时候才收费，这个基本上就是把底下各种硬件、网络等等费用都包进去抽象了一下，对于用户来说更好理解</p> </li> </ul> <p>这样是不是就有感觉了？这个就是Agent时代的PaaS了（或者有人说的Harness as a Service）</p> <p>以前我们享受AWS给我们做好的各种硬件、网络，现在我们享受Anthropic给我们做好Harness</p> <p>讲了这么多，接下去就开启一波分析操作，我们希望从沙盒这个切入口去分析整体的架构可能性。首先自然是收集整个MicroVM里有的东西，这期间我试了一些方式，有些自然而然的想法和动作我没记录下来，我就说一些记得的简要历程：</p> <ul> <li> <p>一般遇到这种我第一实际就是打个tunnel过去，ssh上去收刮一番，这次也是，因为有ant cli，更加方便，丢codex一台公网机器后就让它帮忙打黑工</p> </li> <li> <p>期间发现沙盒出口网络被策略限制了，80/443之类的能通，但是其他端口出不去，就走了一波HTTP转SSH的操作（chisel）</p> </li> <li> <p>ssh上去后，玩了几下就断掉了，看着是idle后就会被停掉。然后就让codex自己去左右开工了，拉任务后上去收刮</p> </li> </ul> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517121_31-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517121_31-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517121_31-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517121_31.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <ul> <li>最后确认了几个关键的二进制，都打包下来用于分析</li> </ul> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517122_32-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517122_32-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517122_32-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517122_32.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <ul> <li> <p>这里面就涉及到对二进制的逆向分析，基本上能确认几个二进制的作用：</p> </li> <li> <p><strong>process_api</strong>：PID=1，Rust写的一个充当根进程的服务，算是沙盒里进程的控制面，负责init、挂载（系统级的）、网络、websocket api、cgroup/oom等内部进程管控。同时也会扮演接收外部发来的exec命令（比如CreateProcess，还有相关的stdin/stdout/stderr, signal等）</p> </li> <li> <p><strong>environment-manager</strong>：算是MA产品层的runner了，负责一些初始化工作，比如git、snapshot、claude code配置、MCP配置、lease、观测相关等等这些活，会根据claude code是否跑在沙盒里行为有差异</p> </li> <li> <p><strong>rclone-filestore</strong>：负责挂载实际Agent会用到的数据，比如上传文件、记忆、skills等，通过FUSE挂载的，后端是到Anthropic的HTTP API（api.anthropic.com）的，不直接到对象存储</p> </li> </ul> <p>到这里基本上有个整体的认知了，刚好有搜到一些相关的文章：</p> <ul> <li> <p><a href="https://johnsonlee.io/2026/03/28/when-claude-starts-to-awaken.en/?lang=en">https://johnsonlee.io/2026/03/28/when-claude-starts-to-awaken.en/?lang=en</a></p> </li> <li> <p><a href="https://aprilnea.me/zh/blog/reverse-engineering-claude-code-antspace">https://aprilnea.me/zh/blog/reverse-engineering-claude-code-antspace</a></p> </li> <li> <p><a href="https://github.com/AprilNEA/reverse-engineering-claude-code-antspace">https://github.com/AprilNEA/reverse-engineering-claude-code-antspace</a></p> </li> </ul> <p>从Johnson Lee的文章可以i看出3月份的时候沙盒还是在gVisor里的，并且被claude code自己突破了！（这里也可以展示隔离性差异，gVisor这种虚拟的内核，隔离性依然不够，更别说基于namespace和cgroup的docker/runc了，也能反应为什么现在那么多采用firecracker的原因了）</p> <p>到现在已经不再使用gVisor了，而是使用firecracker启动microVM，firecracker也是现在行业主流的方案了，E2B也是基于这个方案。但是这个方案有一些条件，最重要的就是<strong>KVM</strong>，这意味着基本上需要走到裸金属/裸机的层面才能轻松拿到KVM，当然有一些将KVM nest进去的也可以，比如我用gcloud拉了n2-standard-4机型的虚拟机是可以的（配套–min-cpu-platform=”Intel Cascade Lake” –enable-nested-virtualization）</p> <p>另外AprilNEA那篇文章则是挖到了3月份firecracker的版本，差异在于，里面有没有stripped的environment-runner版本，可以轻易的逆向出源代码，我相信国内外其他致敬的产品肯定毫不吝啬的分析拿这份去照着开发了🤡</p> <p>看下那篇文章中列的</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">internal</span><span class="o">/</span>
<span class="err">├──</span> <span class="n">api</span><span class="o">/</span>                  <span class="c1"># API 客户端（会话路由、任务轮询、重试）
</span><span class="err">├──</span> <span class="n">auth</span><span class="o">/</span>                 <span class="c1"># GitHub App Token 提供者
</span><span class="err">├──</span> <span class="n">claude</span><span class="o">/</span>               <span class="c1"># Claude Code 安装、升级、执行
</span><span class="err">├──</span> <span class="n">config</span><span class="o">/</span>               <span class="c1"># 会话模式（new/resume/resume-cached/setup-only）
</span><span class="err">├──</span> <span class="n">envtype</span><span class="o">/</span>
<span class="err">│</span>   <span class="err">├──</span> <span class="n">anthropic</span><span class="o">/</span>        <span class="c1"># Anthropic 托管环境
</span><span class="err">│</span>   <span class="err">└──</span> <span class="n">byoc</span><span class="o">/</span>             <span class="c1"># 自带云（Bring Your Own Cloud）
</span><span class="err">├──</span> <span class="n">gitproxy</span><span class="o">/</span>             <span class="c1"># Git 凭证代理服务器
</span><span class="err">├──</span> <span class="nb">input</span><span class="o">/</span>                <span class="c1"># 标准输入解析 + 密钥处理
</span><span class="err">├──</span> <span class="n">manager</span><span class="o">/</span>              <span class="c1"># 会话管理器、MCP 配置、技能提取
</span><span class="err">├──</span> <span class="n">mcp</span><span class="o">/</span>
<span class="err">│</span>   <span class="err">└──</span> <span class="n">servers</span><span class="o">/</span>
<span class="err">│</span>       <span class="err">├──</span> <span class="n">codesign</span><span class="o">/</span>     <span class="c1"># 代码签名 MCP 服务器
</span><span class="err">│</span>       <span class="err">└──</span> <span class="n">supabase</span><span class="o">/</span>     <span class="c1"># Supabase 集成 MCP 服务器
</span><span class="err">├──</span> <span class="n">orchestrator</span><span class="o">/</span>         <span class="c1"># 轮询循环、Hook、身份发现
</span><span class="err">├──</span> <span class="n">podmonitor</span><span class="o">/</span>           <span class="c1"># Kubernetes 租约管理
</span><span class="err">├──</span> <span class="n">process</span><span class="o">/</span>              <span class="c1"># 进程执行 + 脚本运行器
</span><span class="err">├──</span> <span class="n">sandbox</span><span class="o">/</span>              <span class="c1"># 沙箱运行时配置
</span><span class="err">├──</span> <span class="n">session</span><span class="o">/</span>              <span class="c1"># 活动记录器
</span><span class="err">├──</span> <span class="n">sources</span><span class="o">/</span>              <span class="c1"># Git 克隆 + 源码分类
</span><span class="err">├──</span> <span class="n">tunnel</span><span class="o">/</span>               <span class="c1"># WebSocket 隧道 + 动作处理
</span><span class="err">│</span>   <span class="err">└──</span> <span class="n">actions</span><span class="o">/</span>
<span class="err">│</span>       <span class="err">├──</span> <span class="n">deploy</span><span class="o">/</span>       <span class="c1"># ← 重点在这里
</span><span class="err">│</span>       <span class="err">├──</span> <span class="n">snapshot</span><span class="o">/</span>     <span class="c1"># 文件快照
</span><span class="err">│</span>       <span class="err">└──</span> <span class="n">status</span><span class="o">/</span>       <span class="c1"># 状态上报
</span><span class="err">└──</span> <span class="n">util</span><span class="o">/</span>                 <span class="c1"># Git 工具、锁文件、重试、流式日志
</span></code></pre></div></div> <p>信息量还不小：</p> <ul> <li> <p>可以看出Anthropic的内部代码在GitHub上<a href="http://github.com/anthropics/anthropic">github.com/anthropics/anthropic</a>这个Private Repo里，是一个Monorepo，大部分Go写的</p> </li> <li> <p>笑抽了一点是：<code class="language-plaintext highlighter-rouge">**github.com/mark3labs/mcp-go v0.37.0**</code>** ** MCP Go SDK不是用官方的，而是用三方的</p> </li> <li> <p>里面关于Vercel的PaaS竞品AntSpace还挺有意思的，不确定后续是否会对外开放，耐人寻味，引用下文章里的一段话</p> </li> </ul> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517122_33-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517122_33-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517122_33-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517122_33.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <ul> <li>byoc看着就是针对B端用户做的东西</li> </ul> <p>另外其实可以发现，Claude Code可以运行在沙盒里<code class="language-plaintext highlighter-rouge">/opt/claude-code</code>，但是生产环境下默认是跑在了沙盒的外围，这也是企业场景下，规模化下的最优解，因为耗费资源的是沙盒，ClaudeCode外置也有几个好处：</p> <ul> <li> <p>让LLMs调用在沙盒外，杜绝泄漏风险</p> </li> <li> <p>Agent运行时在沙盒外，减少安全问题</p> </li> <li> <p>可以进一步针对性做大规模场景下的优化</p> </li> <li> <p>甚至可以切换成非Claude Code单体版本，用专门的云端版本取代</p> </li> </ul> <h1 id="diy">DIY</h1> <p>RE里讲了不少，但是还是比较散点的想法，因为很多信息都丢失在探索的过程中了，没有第一时间一一记录下来。不过之前也三不五时的分享过一些相关的，这一次依然是想要分享一种hacker的思维，正是因为现在有了AI，我们可以借助AI能撬动更多的东西，很多时候思维才是瓶颈。现在很多时候只需要给AI一点点牵引，就可以得到很多东西，这也是如何在大家都拥有同样的AI之下杀出来的能力之一</p> <p>有了这些信息，下一步就是顺其自然的Build一个了，AI让我们Build一个东西的成本无限低，分析的过程学到了很多，做的过程依然可以学到很多。这也是做中学的妙趣所在</p> <p>直接1v1复刻Anthropic Managed Agents的Web，提取了那些菜单</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517122_34-480.webp 480w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517122_34-800.webp 800w,/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517122_34-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-07-08-inside-anthropic-managed-agents/1783517122_34.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>apiserver做控制面，会落数据到DB，实际上会通过orchestrator去调度，期间会通过sandboxd去拉沙盒（firecracker/docker），process-api对标了process_api，env-runner用来在沙盒里承接任务的，fs-bridge会负责同步相关的工作</p> <p>这边有个不同的设计，就是预期是兼容codex/claude-code/opencode/pi/自研的Agent，因为实际上是可以根据不同的Agent来决定是运行在沙盒里还是沙盒外的，比如pi这种可以配置很多东西的可以放到外部跑，codex开放度不足的可以进到容器里跑</p> <p>不过最近事情多，这个项目Working In Progress，后续会持续推进收尾掉，有兴趣的可以关注一下后续的进展，到时候我也会写一篇更加详细的文章来聊聊怎么在企业场景下建设一个类似Managed Agents这样的能力底座，里面会有什么坑，会用到什么技术，应该怎么规划架构等等这些见解、想法和解决方案</p> <h1 id="写在最后">写在最后</h1> <p>这篇文章本来上个月就该发了，但是事情确实太多，一拖再拖，想想我这一两年来一直坚信的一句话”完成比完美更重要“，因此我觉得我应该让这篇文章完成，我相信让需要的人看到的价值远比我心中想要达到的那个完美更重要，因此就尽量收尾掉这篇文章，不希望像我的很多Drafts烂在笔记本里</p> <p>依然是一篇讲述自己怎么整活的文章，跟前面说的一样，有了AI，可能变傻变懒，但也可以站在巨人的肩膀上去做更多以前很难轻易做到的事情，重要的在于自己是怎么想怎么<strong>做</strong>的，解放想法后，就是Push执行了，其他的就是外部机遇了，爱情、事业和人生，何尝不是如此？</p>]]></content><author><name></name></author><category term="AI"/><category term="AI"/><summary type="html"><![CDATA[Anthropic的Managed Agents出来了几个月了，这波刚好分析一下，从技术触发，分析Anthropic在未来的布局，我个人是觉得很值得AI相关的公司留意这个方向的，很值得深思]]></summary></entry><entry xml:lang="zh"><title type="html">Harness的新理解</title><link href="https://ifuryst.github.io/blog/2026/harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/" rel="alternate" type="text/html" title="Harness的新理解"/><published>2026-05-28T00:00:00+00:00</published><updated>2026-05-28T00:00:00+00:00</updated><id>https://ifuryst.github.io/blog/2026/harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3</id><content type="html" xml:base="https://ifuryst.github.io/blog/2026/harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/"><![CDATA[<p>这次的通透来自于<a href="https://www.xiaoyuzhoufm.com/episode/6a15a2cbff7b9a8c0a5b953f?s=eyJ1IjogIjY4Mzk3OTM0ZDFkMzUwNzI2OWRiOTQ4NCJ9">张小珺和戴雨森的创投观察第2集</a></p> <p>刚好最近在重新盘点审视Harness，这两天听这集播客带来了一些不错的想法！</p> <p>我们看Codex/Claude Code/OpenClaw这些其实都可以说是Harness，本质上是通过一些配套的机制、工具和环境让模型更好的发挥能力。</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-28-harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/1779949798_3-480.webp 480w,/assets/img/2026-05-28-harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/1779949798_3-800.webp 800w,/assets/img/2026-05-28-harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/1779949798_3-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-28-harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/1779949798_3.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>看我用ChatGPT Gen的这张图，简洁明了的介绍了。</p> <p>做个baseline的Harness很简单，codex、openclaw、openhands、hermes开源的，claude code有泄漏的源码，clone一下，和ai cowork一下就能得到一个in-house版本的harness。那harness的价值在哪里？</p> <p>我觉得把Harness类比成Agent OS非常make sense！</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-28-harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/1779949799_4-480.webp 480w,/assets/img/2026-05-28-harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/1779949799_4-800.webp 800w,/assets/img/2026-05-28-harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/1779949799_4-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-28-harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/1779949799_4.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>根据我的想法快速Gen了一张，忽略细节，只是想快速表达想法（暂时不想花太多effort去手撕这个图）。</p> <p>我们可以把LLM想象成以前的CPU，Harness就和OS一样，会针对不同的LLM做Driver，这样LLM本质是可以任意替换的，只是效果差异而已。外层配套的沙盒、Mem、Tool之类的可以不耦合。</p> <p>那么Harness的意义就出来了，比如现在的Claude Code的开放生态并不足，Codex的有APP Server并且官方态度明确支持开放（类似Crypto里的ETH的生态力）。也就出现了很多build on top of “harness”的产品，比如：</p> <ul> <li> <p>slock（原kimi cli团队出来做的）这种，本机直接跑一个daemon通过ws连到其服务端，这样slock的服务端可以下发命令给codex/claude code</p> </li> <li> <p>另外Codex.app也可以看作是基于Codex这个Harness本身去做出来的</p> </li> <li> <p>类似郭宇的wanman这种也是一个不错的点，以前都是Google OAuth2.0登陆一个网站，现在直接是Codex OAuth2.0登陆了，细品</p> </li> <li> <p>Managed Agents这种就是Agent OS的一方服务，甚至更深，可以理解成原来OS里的syscall这种系统调用级别，也就是不需要在Harness之上再做什么动作了，可以对外直接提供这个能力了，这种也就是0代码或者开箱即用的能力</p> </li> </ul> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-28-harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/1779949799_5-480.webp 480w,/assets/img/2026-05-28-harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/1779949799_5-800.webp 800w,/assets/img/2026-05-28-harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/1779949799_5-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-28-harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/1779949799_5.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>依然是GEN的， 不纠结细节，大体是对的，Codex.app现在就是一个壳，底座是基于codex cli的</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-28-harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/1779949800_6-480.webp 480w,/assets/img/2026-05-28-harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/1779949800_6-800.webp 800w,/assets/img/2026-05-28-harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/1779949800_6-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-28-harness%E7%9A%84%E6%96%B0%E7%90%86%E8%A7%A3/1779949800_6.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>可以看到自己打包了codex cli。只不过这是一方的套壳，但是理论上任何人都可以基于codex cli这个harness本身做出一个open-codex.app或任何产品</p> <p>这个就是Harness或者说Agent OS的机会，如果把其当作一个产品，未来是可以单独把其当作OS去售卖，也可以支撑上层APP的快速开花，或者直接以SaaS版本对外提供。并且像codex/claude code本身都是重单机、重本地的版本，Managed Agents背后一定有一套企业级别的Harness，这种才是应该追求的，这个现在讨论和做的人还比较少，但是随着B端对AI盈利的确定性持续增加，必然会成为大家厮杀的一个主战场。</p> <p>因此Harness/Agent OS应该做的本身就是做好这个层：</p> <ul> <li> <p>对外屏蔽掉这些细节，体现产品力、易用灵活、可扩展、稳定等</p> </li> <li> <p>内部做好各种配套的机制来保证稳定、时延、scale、分布、高可用等</p> </li> <li> <p>对下针对LLM做好适配，甚至可能会需要有一些模型和llm infra的认知</p> </li> </ul> <p>当理清了这些概念后，就会有个清晰的图景去引领迭代的方向。认知的提升只能多看多听多试了，现在变化太快，给决策者带来的挑战很大，时刻保持敏感性和大量的信息输入和内化熵减很重要</p>]]></content><author><name></name></author><category term="AI"/><category term="AI"/><summary type="html"><![CDATA[这次的通透来自于张小珺和戴雨森的创投观察第2集]]></summary></entry><entry xml:lang="zh"><title type="html">LLM Infra 101 v0.5: KV Cache分块管理</title><link href="https://ifuryst.github.io/blog/2026/llm-infra-101-v0-5-kv-cache-block/" rel="alternate" type="text/html" title="LLM Infra 101 v0.5: KV Cache分块管理"/><published>2026-05-28T00:00:00+00:00</published><updated>2026-05-28T00:00:00+00:00</updated><id>https://ifuryst.github.io/blog/2026/llm-infra-101-v0-5-kv-cache-block</id><content type="html" xml:base="https://ifuryst.github.io/blog/2026/llm-infra-101-v0-5-kv-cache-block/"><![CDATA[<p>系列的第六集，前面的可以看：</p> <ol> <li> <p><a href="https://www.ifuryst.com/blog/2026/llm-infra-101-model-inference/">LLM Infra 101 v0.0: 推理模型</a></p> </li> <li> <p><a href="https://www.ifuryst.com/blog/2026/llm-infra-101-v0-1-openai-compatible-api/">LLM Infra 101 v0.1: API调用</a></p> </li> <li> <p><a href="https://www.ifuryst.com/blog/2026/llm-infra-101-v0-2-kv-cache-decode/">LLM Infra 101 v0.2: KV Cache</a></p> </li> <li> <p><a href="https://www.ifuryst.com/blog/2026/llm-infra-101-v0-3-static-batching/">LLM Infra 101 v0.3: 静态批处理</a></p> </li> <li> <p><a href="https://www.ifuryst.com/blog/2026/llm-infra-101-v0-4-continuous-batching/">LLM Infra 101 v0.4: 连续批处理</a></p> </li> </ol> <p>这一期的代码在 <a href="https://github.com/iFurySt/nanoLLMServe/tree/release/v0.5.0">https://github.com/iFurySt/nanoLLMServe/tree/release/v0.5.0</a></p> <p>前面已经完成了Continuous Batching的建设了，这波我们会进一步做KV Cache的Block管理模型，为了后续完整的Paged Attention做个基础。</p> <p>前面我们让请求动态进入Batch，动态完成离开Batch，这里面有个比较大的问题，就是请求的序列长度是不一样的，KV Cache的占用也不同，这样我们每个请求都按照一个很大的连续空间去与留KV Cache空间，就会有很多显存浪费了。举例说明一下：</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Request</span> <span class="n">A</span><span class="p">:</span> <span class="n">prompt</span> <span class="mi">200</span> <span class="n">tokens</span><span class="err">，</span><span class="n">最终生成</span> <span class="mi">100</span> <span class="n">tokens</span>  <span class="err">→</span> <span class="n">需要</span> <span class="mi">300</span> <span class="n">tokens</span> <span class="n">的</span> <span class="n">KV</span>
<span class="n">Request</span> <span class="n">B</span><span class="p">:</span> <span class="n">prompt</span> <span class="mi">4</span><span class="p">,</span><span class="mi">000</span> <span class="n">tokens</span><span class="err">，</span><span class="n">最终生成</span> <span class="mi">1</span><span class="p">,</span><span class="mi">000</span> <span class="n">tokens</span> <span class="err">→</span> <span class="n">需要</span> <span class="mi">5</span><span class="p">,</span><span class="mi">000</span> <span class="n">tokens</span> <span class="n">的</span> <span class="n">KV</span>
<span class="n">Request</span> <span class="n">C</span><span class="p">:</span> <span class="n">prompt</span> <span class="mi">50</span> <span class="n">tokens</span><span class="err">，</span><span class="n">最终生成</span> <span class="mi">20</span> <span class="n">tokens</span> <span class="err">→</span> <span class="n">需要</span> <span class="mi">70</span> <span class="n">tokens</span> <span class="n">的</span> <span class="n">KV</span>

<span class="n">Req</span> <span class="n">A</span> <span class="n">实际用</span> <span class="mi">300</span>   <span class="o">/</span> <span class="n">预留</span> <span class="mi">8000</span>  <span class="err">→</span> <span class="n">大量浪费</span>
<span class="n">Req</span> <span class="n">B</span> <span class="n">实际用</span> <span class="mi">5000</span>  <span class="o">/</span> <span class="n">预留</span> <span class="mi">8000</span>  <span class="err">→</span> <span class="n">还行</span>
<span class="n">Req</span> <span class="n">C</span> <span class="n">实际用</span> <span class="mi">70</span>    <span class="o">/</span> <span class="n">预留</span> <span class="mi">8000</span>  <span class="err">→</span> <span class="n">极大浪费</span>
</code></pre></div></div> <p>大概这样的一个情况，对传统的软件开发了解比较深的人应该有熟悉的感觉，可以直接类比联想到OS里的分页Paging逻辑，也就是OS抽象出一个内存页的内存单元，实际上内存页在物理内存里存放的位置不需要连续了。实际上vLLM的Paged Attention也是沿用了这个理念了，把申请的显存打成一个一个的block，这样可以减少显存的碎片化和无用消耗。（所以说万变不离其宗，有些东西不要停留在表面，理解底层原理，换到不同的应用场景下，都能从根源倒推上去，而不会每次都是八股文式的记忆，也能有很多乐趣），我们的block分块管理就是其中的一环，原来的布局是：</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Req</span> <span class="n">A</span><span class="p">:</span>
<span class="p">[....................</span> <span class="mi">8000</span> <span class="n">token</span> <span class="nb">buffer</span> <span class="p">....................]</span>

<span class="n">Req</span> <span class="n">B</span><span class="p">:</span>
<span class="p">[....................</span> <span class="mi">8000</span> <span class="n">token</span> <span class="nb">buffer</span> <span class="p">....................]</span>

<span class="n">Req</span> <span class="n">C</span><span class="p">:</span>
<span class="p">[....................</span> <span class="mi">8000</span> <span class="n">token</span> <span class="nb">buffer</span> <span class="p">....................]</span>
</code></pre></div></div> <p>现在的请求和显存布局会变成这样</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Req</span> <span class="n">A</span><span class="p">:</span>
<span class="p">[</span><span class="n">block</span><span class="p">][</span><span class="n">block</span><span class="p">]</span>

<span class="n">Req</span> <span class="n">B</span><span class="p">:</span>
<span class="p">[</span><span class="n">block</span><span class="p">][</span><span class="n">block</span><span class="p">][</span><span class="n">block</span><span class="p">][</span><span class="n">block</span><span class="p">][</span><span class="n">block</span><span class="p">][</span><span class="n">block</span><span class="p">]</span>

<span class="n">Req</span> <span class="n">C</span><span class="p">:</span>
<span class="p">[</span><span class="n">block</span><span class="p">]</span>
</code></pre></div></div> <p>现在是以固定大小的KV块/页来分配，block在请求结束后会收到池子。这样的显存利用率一下提升了，并且可以动态增长，按需分配。</p> <p>大概知道这个原理后，我们来看看怎么实现</p> <h1 id="实现">实现</h1> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">.</span>
<span class="err">├──</span> <span class="n">benchmarks</span><span class="o">/</span>
<span class="err">│</span>   <span class="err">└──</span> <span class="n">benchmark_block_manager</span><span class="p">.</span><span class="n">py</span>      <span class="c1"># 新增 block KV cache 碎片率 benchmark
</span><span class="err">├──</span> <span class="n">src</span><span class="o">/</span>
<span class="err">│</span>   <span class="err">└──</span> <span class="n">nanollmserve</span><span class="o">/</span>
<span class="err">│</span>       <span class="err">├──</span> <span class="n">cache</span><span class="o">/</span>
<span class="err">│</span>       <span class="err">│</span>   <span class="err">├──</span> <span class="n">__init__</span><span class="p">.</span><span class="n">py</span>             <span class="c1"># 导出 block manager 相关类型
</span><span class="err">│</span>       <span class="err">│</span>   <span class="err">└──</span> <span class="n">block_manager</span><span class="p">.</span><span class="n">py</span>        <span class="c1"># 核心：KVBlockManager / block table / usage metrics
</span><span class="err">│</span>       <span class="err">└──</span> <span class="n">engine</span><span class="o">/</span>
<span class="err">│</span>           <span class="err">└──</span> <span class="n">engine</span><span class="p">.</span><span class="n">py</span>               <span class="c1"># 在生成生命周期里接入 allocate / append / release
</span><span class="err">└──</span> <span class="n">tests</span><span class="o">/</span>
    <span class="err">├──</span> <span class="n">test_block_manager</span><span class="p">.</span><span class="n">py</span>           <span class="c1"># block 分配、追加、释放、超分配测试
</span>    <span class="err">├──</span> <span class="n">test_benchmark_block_manager</span><span class="p">.</span><span class="n">py</span> <span class="c1"># benchmark 汇总字段测试
</span>    <span class="err">└──</span> <span class="n">test_engine</span><span class="p">.</span><span class="n">py</span>                  <span class="c1"># engine 生成时 block 生命周期测试
</span></code></pre></div></div> <p>相关改动是这些文件，核心是增加了<code class="language-plaintext highlighter-rouge">KVBlockManager</code></p> <h2 id="kv-block">KV Block</h2> <p>首先引入了一个Block单元</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@dataclass</span><span class="p">(</span><span class="n">frozen</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">KVBlock</span><span class="p">:</span>
    <span class="n">block_id</span><span class="p">:</span> <span class="nb">int</span>        <span class="c1"># == blocks.index
</span>    <span class="n">capacity_tokens</span><span class="p">:</span> <span class="nb">int</span> <span class="c1"># == block_size
</span></code></pre></div></div> <p>KVBlock是一个固定大小的Token Block，用于承载Token的</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@dataclass</span>
<span class="k">class</span> <span class="nc">KVBlockManager</span><span class="p">:</span>
    <span class="n">total_blocks</span><span class="p">:</span> <span class="nb">int</span>
    <span class="n">block_size</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">16</span>

    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">total_blocks</span> <span class="o">&lt;</span> <span class="mi">1</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">total_blocks must be at least 1</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">block_size</span> <span class="o">&lt;</span> <span class="mi">1</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">block_size must be at least 1</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">blocks</span> <span class="o">=</span> <span class="p">[</span><span class="nc">KVBlock</span><span class="p">(</span><span class="n">block_id</span><span class="o">=</span><span class="n">index</span><span class="p">,</span> <span class="n">capacity_tokens</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">block_size</span><span class="p">)</span> <span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">total_blocks</span><span class="p">)]</span>
        <span class="n">self</span><span class="p">.</span><span class="n">free_block_ids</span><span class="p">:</span> <span class="n">deque</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="nf">deque</span><span class="p">(</span><span class="n">block</span><span class="p">.</span><span class="n">block_id</span> <span class="k">for</span> <span class="n">block</span> <span class="ow">in</span> <span class="n">self</span><span class="p">.</span><span class="n">blocks</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">request_tables</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">RequestBlockTable</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</code></pre></div></div> <p>这里默认定义了KVBlock默认可以承载16个token，id简单对应到block在所有blocks里的下标位置。</p> <p>另外增加某个请求和Block的关系表</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@dataclass</span>
<span class="k">class</span> <span class="nc">RequestBlockTable</span><span class="p">:</span>
    <span class="n">request_id</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">block_ids</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="nf">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
    <span class="n">token_count</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>
</code></pre></div></div> <p>比如：</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">req</span><span class="o">-</span><span class="n">a</span><span class="p">:</span>
  <span class="n">token_count</span> <span class="o">=</span> <span class="mi">33</span>
  <span class="n">block_ids</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">]</span>
</code></pre></div></div> <p>代表的就是req-a的KV Cache被影射到3个block上了，总共有33个token</p> <h2 id="kv-block-manager">KV Block Manager</h2> <p>有了上面这两个定义后，就可以开始针对请求和对应的token数量去分配block了。这个过程就需要一个管理器来负责管理所有block的生命周期，从分配到对应的请求，到回收block，以及彻底销毁 block，这些会统一在<code class="language-plaintext highlighter-rouge">KVBlockManager</code> 里管理</p> <p>我们按照整个流程来走，首先是请求进来后，会调用</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">_allocate_prompt_blocks</span><span class="p">(</span>
    <span class="n">kv_block_manager</span><span class="p">:</span> <span class="n">KVBlockManager</span> <span class="o">|</span> <span class="bp">None</span><span class="p">,</span>
    <span class="n">request_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
    <span class="n">prompt_tokens</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="k">if</span> <span class="n">kv_block_manager</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
        <span class="n">kv_block_manager</span><span class="p">.</span><span class="nf">allocate</span><span class="p">(</span><span class="n">request_id</span><span class="p">,</span> <span class="n">prompt_tokens</span><span class="p">)</span>
</code></pre></div></div> <p>内部是</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="nd">@dataclass</span>
<span class="k">class</span> <span class="nc">KVBlockManager</span><span class="p">:</span>
    <span class="n">total_blocks</span><span class="p">:</span> <span class="nb">int</span>
    <span class="n">block_size</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">16</span>

		<span class="c1"># ...
</span>
    <span class="k">def</span> <span class="nf">allocate</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">request_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">token_count</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">RequestBlockTable</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">request_id</span> <span class="ow">in</span> <span class="n">self</span><span class="p">.</span><span class="n">request_tables</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">request already has allocated blocks: </span><span class="si">{</span><span class="n">request_id</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">token_count</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">token_count must be non-negative</span><span class="sh">"</span><span class="p">)</span>

        <span class="n">needed_blocks</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">_blocks_for_tokens</span><span class="p">(</span><span class="n">token_count</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="nf">_ensure_free_blocks</span><span class="p">(</span><span class="n">needed_blocks</span><span class="p">)</span>
        <span class="n">table</span> <span class="o">=</span> <span class="nc">RequestBlockTable</span><span class="p">(</span>
            <span class="n">request_id</span><span class="o">=</span><span class="n">request_id</span><span class="p">,</span>
            <span class="n">block_ids</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="nf">_take_blocks</span><span class="p">(</span><span class="n">needed_blocks</span><span class="p">),</span>
            <span class="n">token_count</span><span class="o">=</span><span class="n">token_count</span><span class="p">,</span>
        <span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">request_tables</span><span class="p">[</span><span class="n">request_id</span><span class="p">]</span> <span class="o">=</span> <span class="n">table</span>
        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="nf">snapshot_request</span><span class="p">(</span><span class="n">request_id</span><span class="p">)</span>
</code></pre></div></div> <p>会根据token数量计算需要几个block</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mi">9</span> <span class="n">tokens</span>  <span class="o">-&gt;</span> <span class="nf">ceil</span><span class="p">(</span><span class="mi">9</span> <span class="o">/</span> <span class="mi">16</span><span class="p">)</span>  <span class="o">=</span> <span class="mi">1</span> <span class="n">block</span>
<span class="mi">17</span> <span class="n">tokens</span> <span class="o">-&gt;</span> <span class="nf">ceil</span><span class="p">(</span><span class="mi">17</span> <span class="o">/</span> <span class="mi">16</span><span class="p">)</span> <span class="o">=</span> <span class="mi">2</span> <span class="n">blocks</span>
<span class="mi">33</span> <span class="n">tokens</span> <span class="o">-&gt;</span> <span class="nf">ceil</span><span class="p">(</span><span class="mi">33</span> <span class="o">/</span> <span class="mi">16</span><span class="p">)</span> <span class="o">=</span> <span class="mi">3</span> <span class="n">blocks</span>
</code></pre></div></div> <p>然后到Free Pool里确认是否有足够的block（这里非常基础的版本，初始化默认分配<code class="language-plaintext highlighter-rouge">total_blocks</code>块，后续没有动态增加分配之类的管理），然后分配这些blocks给对应的Req并记录对应的关系表。</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">free_block_ids</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="n">request_tables</span> <span class="o">=</span> <span class="p">{}</span>

<span class="c1"># 变成
</span>
<span class="n">free_block_ids</span> <span class="o">=</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="n">request_tables</span> <span class="o">=</span> <span class="p">{</span>
  <span class="sh">"</span><span class="s">req-a</span><span class="sh">"</span><span class="p">:</span> <span class="n">block_ids</span><span class="o">=</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span> <span class="n">token_count</span><span class="o">=</span><span class="mi">33</span>
<span class="p">}</span>
</code></pre></div></div> <p>接着在Decode生成Token后，会调用</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">_append_generated_block_token</span><span class="p">(</span>
    <span class="n">kv_block_manager</span><span class="p">:</span> <span class="n">KVBlockManager</span> <span class="o">|</span> <span class="bp">None</span><span class="p">,</span>
    <span class="n">request_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="k">if</span> <span class="n">kv_block_manager</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
        <span class="n">kv_block_manager</span><span class="p">.</span><span class="nf">append_tokens</span><span class="p">(</span><span class="n">request_id</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</code></pre></div></div> <p>内部是</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@dataclass</span>
<span class="k">class</span> <span class="nc">KVBlockManager</span><span class="p">:</span>
    <span class="n">total_blocks</span><span class="p">:</span> <span class="nb">int</span>
    <span class="n">block_size</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">16</span>

		<span class="c1"># ...
</span>
    <span class="k">def</span> <span class="nf">append_tokens</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">request_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">token_count</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">1</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">RequestBlockTable</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">token_count</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">token_count must be non-negative</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">request_id</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">self</span><span class="p">.</span><span class="n">request_tables</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">KeyError</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">request has no allocated blocks: </span><span class="si">{</span><span class="n">request_id</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">token_count</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="nf">snapshot_request</span><span class="p">(</span><span class="n">request_id</span><span class="p">)</span>

        <span class="n">table</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">request_tables</span><span class="p">[</span><span class="n">request_id</span><span class="p">]</span>
        <span class="n">old_blocks</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">_blocks_for_tokens</span><span class="p">(</span><span class="n">table</span><span class="p">.</span><span class="n">token_count</span><span class="p">)</span>
        <span class="n">new_token_count</span> <span class="o">=</span> <span class="n">table</span><span class="p">.</span><span class="n">token_count</span> <span class="o">+</span> <span class="n">token_count</span>
        <span class="n">new_blocks</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">_blocks_for_tokens</span><span class="p">(</span><span class="n">new_token_count</span><span class="p">)</span>
        <span class="n">additional_blocks</span> <span class="o">=</span> <span class="n">new_blocks</span> <span class="o">-</span> <span class="n">old_blocks</span>
        <span class="n">self</span><span class="p">.</span><span class="nf">_ensure_free_blocks</span><span class="p">(</span><span class="n">additional_blocks</span><span class="p">)</span>
        <span class="n">table</span><span class="p">.</span><span class="n">block_ids</span><span class="p">.</span><span class="nf">extend</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="nf">_take_blocks</span><span class="p">(</span><span class="n">additional_blocks</span><span class="p">))</span>
        <span class="n">table</span><span class="p">.</span><span class="n">token_count</span> <span class="o">=</span> <span class="n">new_token_count</span>
        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="nf">snapshot_request</span><span class="p">(</span><span class="n">request_id</span><span class="p">)</span>
</code></pre></div></div> <p>基本上就是告诉Manager增加了一个token，然后会确认一下是否需要增加block，增加的话需要看看是否还有空闲的block可用</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">req</span><span class="o">-</span><span class="n">a</span><span class="p">:</span> <span class="mi">48</span> <span class="n">tokens</span> <span class="o">-&gt;</span> <span class="mi">49</span> <span class="n">tokens</span>

<span class="n">原来</span> <span class="mi">3</span> <span class="n">blocks</span>
<span class="n">现在需要</span> <span class="mi">4</span> <span class="n">blocks</span>
<span class="n">追加一个</span> <span class="n">block</span>
</code></pre></div></div> <p>请求完成后会释放</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">_release_blocks</span><span class="p">(</span><span class="n">kv_block_manager</span><span class="p">:</span> <span class="n">KVBlockManager</span> <span class="o">|</span> <span class="bp">None</span><span class="p">,</span> <span class="n">request_ids</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="k">if</span> <span class="n">kv_block_manager</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
        <span class="k">return</span>
    <span class="k">for</span> <span class="n">request_id</span> <span class="ow">in</span> <span class="nf">reversed</span><span class="p">(</span><span class="n">request_ids</span><span class="p">):</span>
        <span class="n">kv_block_manager</span><span class="p">.</span><span class="nf">release</span><span class="p">(</span><span class="n">request_id</span><span class="p">)</span>
</code></pre></div></div> <p>会将这个Req直接从Block Table里删除，对应的blocks放回到Free Pool</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@dataclass</span>
<span class="k">class</span> <span class="nc">KVBlockManager</span><span class="p">:</span>
    <span class="n">total_blocks</span><span class="p">:</span> <span class="nb">int</span>
    <span class="n">block_size</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">16</span>

		<span class="c1"># ...
</span>
    <span class="k">def</span> <span class="nf">release</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">request_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">int</span><span class="p">]:</span>
        <span class="n">table</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">request_tables</span><span class="p">.</span><span class="nf">pop</span><span class="p">(</span><span class="n">request_id</span><span class="p">,</span> <span class="bp">None</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">table</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">KeyError</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">request has no allocated blocks: </span><span class="si">{</span><span class="n">request_id</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
        <span class="n">released</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span><span class="n">table</span><span class="p">.</span><span class="n">block_ids</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">free_block_ids</span><span class="p">.</span><span class="nf">extend</span><span class="p">(</span><span class="n">released</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">released</span>
</code></pre></div></div> <p>到这里就完整的过完KV Cache Block的全生命周期了，再用High Level来看看</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tokenize</span> <span class="n">prompt</span>
<span class="o">-&gt;</span> <span class="n">allocate</span> <span class="n">prompt</span> <span class="n">blocks</span>
<span class="o">-&gt;</span> <span class="n">prefill</span>
<span class="o">-&gt;</span> <span class="n">sample</span> <span class="n">token</span>
<span class="o">-&gt;</span> <span class="n">append</span> <span class="n">generated</span> <span class="n">token</span> <span class="n">block</span>
<span class="o">-&gt;</span> <span class="n">decode</span> <span class="n">loop</span>
<span class="o">-&gt;</span> <span class="n">append</span> <span class="n">generated</span> <span class="n">token</span> <span class="n">block</span>
<span class="o">-&gt;</span> <span class="n">release</span> <span class="n">blocks</span> <span class="ow">in</span> <span class="k">finally</span>
</code></pre></div></div> <p>有了这个机制后，现在Continuous Batching会在请求完成后释放对应的Block，而不是等到整个Batch都结束才释放</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">step</span> <span class="mi">0</span><span class="p">:</span> <span class="n">short</span><span class="o">-</span><span class="mi">0</span> <span class="n">running</span>
<span class="n">step</span> <span class="mi">1</span><span class="p">:</span> <span class="n">short</span><span class="o">-</span><span class="mi">0</span> <span class="n">finished</span><span class="p">,</span> <span class="n">late</span><span class="o">-</span><span class="mi">1</span> <span class="n">running</span>
       <span class="o">-&gt;</span> <span class="n">release</span> <span class="n">short</span><span class="o">-</span><span class="mi">0</span> <span class="n">blocks</span> <span class="n">immediately</span>
<span class="n">step</span> <span class="mi">2</span><span class="p">:</span> <span class="n">late</span><span class="o">-</span><span class="mi">1</span> <span class="n">running</span>
<span class="bp">...</span>
</code></pre></div></div> <h1 id="推理">推理</h1> <p>我们还是走bench来观测Block的情况，里面几个指标的意思：</p> <table> <thead> <tr> <th><strong>字段</strong></th> <th><strong>含义</strong></th> </tr> </thead> <tbody> <tr> <td>used_blocks</td> <td>当前被请求占用的block数</td> </tr> <tr> <td>free_blocks</td> <td>当前空闲block数</td> </tr> <tr> <td>allocated_tokens</td> <td>请求真实需要的token数</td> </tr> <tr> <td>reserved_tokens</td> <td>block实际预留的token容量，也就是block_size的大小</td> </tr> <tr> <td>internal_fragmentation_tokens</td> <td>block内部浪费的token容量</td> </tr> <tr> <td>block_utilization</td> <td>allocated_tokens / reserved_tokens</td> </tr> </tbody> </table> <p>举例来说：</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">block_size</span> <span class="o">=</span> <span class="mi">16</span>
<span class="n">req</span><span class="o">-</span><span class="n">a</span> <span class="o">=</span> <span class="mi">9</span> <span class="n">tokens</span>
</code></pre></div></div> <p>需要一个block，这种情况下：</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">allocated_tokens</span> <span class="o">=</span> <span class="mi">9</span>
<span class="n">reserved_tokens</span> <span class="o">=</span> <span class="mi">16</span>
<span class="n">internal_fragmentation_tokens</span> <span class="o">=</span> <span class="mi">7</span>
<span class="n">utilization</span> <span class="o">=</span> <span class="mi">9</span> <span class="o">/</span> <span class="mi">16</span>
</code></pre></div></div> <p>这个时候利用率只有大概56%。我们来看bench的结果</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-28-llm-infra-101-v0-5-kv-cache-block/1779949860_7-480.webp 480w,/assets/img/2026-05-28-llm-infra-101-v0-5-kv-cache-block/1779949860_7-800.webp 800w,/assets/img/2026-05-28-llm-infra-101-v0-5-kv-cache-block/1779949860_7-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-28-llm-infra-101-v0-5-kv-cache-block/1779949860_7.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="n">base</span><span class="p">)</span> <span class="n">gpu</span><span class="o">-</span><span class="n">A100</span><span class="o">-</span><span class="mi">05</span> <span class="n">nanoLLMServe</span> <span class="c1"># CUDA_VISIBLE_DEVICES=0 PYTHONPATH=src /data/anaconda3/bin/python -m benchmarks.benchmark_block_manager \
</span>  <span class="o">--</span><span class="n">block</span><span class="o">-</span><span class="n">size</span> <span class="mi">16</span> \
  <span class="o">--</span><span class="n">total</span><span class="o">-</span><span class="n">blocks</span> <span class="mi">64</span> \
  <span class="o">--</span><span class="n">request</span><span class="o">-</span><span class="n">tokens</span> <span class="mi">9</span><span class="p">,</span><span class="mi">17</span><span class="p">,</span><span class="mi">33</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">41</span><span class="p">,</span><span class="mi">12</span>
<span class="p">{</span>
  <span class="sh">"</span><span class="s">block_size</span><span class="sh">"</span><span class="p">:</span> <span class="mi">16</span><span class="p">,</span>
  <span class="sh">"</span><span class="s">block_usage</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span>
    <span class="sh">"</span><span class="s">allocated_tokens</span><span class="sh">"</span><span class="p">:</span> <span class="mi">117</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">block_utilization</span><span class="sh">"</span><span class="p">:</span> <span class="mf">0.6647727272727273</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">free_blocks</span><span class="sh">"</span><span class="p">:</span> <span class="mi">53</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">internal_fragmentation_tokens</span><span class="sh">"</span><span class="p">:</span> <span class="mi">59</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">reserved_tokens</span><span class="sh">"</span><span class="p">:</span> <span class="mi">176</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">used_blocks</span><span class="sh">"</span><span class="p">:</span> <span class="mi">11</span>
  <span class="p">},</span>
  <span class="sh">"</span><span class="s">contiguous_fixed_slot_baseline</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span>
    <span class="sh">"</span><span class="s">internal_fragmentation_tokens</span><span class="sh">"</span><span class="p">:</span> <span class="mi">129</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">reserved_tokens</span><span class="sh">"</span><span class="p">:</span> <span class="mi">246</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">utilization</span><span class="sh">"</span><span class="p">:</span> <span class="mf">0.47560975609756095</span>
  <span class="p">},</span>
  <span class="sh">"</span><span class="s">fragmentation_tokens_saved_vs_contiguous</span><span class="sh">"</span><span class="p">:</span> <span class="mi">70</span><span class="p">,</span>
  <span class="sh">"</span><span class="s">request_tokens</span><span class="sh">"</span><span class="p">:</span> <span class="p">[</span>
    <span class="mi">9</span><span class="p">,</span>
    <span class="mi">17</span><span class="p">,</span>
    <span class="mi">33</span><span class="p">,</span>
    <span class="mi">5</span><span class="p">,</span>
    <span class="mi">41</span><span class="p">,</span>
    <span class="mi">12</span>
  <span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div> <p>模拟6个请求，每个请求的token数分别是9,17,33,5,41,12，Block size 16的情况下：</p> <table> <thead> <tr> <th><strong>Request</strong></th> <th><strong>实际 Tokens (</strong>allocated_tokens<strong>)</strong></th> <th><strong>使用 Blocks (</strong>used_blocks<strong>)</strong></th> <th><strong>预留容量 (</strong>reserved_tokens<strong>)</strong></th> <th><strong>内部碎片 (</strong>fragmentation<strong>)</strong></th> <th><strong>利用率</strong></th> </tr> </thead> <tbody> <tr> <td>Req A</td> <td>9</td> <td>1</td> <td>16</td> <td>7</td> <td>56.3%</td> </tr> <tr> <td>Req B</td> <td>17</td> <td>2</td> <td>32</td> <td>15</td> <td>53.1%</td> </tr> <tr> <td>Req C</td> <td>33</td> <td>3</td> <td>48</td> <td>15</td> <td>68.8%</td> </tr> <tr> <td>Req D</td> <td>5</td> <td>1</td> <td>16</td> <td>11</td> <td>31.3%</td> </tr> <tr> <td>Req E</td> <td>41</td> <td>3</td> <td>48</td> <td>7</td> <td>85.4%</td> </tr> <tr> <td>Req F</td> <td>12</td> <td>1</td> <td>16</td> <td>4</td> <td>75.0%</td> </tr> </tbody> </table> <p>因此实际上：</p> <table> <thead> <tr> <th><strong>指标</strong></th> <th><strong>数值</strong></th> </tr> </thead> <tbody> <tr> <td>实际 Tokens</td> <td>117</td> </tr> <tr> <td>使用 Blocks</td> <td>11</td> </tr> <tr> <td>Block Size</td> <td>16</td> </tr> <tr> <td>预留容量</td> <td>176</td> </tr> <tr> <td>内部碎片</td> <td>59</td> </tr> <tr> <td>总体利用率</td> <td>66.5%</td> </tr> </tbody> </table> <p>结果里有个<code class="language-plaintext highlighter-rouge">contiguous_fixed_slot_baseline</code> 对对照组，按照最长的41去分配连续的空间，我们也计算一下：</p> <table> <thead> <tr> <th><strong>Request</strong></th> <th><strong>实际 Tokens</strong></th> <th><strong>固定预留容量</strong></th> <th><strong>内部碎片</strong></th> <th><strong>利用率</strong></th> </tr> </thead> <tbody> <tr> <td>Req A</td> <td>9</td> <td>41</td> <td>32</td> <td>22.0%</td> </tr> <tr> <td>Req B</td> <td>17</td> <td>41</td> <td>24</td> <td>41.5%</td> </tr> <tr> <td>Req C</td> <td>33</td> <td>41</td> <td>8</td> <td>80.5%</td> </tr> <tr> <td>Req D</td> <td>5</td> <td>41</td> <td>36</td> <td>12.2%</td> </tr> <tr> <td>Req E</td> <td>41</td> <td>41</td> <td>0</td> <td>100%</td> </tr> <tr> <td>Req F</td> <td>12</td> <td>41</td> <td>29</td> <td>29.3%</td> </tr> </tbody> </table> <p>整体是</p> <table> <thead> <tr> <th><strong>指标</strong></th> <th><strong>数值</strong></th> </tr> </thead> <tbody> <tr> <td>实际 Tokens</td> <td>117</td> </tr> <tr> <td>请求数</td> <td>6</td> </tr> <tr> <td>每请求固定槽位</td> <td>41</td> </tr> <tr> <td>总预留容量</td> <td>246</td> </tr> <tr> <td>内部碎片</td> <td>129</td> </tr> <tr> <td>总体利用率</td> <td>47.6%</td> </tr> </tbody> </table> <p>拉到一个表里对比一下：</p> <table> <thead> <tr> <th><strong>指标</strong></th> <th><strong>Paged Blocks</strong></th> <th><strong>Fixed Contiguous</strong></th> <th><strong>改善</strong></th> </tr> </thead> <tbody> <tr> <td>实际 Tokens</td> <td>117</td> <td>117</td> <td>—</td> </tr> <tr> <td>预留容量</td> <td>176</td> <td>246</td> <td>↓ 70</td> </tr> <tr> <td>内部碎片</td> <td>59</td> <td>129</td> <td>↓ 70</td> </tr> <tr> <td>利用率</td> <td>66.5%</td> <td>47.6%</td> <td>↑ 18.9%</td> </tr> </tbody> </table> <p>很直观的看出来，有了分块（分页）的处理后，整体的显存利用率都得到提升了</p> <h1 id="总结">总结</h1> <p>大块固定分配的内存（显存）块提升成按固定块去分配，虽然在单块里依然存在浪费的情况，但是范围局限在单个Block里了，这一点就已经将内存利用率提升上来了，另一点就是在一些请求结束后动态回收对应的内存用于分配给其他请求使用，又进一步提升利用率。</p> <p>最后还记得我们最早v0.2的时候做了KV Cache，针对单个请求用Prefill去做KV Cache，但是实际上多个请求之间是有可能有一样的前缀KV Cache，我们后续就要继续做一个更接近生产级别的优化，前缀缓存Prefix Cache</p>]]></content><author><name></name></author><category term="AI"/><category term="AI"/><category term="LLMInfra-101"/><summary type="html"><![CDATA[系列的第六集，前面的可以看：]]></summary></entry><entry xml:lang="zh"><title type="html">LLM Infra 101 v0.4: 连续批处理</title><link href="https://ifuryst.github.io/blog/2026/llm-infra-101-v0-4-continuous-batching/" rel="alternate" type="text/html" title="LLM Infra 101 v0.4: 连续批处理"/><published>2026-05-25T00:00:00+00:00</published><updated>2026-05-25T00:00:00+00:00</updated><id>https://ifuryst.github.io/blog/2026/llm-infra-101-v0-4-continuous-batching</id><content type="html" xml:base="https://ifuryst.github.io/blog/2026/llm-infra-101-v0-4-continuous-batching/"><![CDATA[<p>系列的第五集，前面的可以看：</p> <ol> <li> <p><a href="https://www.ifuryst.com/blog/2026/llm-infra-101-model-inference/">LLM Infra 101 v0.0: 推理模型</a></p> </li> <li> <p><a href="https://www.ifuryst.com/blog/2026/llm-infra-101-v0-1-openai-compatible-api/">LLM Infra 101 v0.1: API调用</a></p> </li> <li> <p><a href="https://www.ifuryst.com/blog/2026/llm-infra-101-v0-2-kv-cache-decode/">LLM Infra 101 v0.2: KV Cache</a></p> </li> <li> <p><a href="https://www.ifuryst.com/blog/2026/llm-infra-101-v0-3-static-batching/">LLM Infra 101 v0.3: 静态批处理</a></p> </li> </ol> <p>这一期的代码在 <a href="https://github.com/iFurySt/nanoLLMServe/tree/release/v0.4.0">https://github.com/iFurySt/nanoLLMServe/tree/release/v0.4.0</a></p> <p>上期做到Static Batching，遗留下来的问题很明确：</p> <ul> <li> <p>新请求不能中途进入</p> </li> <li> <p>某个请求结束后，batch里这个请求的位置还会占用着不能释放出来</p> </li> <li> <p>整个batch生命周期被最慢的那个请求拖住</p> </li> </ul> <p>为了处理这些问题，我们需要引入连续批处理（Continuous Batching）</p> <h1 id="实现">实现</h1> <p>这次主要涉及的改动是</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">.</span>
├── src/
│   └── nanollmserve/
│       └── engine/
│           ├── engine.py              <span class="c"># 新增 generate_continuous_batch：运行中 admission、每步重建 active batch、完成请求移出</span>
│           └── scheduler.py           <span class="c"># 核心调度结构：waiting/running/finished 队列、RequestLifecycle、SchedulerStepStats</span>
└── tests/
    ├── test_benchmark_generate.py     <span class="c"># continuous_batch benchmark 汇总字段回归：active batch size / request count / scheduler steps</span>
    └── test_engine.py                 <span class="c"># 连续批处理行为回归：中途加入、完成移除、max_batch_size backpressure</span>
</code></pre></div></div> <h2 id="scheduler">Scheduler</h2> <p><code class="language-plaintext highlighter-rouge">src/nanollmserve/engine/scheduler.py</code></p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="sh">"""</span><span class="s">Teaching-scale continuous batching scheduler.</span><span class="sh">"""</span>

<span class="kn">from</span> <span class="n">__future__</span> <span class="kn">import</span> <span class="n">annotations</span>

<span class="kn">from</span> <span class="n">collections</span> <span class="kn">import</span> <span class="n">deque</span>
<span class="kn">from</span> <span class="n">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
<span class="kn">from</span> <span class="n">enum</span> <span class="kn">import</span> <span class="n">Enum</span>


<span class="k">class</span> <span class="nc">RequestLifecycle</span><span class="p">(</span><span class="nb">str</span><span class="p">,</span> <span class="n">Enum</span><span class="p">):</span>
    <span class="n">WAITING</span> <span class="o">=</span> <span class="sh">"</span><span class="s">waiting</span><span class="sh">"</span>
    <span class="n">RUNNING</span> <span class="o">=</span> <span class="sh">"</span><span class="s">running</span><span class="sh">"</span>
    <span class="n">FINISHED</span> <span class="o">=</span> <span class="sh">"</span><span class="s">finished</span><span class="sh">"</span>


<span class="nd">@dataclass</span><span class="p">(</span><span class="n">frozen</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">ContinuousBatchRequest</span><span class="p">:</span>
    <span class="n">request_id</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">prompt</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">max_new_tokens</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">32</span>
    <span class="n">arrival_step</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>


<span class="nd">@dataclass</span><span class="p">(</span><span class="n">frozen</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">SchedulerStepStats</span><span class="p">:</span>
    <span class="n">step</span><span class="p">:</span> <span class="nb">int</span>
    <span class="n">admitted_request_ids</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>
    <span class="n">running_request_ids</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>
    <span class="n">completed_request_ids</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>
    <span class="n">active_batch_size</span><span class="p">:</span> <span class="nb">int</span>


<span class="nd">@dataclass</span>
<span class="k">class</span> <span class="nc">ScheduledRequestState</span><span class="p">:</span>
    <span class="n">request</span><span class="p">:</span> <span class="n">ContinuousBatchRequest</span>
    <span class="n">lifecycle</span><span class="p">:</span> <span class="n">RequestLifecycle</span> <span class="o">=</span> <span class="n">RequestLifecycle</span><span class="p">.</span><span class="n">WAITING</span>
    <span class="n">admitted_step</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span>
    <span class="n">finished_step</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span>


<span class="nd">@dataclass</span>
<span class="k">class</span> <span class="nc">ContinuousBatchScheduler</span><span class="p">:</span>
    <span class="n">requests</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ContinuousBatchRequest</span><span class="p">]</span>
    <span class="n">max_batch_size</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span>
    <span class="n">waiting</span><span class="p">:</span> <span class="n">deque</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">]</span> <span class="o">=</span> <span class="nf">field</span><span class="p">(</span><span class="n">init</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
    <span class="n">running</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">]</span> <span class="o">=</span> <span class="nf">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">,</span> <span class="n">init</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
    <span class="n">finished</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">]</span> <span class="o">=</span> <span class="nf">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">,</span> <span class="n">init</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">max_batch_size</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span> <span class="ow">and</span> <span class="n">self</span><span class="p">.</span><span class="n">max_batch_size</span> <span class="o">&lt;</span> <span class="mi">1</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">max_batch_size must be at least 1</span><span class="sh">"</span><span class="p">)</span>

        <span class="n">seen</span><span class="p">:</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="nf">set</span><span class="p">()</span>
        <span class="n">indexed_states</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">ScheduledRequestState</span><span class="p">]]</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="k">for</span> <span class="n">index</span><span class="p">,</span> <span class="n">request</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">requests</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">request</span><span class="p">.</span><span class="n">request_id</span> <span class="ow">in</span> <span class="n">seen</span><span class="p">:</span>
                <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">duplicate request_id: </span><span class="si">{</span><span class="n">request</span><span class="p">.</span><span class="n">request_id</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
            <span class="n">seen</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="n">request_id</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">request</span><span class="p">.</span><span class="n">arrival_step</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">:</span>
                <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">arrival_step must be non-negative</span><span class="sh">"</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">request</span><span class="p">.</span><span class="n">max_new_tokens</span> <span class="o">&lt;</span> <span class="mi">1</span><span class="p">:</span>
                <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">max_new_tokens must be at least 1</span><span class="sh">"</span><span class="p">)</span>
            <span class="k">if</span> <span class="ow">not</span> <span class="n">request</span><span class="p">.</span><span class="n">prompt</span><span class="p">:</span>
                <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">prompt must not be empty</span><span class="sh">"</span><span class="p">)</span>
            <span class="n">indexed_states</span><span class="p">.</span><span class="nf">append</span><span class="p">((</span><span class="n">index</span><span class="p">,</span> <span class="nc">ScheduledRequestState</span><span class="p">(</span><span class="n">request</span><span class="o">=</span><span class="n">request</span><span class="p">)))</span>

        <span class="n">indexed_states</span><span class="p">.</span><span class="nf">sort</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">item</span><span class="p">:</span> <span class="p">(</span><span class="n">item</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">request</span><span class="p">.</span><span class="n">arrival_step</span><span class="p">,</span> <span class="n">item</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span>
        <span class="n">self</span><span class="p">.</span><span class="n">waiting</span> <span class="o">=</span> <span class="nf">deque</span><span class="p">(</span><span class="n">state</span> <span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">state</span> <span class="ow">in</span> <span class="n">indexed_states</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">has_work</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
        <span class="k">return</span> <span class="nf">bool</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">waiting</span> <span class="ow">or</span> <span class="n">self</span><span class="p">.</span><span class="n">running</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">next_arrival_step</span><span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span> <span class="o">|</span> <span class="bp">None</span><span class="p">:</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">self</span><span class="p">.</span><span class="n">waiting</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">None</span>
        <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">waiting</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">request</span><span class="p">.</span><span class="n">arrival_step</span>

    <span class="k">def</span> <span class="nf">admit</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">step</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">]:</span>
        <span class="n">admitted</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="k">while</span> <span class="n">self</span><span class="p">.</span><span class="n">waiting</span> <span class="ow">and</span> <span class="n">self</span><span class="p">.</span><span class="n">waiting</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">request</span><span class="p">.</span><span class="n">arrival_step</span> <span class="o">&lt;=</span> <span class="n">step</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">max_batch_size</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span> <span class="ow">and</span> <span class="nf">len</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">running</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="n">self</span><span class="p">.</span><span class="n">max_batch_size</span><span class="p">:</span>
                <span class="k">break</span>
            <span class="n">state</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">waiting</span><span class="p">.</span><span class="nf">popleft</span><span class="p">()</span>
            <span class="n">state</span><span class="p">.</span><span class="n">lifecycle</span> <span class="o">=</span> <span class="n">RequestLifecycle</span><span class="p">.</span><span class="n">RUNNING</span>
            <span class="n">state</span><span class="p">.</span><span class="n">admitted_step</span> <span class="o">=</span> <span class="n">step</span>
            <span class="n">self</span><span class="p">.</span><span class="n">running</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">state</span><span class="p">)</span>
            <span class="n">admitted</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">state</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">admitted</span>

    <span class="k">def</span> <span class="nf">finish</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">request_ids</span><span class="p">:</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span> <span class="n">step</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">]:</span>
        <span class="n">completed</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="n">still_running</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="k">for</span> <span class="n">state</span> <span class="ow">in</span> <span class="n">self</span><span class="p">.</span><span class="n">running</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">state</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="n">request_id</span> <span class="ow">in</span> <span class="n">request_ids</span><span class="p">:</span>
                <span class="n">state</span><span class="p">.</span><span class="n">lifecycle</span> <span class="o">=</span> <span class="n">RequestLifecycle</span><span class="p">.</span><span class="n">FINISHED</span>
                <span class="n">state</span><span class="p">.</span><span class="n">finished_step</span> <span class="o">=</span> <span class="n">step</span>
                <span class="n">completed</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">state</span><span class="p">)</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">still_running</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">state</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">running</span> <span class="o">=</span> <span class="n">still_running</span>
        <span class="n">self</span><span class="p">.</span><span class="n">finished</span><span class="p">.</span><span class="nf">extend</span><span class="p">(</span><span class="n">completed</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">completed</span>

    <span class="k">def</span> <span class="nf">record_step</span><span class="p">(</span>
        <span class="n">self</span><span class="p">,</span>
        <span class="o">*</span><span class="p">,</span>
        <span class="n">step</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span>
        <span class="n">admitted</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">],</span>
        <span class="n">running_request_ids</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
        <span class="n">completed</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">],</span>
    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">SchedulerStepStats</span><span class="p">:</span>
        <span class="k">return</span> <span class="nc">SchedulerStepStats</span><span class="p">(</span>
            <span class="n">step</span><span class="o">=</span><span class="n">step</span><span class="p">,</span>
            <span class="n">admitted_request_ids</span><span class="o">=</span><span class="p">[</span><span class="n">state</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="n">request_id</span> <span class="k">for</span> <span class="n">state</span> <span class="ow">in</span> <span class="n">admitted</span><span class="p">],</span>
            <span class="n">running_request_ids</span><span class="o">=</span><span class="n">running_request_ids</span><span class="p">,</span>
            <span class="n">completed_request_ids</span><span class="o">=</span><span class="p">[</span><span class="n">state</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="n">request_id</span> <span class="k">for</span> <span class="n">state</span> <span class="ow">in</span> <span class="n">completed</span><span class="p">],</span>
            <span class="n">active_batch_size</span><span class="o">=</span><span class="nf">len</span><span class="p">(</span><span class="n">running_request_ids</span><span class="p">),</span>
        <span class="p">)</span>

</code></pre></div></div> <p>这次引入了Scheduler用来处理和调度请求，首先是定义请求的生命周期：</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">RequestLifecycle</span><span class="p">(</span><span class="nb">str</span><span class="p">,</span> <span class="n">Enum</span><span class="p">):</span>
    <span class="n">WAITING</span> <span class="o">=</span> <span class="sh">"</span><span class="s">waiting</span><span class="sh">"</span>
    <span class="n">RUNNING</span> <span class="o">=</span> <span class="sh">"</span><span class="s">running</span><span class="sh">"</span>
    <span class="n">FINISHED</span> <span class="o">=</span> <span class="sh">"</span><span class="s">finished</span><span class="sh">"</span>
</code></pre></div></div> <p>请求刚来的时候在waiting中，到了某个scheduler step的时候，被admit进入active batch的时候，就会变成running，在生成结束后，从running set里被移出后，会变成finished</p> <p>现在进来的请求长这样：</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@dataclass</span><span class="p">(</span><span class="n">frozen</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">ContinuousBatchRequest</span><span class="p">:</span>
    <span class="n">request_id</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">prompt</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">max_new_tokens</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">32</span>
    <span class="n">arrival_step</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>
</code></pre></div></div> <p>类似这样调用</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">ContinuousBatchRequest</span><span class="p">(</span><span class="sh">"</span><span class="s">req-0</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">hello</span><span class="sh">"</span><span class="p">,</span> <span class="n">arrival_step</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="nc">ContinuousBatchRequest</span><span class="p">(</span><span class="sh">"</span><span class="s">req-1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">你好</span><span class="sh">"</span><span class="p">,</span> <span class="n">arrival_step</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
</code></pre></div></div> <p>这个代表了2个请求，req-0在step0的时候到达，req-1在step2的时候到达</p> <p>这里面定义了2个列表和1个队列：</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@dataclass</span>
<span class="k">class</span> <span class="nc">ContinuousBatchScheduler</span><span class="p">:</span>
    <span class="n">requests</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ContinuousBatchRequest</span><span class="p">]</span>
    <span class="n">max_batch_size</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span>
    <span class="n">waiting</span><span class="p">:</span> <span class="n">deque</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">]</span> <span class="o">=</span> <span class="nf">field</span><span class="p">(</span><span class="n">init</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
    <span class="n">running</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">]</span> <span class="o">=</span> <span class="nf">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">,</span> <span class="n">init</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
    <span class="n">finished</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">]</span> <span class="o">=</span> <span class="nf">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">,</span> <span class="n">init</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
</code></pre></div></div> <p>分别代表了前面提到的3个不同生命周期阶段对应的请求，另外配套了两个重要的方法</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">admit</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">step</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">]:</span>
    <span class="n">admitted</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">while</span> <span class="n">self</span><span class="p">.</span><span class="n">waiting</span> <span class="ow">and</span> <span class="n">self</span><span class="p">.</span><span class="n">waiting</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">request</span><span class="p">.</span><span class="n">arrival_step</span> <span class="o">&lt;=</span> <span class="n">step</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">self</span><span class="p">.</span><span class="n">max_batch_size</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span> <span class="ow">and</span> <span class="nf">len</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">running</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="n">self</span><span class="p">.</span><span class="n">max_batch_size</span><span class="p">:</span>
            <span class="k">break</span>
        <span class="n">state</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">waiting</span><span class="p">.</span><span class="nf">popleft</span><span class="p">()</span>
        <span class="n">state</span><span class="p">.</span><span class="n">lifecycle</span> <span class="o">=</span> <span class="n">RequestLifecycle</span><span class="p">.</span><span class="n">RUNNING</span>
        <span class="n">state</span><span class="p">.</span><span class="n">admitted_step</span> <span class="o">=</span> <span class="n">step</span>
        <span class="n">self</span><span class="p">.</span><span class="n">running</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">state</span><span class="p">)</span>
        <span class="n">admitted</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">state</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">admitted</span>

<span class="k">def</span> <span class="nf">finish</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">request_ids</span><span class="p">:</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span> <span class="n">step</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">]:</span>
    <span class="n">completed</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="n">still_running</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ScheduledRequestState</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span> <span class="n">state</span> <span class="ow">in</span> <span class="n">self</span><span class="p">.</span><span class="n">running</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">state</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="n">request_id</span> <span class="ow">in</span> <span class="n">request_ids</span><span class="p">:</span>
            <span class="n">state</span><span class="p">.</span><span class="n">lifecycle</span> <span class="o">=</span> <span class="n">RequestLifecycle</span><span class="p">.</span><span class="n">FINISHED</span>
            <span class="n">state</span><span class="p">.</span><span class="n">finished_step</span> <span class="o">=</span> <span class="n">step</span>
            <span class="n">completed</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">state</span><span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">still_running</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">state</span><span class="p">)</span>
    <span class="n">self</span><span class="p">.</span><span class="n">running</span> <span class="o">=</span> <span class="n">still_running</span>
    <span class="n">self</span><span class="p">.</span><span class="n">finished</span><span class="p">.</span><span class="nf">extend</span><span class="p">(</span><span class="n">completed</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">completed</span>
</code></pre></div></div> <p>admin会把已经到达在等待的请求从waiting移到running，而finish会把已经完成的请求从running移到finished</p> <h2 id="engine">Engine</h2> <p>真正处理Continuous Batching是在<code class="language-plaintext highlighter-rouge">src/nanollmserve/engine/engine.py</code> 里的<code class="language-plaintext highlighter-rouge">generate_continuous_batch</code></p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span> <span class="n">torch</span><span class="p">.</span><span class="nf">inference_mode</span><span class="p">():</span>
    <span class="k">while</span> <span class="n">scheduler</span><span class="p">.</span><span class="nf">has_work</span><span class="p">():</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">scheduler</span><span class="p">.</span><span class="n">running</span> <span class="ow">and</span> <span class="n">scheduler</span><span class="p">.</span><span class="nf">next_arrival_step</span><span class="p">()</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
            <span class="n">step</span> <span class="o">=</span> <span class="nf">max</span><span class="p">(</span><span class="n">step</span><span class="p">,</span> <span class="n">scheduler</span><span class="p">.</span><span class="nf">next_arrival_step</span><span class="p">())</span>

        <span class="n">admitted</span> <span class="o">=</span> <span class="n">scheduler</span><span class="p">.</span><span class="nf">admit</span><span class="p">(</span><span class="n">step</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">scheduled</span> <span class="ow">in</span> <span class="n">admitted</span><span class="p">:</span>
            <span class="n">states</span><span class="p">[</span><span class="n">scheduled</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="n">request_id</span><span class="p">]</span> <span class="o">=</span> <span class="nf">_state_from_prompt</span><span class="p">(</span>
                <span class="n">tokenizer</span><span class="p">,</span>
                <span class="n">scheduled</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="n">prompt</span><span class="p">,</span>
                <span class="n">device</span><span class="p">,</span>
            <span class="p">)</span>
            <span class="n">admitted_at</span><span class="p">[</span><span class="n">scheduled</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="n">request_id</span><span class="p">]</span> <span class="o">=</span> <span class="nf">perf_counter</span><span class="p">()</span>

        <span class="n">running_ids</span> <span class="o">=</span> <span class="p">[</span><span class="n">state</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="n">request_id</span> <span class="k">for</span> <span class="n">state</span> <span class="ow">in</span> <span class="n">scheduler</span><span class="p">.</span><span class="n">running</span><span class="p">]</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">running_ids</span><span class="p">:</span>
            <span class="k">continue</span>

        <span class="n">batch</span> <span class="o">=</span> <span class="nf">_continuous_batch_tensors</span><span class="p">(</span><span class="n">states</span><span class="p">,</span> <span class="n">running_ids</span><span class="p">,</span> <span class="n">tokenizer</span><span class="p">,</span> <span class="n">device</span><span class="p">)</span>
        <span class="n">batch_start</span> <span class="o">=</span> <span class="nf">perf_counter</span><span class="p">()</span>
        <span class="n">outputs</span> <span class="o">=</span> <span class="nf">model</span><span class="p">(</span>
            <span class="n">input_ids</span><span class="o">=</span><span class="n">batch</span><span class="p">[</span><span class="sh">"</span><span class="s">input_ids</span><span class="sh">"</span><span class="p">],</span>
            <span class="n">attention_mask</span><span class="o">=</span><span class="n">batch</span><span class="p">[</span><span class="sh">"</span><span class="s">attention_mask</span><span class="sh">"</span><span class="p">],</span>
            <span class="n">use_cache</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span>
        <span class="p">)</span>
        <span class="n">batch_elapsed</span> <span class="o">=</span> <span class="nf">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">batch_start</span>
        <span class="n">next_logits</span> <span class="o">=</span> <span class="nf">_select_last_token_logits</span><span class="p">(</span><span class="n">outputs</span><span class="p">.</span><span class="n">logits</span><span class="p">,</span> <span class="n">batch</span><span class="p">[</span><span class="sh">"</span><span class="s">attention_mask</span><span class="sh">"</span><span class="p">])</span>
        <span class="n">next_tokens</span> <span class="o">=</span> <span class="nf">_sample_from_logits</span><span class="p">(</span>
            <span class="n">next_logits</span><span class="p">,</span>
            <span class="n">temperature</span><span class="o">=</span><span class="n">temperature</span><span class="p">,</span>
            <span class="n">generator</span><span class="o">=</span><span class="n">generator</span><span class="p">,</span>
        <span class="p">)</span>

        <span class="n">completed_ids</span><span class="p">:</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="nf">set</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">index</span><span class="p">,</span> <span class="n">request_id</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">running_ids</span><span class="p">):</span>
            <span class="n">state</span> <span class="o">=</span> <span class="n">states</span><span class="p">[</span><span class="n">request_id</span><span class="p">]</span>
            <span class="n">request</span> <span class="o">=</span> <span class="n">request_by_id</span><span class="p">[</span><span class="n">request_id</span><span class="p">]</span>
            <span class="n">token_id</span> <span class="o">=</span> <span class="nf">int</span><span class="p">(</span><span class="n">next_tokens</span><span class="p">[</span><span class="n">index</span><span class="p">,</span> <span class="mi">0</span><span class="p">].</span><span class="nf">item</span><span class="p">())</span>
            <span class="n">state</span><span class="p">.</span><span class="n">generated_token_ids</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">token_id</span><span class="p">)</span>
            <span class="n">state</span><span class="p">.</span><span class="n">attention_mask</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">cat</span><span class="p">(</span>
                <span class="p">[</span>
                    <span class="n">state</span><span class="p">.</span><span class="n">attention_mask</span><span class="p">,</span>
                    <span class="n">torch</span><span class="p">.</span><span class="nf">ones</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="n">state</span><span class="p">.</span><span class="n">attention_mask</span><span class="p">.</span><span class="n">dtype</span><span class="p">,</span> <span class="n">device</span><span class="o">=</span><span class="n">state</span><span class="p">.</span><span class="n">attention_mask</span><span class="p">.</span><span class="n">device</span><span class="p">),</span>
                <span class="p">],</span>
                <span class="n">dim</span><span class="o">=-</span><span class="mi">1</span><span class="p">,</span>
            <span class="p">)</span>
            <span class="k">if</span> <span class="n">state</span><span class="p">.</span><span class="n">generated_tokens</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
                <span class="n">state</span><span class="p">.</span><span class="n">ttft_seconds</span> <span class="o">=</span> <span class="nf">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">admitted_at</span><span class="p">[</span><span class="n">request_id</span><span class="p">]</span>
                <span class="n">state</span><span class="p">.</span><span class="n">prefill_seconds</span> <span class="o">+=</span> <span class="n">batch_elapsed</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">state</span><span class="p">.</span><span class="n">decode_seconds</span> <span class="o">+=</span> <span class="n">batch_elapsed</span>
            <span class="k">if</span> <span class="n">token_id</span> <span class="ow">in</span> <span class="n">eos_token_ids</span> <span class="ow">or</span> <span class="n">state</span><span class="p">.</span><span class="n">generated_tokens</span> <span class="o">&gt;=</span> <span class="n">request</span><span class="p">.</span><span class="n">max_new_tokens</span><span class="p">:</span>
                <span class="n">state</span><span class="p">.</span><span class="n">finished</span> <span class="o">=</span> <span class="n">token_id</span> <span class="ow">in</span> <span class="n">eos_token_ids</span>
                <span class="n">completed_ids</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">request_id</span><span class="p">)</span>
                <span class="n">finished_at</span><span class="p">[</span><span class="n">request_id</span><span class="p">]</span> <span class="o">=</span> <span class="nf">perf_counter</span><span class="p">()</span>

        <span class="n">completed</span> <span class="o">=</span> <span class="n">scheduler</span><span class="p">.</span><span class="nf">finish</span><span class="p">(</span><span class="n">completed_ids</span><span class="p">,</span> <span class="n">step</span><span class="p">)</span>
        <span class="n">scheduler_steps</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span>
            <span class="n">scheduler</span><span class="p">.</span><span class="nf">record_step</span><span class="p">(</span>
                <span class="n">step</span><span class="o">=</span><span class="n">step</span><span class="p">,</span>
                <span class="n">admitted</span><span class="o">=</span><span class="n">admitted</span><span class="p">,</span>
                <span class="n">running_request_ids</span><span class="o">=</span><span class="n">running_ids</span><span class="p">,</span>
                <span class="n">completed</span><span class="o">=</span><span class="n">completed</span><span class="p">,</span>
            <span class="p">)</span>
        <span class="p">)</span>
        <span class="n">step</span> <span class="o">+=</span> <span class="mi">1</span>
</code></pre></div></div> <p>先让scheduler（基于当前step）把可以执行的请求加到running里：</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">admitted</span> <span class="o">=</span> <span class="n">scheduler</span><span class="p">.</span><span class="nf">admit</span><span class="p">(</span><span class="n">step</span><span class="p">)</span>
<span class="k">for</span> <span class="n">scheduled</span> <span class="ow">in</span> <span class="n">admitted</span><span class="p">:</span>
    <span class="n">states</span><span class="p">[</span><span class="n">scheduled</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="n">request_id</span><span class="p">]</span> <span class="o">=</span> <span class="nf">_state_from_prompt</span><span class="p">(</span>
        <span class="n">tokenizer</span><span class="p">,</span>
        <span class="n">scheduled</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="n">prompt</span><span class="p">,</span>
        <span class="n">device</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="n">admitted_at</span><span class="p">[</span><span class="n">scheduled</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="n">request_id</span><span class="p">]</span> <span class="o">=</span> <span class="nf">perf_counter</span><span class="p">()</span>
</code></pre></div></div> <p>然后拿到所有的running_ids，并根据running_ids去重建active batch</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">running_ids</span> <span class="o">=</span> <span class="p">[</span><span class="n">state</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="n">request_id</span> <span class="k">for</span> <span class="n">state</span> <span class="ow">in</span> <span class="n">scheduler</span><span class="p">.</span><span class="n">running</span><span class="p">]</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">running_ids</span><span class="p">:</span>
    <span class="k">continue</span>

<span class="n">batch</span> <span class="o">=</span> <span class="nf">_continuous_batch_tensors</span><span class="p">(</span><span class="n">states</span><span class="p">,</span> <span class="n">running_ids</span><span class="p">,</span> <span class="n">tokenizer</span><span class="p">,</span> <span class="n">device</span><span class="p">)</span>
</code></pre></div></div> <p>其中_continuous_batch_tensors是把running的多个请求拼成一个padding后的batch，之前我们也有说过，batch的请求是需要对齐的</p> <p>然后后面的处理和之前的就都一样了，得到logits，采样出下个token</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">outputs</span> <span class="o">=</span> <span class="nf">model</span><span class="p">(</span>
    <span class="n">input_ids</span><span class="o">=</span><span class="n">batch</span><span class="p">[</span><span class="sh">"</span><span class="s">input_ids</span><span class="sh">"</span><span class="p">],</span>
    <span class="n">attention_mask</span><span class="o">=</span><span class="n">batch</span><span class="p">[</span><span class="sh">"</span><span class="s">attention_mask</span><span class="sh">"</span><span class="p">],</span>
    <span class="n">use_cache</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">batch_elapsed</span> <span class="o">=</span> <span class="nf">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">batch_start</span>
<span class="n">next_logits</span> <span class="o">=</span> <span class="nf">_select_last_token_logits</span><span class="p">(</span><span class="n">outputs</span><span class="p">.</span><span class="n">logits</span><span class="p">,</span> <span class="n">batch</span><span class="p">[</span><span class="sh">"</span><span class="s">attention_mask</span><span class="sh">"</span><span class="p">])</span>
<span class="n">next_tokens</span> <span class="o">=</span> <span class="nf">_sample_from_logits</span><span class="p">(</span>
    <span class="n">next_logits</span><span class="p">,</span>
    <span class="n">temperature</span><span class="o">=</span><span class="n">temperature</span><span class="p">,</span>
    <span class="n">generator</span><span class="o">=</span><span class="n">generator</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div></div> <p>接着过一遍这次forward的请求，把生成的token追加回每个请求的state里，包括attention mask增加一位。最后看看有哪些请求已经结束了，可以反馈给Scheduler</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">completed_ids</span><span class="p">:</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="nf">set</span><span class="p">()</span>
<span class="k">for</span> <span class="n">index</span><span class="p">,</span> <span class="n">request_id</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">running_ids</span><span class="p">):</span>
    <span class="n">state</span> <span class="o">=</span> <span class="n">states</span><span class="p">[</span><span class="n">request_id</span><span class="p">]</span>
    <span class="n">request</span> <span class="o">=</span> <span class="n">request_by_id</span><span class="p">[</span><span class="n">request_id</span><span class="p">]</span>
    <span class="n">token_id</span> <span class="o">=</span> <span class="nf">int</span><span class="p">(</span><span class="n">next_tokens</span><span class="p">[</span><span class="n">index</span><span class="p">,</span> <span class="mi">0</span><span class="p">].</span><span class="nf">item</span><span class="p">())</span>
    <span class="n">state</span><span class="p">.</span><span class="n">generated_token_ids</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">token_id</span><span class="p">)</span>
    <span class="n">state</span><span class="p">.</span><span class="n">attention_mask</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">cat</span><span class="p">(</span>
        <span class="p">[</span>
            <span class="n">state</span><span class="p">.</span><span class="n">attention_mask</span><span class="p">,</span>
            <span class="n">torch</span><span class="p">.</span><span class="nf">ones</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="n">state</span><span class="p">.</span><span class="n">attention_mask</span><span class="p">.</span><span class="n">dtype</span><span class="p">,</span> <span class="n">device</span><span class="o">=</span><span class="n">state</span><span class="p">.</span><span class="n">attention_mask</span><span class="p">.</span><span class="n">device</span><span class="p">),</span>
        <span class="p">],</span>
        <span class="n">dim</span><span class="o">=-</span><span class="mi">1</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="k">if</span> <span class="n">state</span><span class="p">.</span><span class="n">generated_tokens</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
        <span class="n">state</span><span class="p">.</span><span class="n">ttft_seconds</span> <span class="o">=</span> <span class="nf">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">admitted_at</span><span class="p">[</span><span class="n">request_id</span><span class="p">]</span>
        <span class="n">state</span><span class="p">.</span><span class="n">prefill_seconds</span> <span class="o">+=</span> <span class="n">batch_elapsed</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">state</span><span class="p">.</span><span class="n">decode_seconds</span> <span class="o">+=</span> <span class="n">batch_elapsed</span>
    <span class="k">if</span> <span class="n">token_id</span> <span class="ow">in</span> <span class="n">eos_token_ids</span> <span class="ow">or</span> <span class="n">state</span><span class="p">.</span><span class="n">generated_tokens</span> <span class="o">&gt;=</span> <span class="n">request</span><span class="p">.</span><span class="n">max_new_tokens</span><span class="p">:</span>
        <span class="n">state</span><span class="p">.</span><span class="n">finished</span> <span class="o">=</span> <span class="n">token_id</span> <span class="ow">in</span> <span class="n">eos_token_ids</span>
        <span class="n">completed_ids</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">request_id</span><span class="p">)</span>
        <span class="n">finished_at</span><span class="p">[</span><span class="n">request_id</span><span class="p">]</span> <span class="o">=</span> <span class="nf">perf_counter</span><span class="p">()</span>

<span class="n">completed</span> <span class="o">=</span> <span class="n">scheduler</span><span class="p">.</span><span class="nf">finish</span><span class="p">(</span><span class="n">completed_ids</span><span class="p">,</span> <span class="n">step</span><span class="p">)</span>
</code></pre></div></div> <p>这就是完整的Continuous Batching。但是这里面还有一些问题，粒度太粗，性能不是最优的</p> <p>现在每次<code class="language-plaintext highlighter-rouge">_continuous_batch_tensors</code> 的时候，都是</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sequence</span> <span class="o">=</span> <span class="n">state</span><span class="p">.</span><span class="n">prompt_token_ids</span> <span class="o">+</span> <span class="n">state</span><span class="p">.</span><span class="n">generated_token_ids</span>
</code></pre></div></div> <p>把整个序列都重新forward了，也就是KV Cache又掉了，也就是我们v0.2的KV Cache并没有实际运用进来。这个是后续我们会做的一个paged-KV continuous batching</p> <h1 id="推理">推理</h1> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-25-llm-infra-101-v0-4-continuous-batching/1779683395_2-480.webp 480w,/assets/img/2026-05-25-llm-infra-101-v0-4-continuous-batching/1779683395_2-800.webp 800w,/assets/img/2026-05-25-llm-infra-101-v0-4-continuous-batching/1779683395_2-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-25-llm-infra-101-v0-4-continuous-batching/1779683395_2.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>我们这波也还只能在bench里观测，可以看到<code class="language-plaintext highlighter-rouge">continuous_batch</code> 里的数据指标</p> <h1 id="总结">总结</h1> <p>这波支持了Continuous Batching了，实现谁结束了谁滚蛋，谁来了谁补位的目标，不会在batch里有请求已经结束的情况下还占用显存的slot</p> <p>但是我们前面也提到，现在的更多是完整展示Continuous Batching这个概念本身，实际并不是vllm/sglang之类在生产环境上能跑的版本，我们还需要做一些工作来支持，下一步我们要做的就是借助Paged KV Cache（Block）来支持Paged-KV Continuous Batching</p>]]></content><author><name></name></author><category term="AI"/><category term="AI"/><category term="LLMInfra-101"/><summary type="html"><![CDATA[系列的第五集，前面的可以看：]]></summary></entry><entry xml:lang="zh"><title type="html">LLM Infra 101 v0.3: 静态批处理</title><link href="https://ifuryst.github.io/blog/2026/llm-infra-101-v0-3-static-batching/" rel="alternate" type="text/html" title="LLM Infra 101 v0.3: 静态批处理"/><published>2026-05-21T00:00:00+00:00</published><updated>2026-05-21T00:00:00+00:00</updated><id>https://ifuryst.github.io/blog/2026/llm-infra-101-v0-3-static-batching</id><content type="html" xml:base="https://ifuryst.github.io/blog/2026/llm-infra-101-v0-3-static-batching/"><![CDATA[<p>系列的第二集，前面的可以看：</p> <ol> <li> <p><a href="https://www.ifuryst.com/blog/2026/llm-infra-101-model-inference/">LLM Infra 101 v0.0: 推理模型</a></p> </li> <li> <p><a href="https://www.ifuryst.com/blog/2026/llm-infra-101-v0-1-openai-compatible-api/">LLM Infra 101 v0.1: API调用</a></p> </li> <li> <p><a href="https://www.ifuryst.com/blog/2026/llm-infra-101-v0-2-kv-cache-decode/">LLM Infra 101 v0.2: KV Cache</a></p> </li> </ol> <p>这一期的代码在 <a href="https://github.com/iFurySt/nanoLLMServe/tree/release/v0.3.1">https://github.com/iFurySt/nanoLLMServe/tree/release/v0.3.1</a></p> <p>上期过完我们对于KV Cache已经有了认知和实现了，现在我们要继续看一个问题，我们现在每次收到请求是把这个请求单独处理，下个请求也是单独处理，但是实际生产中这样会带来一些问题诸如吞吐低、时延高、资源利用率低这些问题。</p> <p>因为每次请求一个请求，可以理解为是串行的处理，GPU算力空闲率高，并且每个请求独立做prefill。因此自然而然我们就会做一些批量（Batching）的优化动作，这样可以并行处理多个请求，提高整体的GPU利用率，也能批量化内存和算子调度。</p> <p>Batching这块核心就是两种：</p> <ul> <li> <p>静态批处理（Static Batching）：相对传统的Batching，但是对于我们理解机制原理很有帮助</p> </li> <li> <p>连续批处理（Continuous Batching）：现在主流的infra采用的技术</p> </li> </ul> <p>我们这次主要针对Static Batching，下一集会推进到Continuous Batching。</p> <p>Static Batching的原理是，一次固定处理一批请求，整批一起forward，大概如下：</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>req1
req2
req3
↓
组成一个固定 batch
↓
一起 forward
↓
等整个 batch 全部结束
↓
再处理下一批
</code></pre></div></div> <p>对比一下，原来是这样的</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">时间轴</span> <span class="err">→</span>

<span class="n">Req</span> <span class="n">A</span> <span class="err">─────</span> <span class="n">GPU</span> <span class="n">forward</span> <span class="err">─────</span>
<span class="n">Req</span> <span class="n">B</span>                     <span class="err">─────</span> <span class="n">GPU</span> <span class="n">forward</span> <span class="err">─────</span>
<span class="n">Req</span> <span class="n">C</span>                                         <span class="err">─────</span> <span class="n">GPU</span> <span class="n">forward</span> <span class="err">─────</span>
</code></pre></div></div> <p>现在是</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>              <span class="err">┌───────────────┐</span>
<span class="n">Req</span> <span class="n">A</span> <span class="err">───────▶│</span>               <span class="err">│</span>
<span class="n">Req</span> <span class="n">B</span> <span class="err">───────▶│</span>   <span class="n">Batch</span><span class="o">=</span><span class="mi">3</span>     <span class="err">│───▶</span> <span class="n">GPU</span> <span class="n">Forward</span>
<span class="n">Req</span> <span class="n">C</span> <span class="err">───────▶│</span>               <span class="err">│</span>
              <span class="err">└───────────────┘</span>
</code></pre></div></div> <h1 id="实现">实现</h1> <p>改动涉及的文件：</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">.</span>
├── src/
│   └── nanollmserve/
│       ├── api/
│       │   ├── openai_server.py    <span class="c"># Responses 子集对齐：服务层接口行为和路由处理扩展</span>
│       │   └── protocol.py         <span class="c"># 协议模型更新：响应子集相关字段、请求/响应结构收敛</span>
│       └── engine/
│           └── engine.py           <span class="c"># 在现有 generate_one 的基础上引入静态批处理路径与调度</span>
└── tests/
    ├── test_benchmark_generate.py   <span class="c"># benchmark 汇总项与静态/响应场景回归覆盖</span>
    ├── test_engine.py               <span class="c"># engine 行为回归（含批处理/状态路径）</span>
    └── test_openai_server.py        <span class="c"># OpenAI 兼容层改造后的接口回归（含 Responses 子集）</span>
</code></pre></div></div> <h2 id="batching">Batching</h2> <p>原来只有<code class="language-plaintext highlighter-rouge">generate_one(model, tokenizer, prompt, ...)</code> ，这次新增了<code class="language-plaintext highlighter-rouge">generate_batch(model, tokenizer, prompts, ...)</code> ，现在调用变成了一组prompt</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">results</span> <span class="o">=</span> <span class="nf">generate_batch</span><span class="p">(</span>
    <span class="n">model</span><span class="p">,</span>
    <span class="n">tokenizer</span><span class="p">,</span>
    <span class="p">[</span><span class="sh">"</span><span class="s">hello</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">world</span><span class="sh">"</span><span class="p">],</span>
    <span class="n">max_new_tokens</span><span class="o">=</span><span class="mi">32</span><span class="p">,</span>
    <span class="n">temperature</span><span class="o">=</span><span class="mf">0.0</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div></div> <p>但是我们没有把generate_batch对接到API和CLI里，因为马上我们就要做Continuous Batching了，这边就做一个过渡</p> <p>因为这边prompt进来是一组的，prompt长度可能都不一样：</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>A input_ids: [101, 102]
B input_ids: [201, 202, 203, 204]
</code></pre></div></div> <p>因此tokenizer需要打开Padding</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">encoded</span> <span class="o">=</span> <span class="nf">tokenizer</span><span class="p">(</span>
    <span class="n">prompts</span><span class="p">,</span>
    <span class="n">return_tensors</span><span class="o">=</span><span class="sh">"</span><span class="s">pt</span><span class="sh">"</span><span class="p">,</span>
    <span class="n">padding</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div></div> <p>padding后会变为类似这样的：</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>A input_ids: [101, 102, 0, 0]
A attention_mask: [1, 1, 0, 0]

B input_ids: [201, 202, 203, 204]
B attention_mask: [1, 1, 1, 1]
</code></pre></div></div> <p>这个其实我们第一章的时候已经说过一次了，当时我们没有batching机制，所以当时默认bacth 1，现在就会有多个batch了</p> <h2 id="batch-prefill">Batch Prefill</h2> <p>之前我们做了prefill，不过当时针对的是单个请求：</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>prompt -&gt; model -&gt; past_key_values
</code></pre></div></div> <p>现在变成了batch prefill</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[prompt A, prompt B, prompt C] -&gt; model -&gt; batch past_key_values
</code></pre></div></div> <p>第一次forward变成了batch，这个其实之前也都有，所以机制上是已经有了，只不过有个小地方需要调整，原来logits是从-1取的，但是现在-1可能是padding，所以需要调整一下</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">_select_last_token_logits</span><span class="p">(</span><span class="n">logits</span><span class="p">,</span> <span class="n">attention_mask</span><span class="p">):</span>
    <span class="n">indices</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">clamp</span><span class="p">(</span><span class="n">attention_mask</span><span class="p">.</span><span class="nf">sum</span><span class="p">(</span><span class="n">dim</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="nb">min</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
    <span class="n">batch</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">arange</span><span class="p">(</span><span class="n">logits</span><span class="p">.</span><span class="nf">size</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="n">device</span><span class="o">=</span><span class="n">logits</span><span class="p">.</span><span class="n">device</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">logits</span><span class="p">[</span><span class="n">batch</span><span class="p">,</span> <span class="n">indices</span><span class="p">,</span> <span class="p">:]</span>
</code></pre></div></div> <p>现在是根据attention_mask来找最后一个真实token的位置，因为掩码里有对应的信息</p> <h2 id="batch-decode">batch decode</h2> <p>decode也是一样，原来已经有batch的机制了：</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">input_ids</span> <span class="o">=</span> <span class="p">[[</span><span class="n">last_token</span><span class="p">]]</span>
</code></pre></div></div> <p>现在是</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">input_ids</span> <span class="o">=</span> <span class="p">[</span>
  <span class="p">[</span><span class="n">last_token_for_A</span><span class="p">],</span>
  <span class="p">[</span><span class="n">last_token_for_B</span><span class="p">],</span>
  <span class="p">[</span><span class="n">last_token_for_C</span><span class="p">],</span>
<span class="p">]</span>
</code></pre></div></div> <p>现在会在某个step里分别去生成batch里的请求的下一个token</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">step</span> <span class="mi">1</span><span class="p">:</span> <span class="n">A生成一个token</span><span class="err">，</span><span class="n">B生成一个</span> <span class="n">token</span><span class="err">，</span><span class="n">C生成一个token</span>
<span class="n">step</span> <span class="mi">2</span><span class="p">:</span> <span class="n">A生成一个token</span><span class="err">，</span><span class="n">B生成一个</span> <span class="n">token</span><span class="err">，</span><span class="n">C生成一个token</span>
<span class="n">step</span> <span class="mi">3</span><span class="p">:</span> <span class="bp">...</span>
</code></pre></div></div> <p>但是因为实际序列都不一样长，有一些请求会更早结束</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Req</span> <span class="n">A</span> <span class="err">→</span> <span class="mi">10</span> <span class="n">tokens</span>
<span class="n">Req</span> <span class="n">B</span> <span class="err">→</span> <span class="mi">500</span> <span class="n">tokens</span>
<span class="n">Req</span> <span class="n">C</span> <span class="err">→</span> <span class="mi">50</span> <span class="n">tokens</span>

<span class="n">Req</span> <span class="n">A</span><span class="p">:</span> <span class="n">finished</span>
<span class="n">Req</span> <span class="n">B</span><span class="p">:</span> <span class="n">running</span>
<span class="n">Req</span> <span class="n">C</span><span class="p">:</span> <span class="n">running</span>
</code></pre></div></div> <p>在Static Batching里，先遇到EOS结束的请求会标记成finished，后续不会再往它的generated_token_ids里追加token了。但是这个batch里已经有请求结束了，GPU就会出现空洞的情况：</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">batch</span> <span class="o">=</span> <span class="p">[</span><span class="n">EMPTY</span><span class="p">,</span> <span class="n">B</span><span class="p">,</span> <span class="n">C</span><span class="p">]</span>
<span class="p">[</span> <span class="n">_</span><span class="p">,</span> <span class="n">B</span><span class="p">,</span> <span class="n">C</span> <span class="p">]</span>

<span class="n">batch</span> <span class="o">=</span> <span class="p">[</span><span class="n">EMPTY</span><span class="p">,</span> <span class="n">B</span><span class="p">,</span> <span class="n">EMPTY</span><span class="p">]</span>
<span class="p">[</span> <span class="n">_</span><span class="p">,</span> <span class="n">B</span><span class="p">,</span> <span class="n">_</span> <span class="p">]</span>
</code></pre></div></div> <p>这样GPU计算的利用率到后面是越来越少的，也就是退化回单条请求。但是同时显存的slot并不会释放，造成了显存的浪费</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># step1: GPU0 KV Memory
</span><span class="err">┌────┬────┬────┐</span>
<span class="err">│</span> <span class="n">A</span>  <span class="err">│</span> <span class="n">B</span>  <span class="err">│</span> <span class="n">C</span>  <span class="err">│</span>
<span class="err">└────┴────┴────┘</span>

<span class="c1"># step2: GPU0 KV Memory
</span><span class="err">┌────┬────┬────┐</span>
<span class="err">│</span><span class="n">idle</span><span class="err">│</span> <span class="n">B</span>  <span class="err">│</span> <span class="n">C</span>  <span class="err">│</span>
<span class="err">└────┴────┴────┘</span>

<span class="c1"># step3: GPU0 KV Memory
</span><span class="err">┌────┬────┬────┐</span>
<span class="err">│</span><span class="n">idle</span><span class="err">│</span> <span class="n">B</span>  <span class="err">│</span><span class="n">idle</span><span class="err">│</span>
<span class="err">└────┴────┴────┘</span>
</code></pre></div></div> <p>这个其实也是我们下一章Continuous Batching要解决的！（大体解决思路是谁结束了谁滚蛋，谁来了谁补位）这边我们先不展开</p> <h1 id="推理">推理</h1> <p>这次我们基本也是在bench里观测一下</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">(base)</span><span class="w"> </span><span class="err">gpu-A</span><span class="mi">100-05</span><span class="w"> </span><span class="err">nanoLLMServe</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="err">for</span><span class="w"> </span><span class="err">BS</span><span class="w"> </span><span class="err">in</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mi">8</span><span class="err">;</span><span class="w"> </span><span class="err">do</span><span class="w">
  </span><span class="err">CUDA_VISIBLE_DEVICES=</span><span class="mi">0</span><span class="w"> </span><span class="err">PYTHONPATH=src</span><span class="w"> </span><span class="err">/data/anaconda</span><span class="mi">3</span><span class="err">/bin/python</span><span class="w"> </span><span class="err">-m</span><span class="w"> </span><span class="err">benchmarks.benchmark_generate</span><span class="w"> </span><span class="err">\</span><span class="w">
    </span><span class="err">--model</span><span class="w"> </span><span class="err">/data</span><span class="mi">2</span><span class="err">/nanoLLMServe/models/Qwen</span><span class="mi">3-8</span><span class="err">B</span><span class="w"> </span><span class="err">\</span><span class="w">
    </span><span class="err">--prompt</span><span class="w"> </span><span class="s2">"Explain static batching in one sentence."</span><span class="w"> </span><span class="err">\</span><span class="w">
    </span><span class="err">--max-new-tokens</span><span class="w"> </span><span class="mi">64</span><span class="w"> </span><span class="err">\</span><span class="w">
    </span><span class="err">--runs</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="err">\</span><span class="w">
    </span><span class="err">--warmup</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="err">\</span><span class="w">
    </span><span class="err">--batch-size</span><span class="w"> </span><span class="s2">"$BS"</span><span class="w"> </span><span class="err">\</span><span class="w">
    </span><span class="err">--device</span><span class="w"> </span><span class="err">cuda</span><span class="w"> </span><span class="err">\</span><span class="w">
    </span><span class="err">--dtype</span><span class="w"> </span><span class="err">bfloat</span><span class="mi">16</span><span class="w"> </span><span class="err">\</span><span class="w">
    </span><span class="err">--local-files-only</span><span class="w"> </span><span class="err">\</span><span class="w">
    </span><span class="err">--skip-naive-baseline</span><span class="w">
</span><span class="err">done</span><span class="w">
</span><span class="err">Loading</span><span class="w"> </span><span class="err">checkpoint</span><span class="w"> </span><span class="err">shards:</span><span class="w"> </span><span class="mi">100</span><span class="err">%|██████████████|</span><span class="w"> </span><span class="mi">5</span><span class="err">/</span><span class="mi">5</span><span class="w"> </span><span class="p">[</span><span class="mi">00</span><span class="err">:</span><span class="mi">00</span><span class="err">&lt;</span><span class="mi">00</span><span class="err">:</span><span class="mi">00</span><span class="p">,</span><span class="w"> </span><span class="mf">142.39</span><span class="err">it/s</span><span class="p">]</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"batch_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
  </span><span class="nl">"device"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cuda"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"dtype"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bfloat16"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"kv_cache_decode"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"generated_tokens"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"mean_decode_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.8507717087864877</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_elapsed_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.9322640344500541</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_prefill_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.030887942016124725</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_tokens_per_second"</span><span class="p">:</span><span class="w"> </span><span class="mf">33.12221489162687</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_tpot_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.029377328710896627</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_ttft_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.03308003842830658</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"model"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/data2/nanoLLMServe/models/Qwen3-8B"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"prompt_tokens"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w">
  </span><span class="nl">"runs"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
  </span><span class="nl">"warmup"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="err">Loading</span><span class="w"> </span><span class="err">checkpoint</span><span class="w"> </span><span class="err">shards:</span><span class="w"> </span><span class="mi">100</span><span class="err">%|██████████████|</span><span class="w"> </span><span class="mi">5</span><span class="err">/</span><span class="mi">5</span><span class="w"> </span><span class="p">[</span><span class="mi">00</span><span class="err">:</span><span class="mi">00</span><span class="err">&lt;</span><span class="mi">00</span><span class="err">:</span><span class="mi">00</span><span class="p">,</span><span class="w"> </span><span class="mf">139.83</span><span class="err">it/s</span><span class="p">]</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"batch_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
  </span><span class="nl">"device"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cuda"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"dtype"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bfloat16"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"kv_cache_decode"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"generated_tokens"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"mean_decode_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.8655244752764701</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_elapsed_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.9476028025150298</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_prefill_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.03145704716444016</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_tokens_per_second"</span><span class="p">:</span><span class="w"> </span><span class="mf">32.86332616672236</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_tpot_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.02961149960756302</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_ttft_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.03360582888126373</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"model"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/data2/nanoLLMServe/models/Qwen3-8B"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"prompt_tokens"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w">
  </span><span class="nl">"runs"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
  </span><span class="nl">"static_batch"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"batch_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
    </span><span class="nl">"generated_tokens"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"mean_batch_elapsed_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.9898763984441756</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_batch_tokens_per_second"</span><span class="p">:</span><span class="w"> </span><span class="mf">64.32565239699788</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_decode_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.9065125167369843</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_generated_tokens"</span><span class="p">:</span><span class="w"> </span><span class="mi">64</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_prefill_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.03188993483781814</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_prompt_tokens"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_tpot_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.03026210344026959</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_ttft_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.034097179770469666</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"warmup"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="err">Loading</span><span class="w"> </span><span class="err">checkpoint</span><span class="w"> </span><span class="err">shards:</span><span class="w"> </span><span class="mi">100</span><span class="err">%|██████████████|</span><span class="w"> </span><span class="mi">5</span><span class="err">/</span><span class="mi">5</span><span class="w"> </span><span class="p">[</span><span class="mi">00</span><span class="err">:</span><span class="mi">00</span><span class="err">&lt;</span><span class="mi">00</span><span class="err">:</span><span class="mi">00</span><span class="p">,</span><span class="w"> </span><span class="mf">141.49</span><span class="err">it/s</span><span class="p">]</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"batch_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w">
  </span><span class="nl">"device"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cuda"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"dtype"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bfloat16"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"kv_cache_decode"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"generated_tokens"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"mean_decode_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.8458494618535042</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_elapsed_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.9274740874767304</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_prefill_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.031040719151496886</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_tokens_per_second"</span><span class="p">:</span><span class="w"> </span><span class="mf">33.204527220435416</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_tpot_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.029299197807198477</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_ttft_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.033210942149162294</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"model"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/data2/nanoLLMServe/models/Qwen3-8B"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"prompt_tokens"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w">
  </span><span class="nl">"runs"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
  </span><span class="nl">"static_batch"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"batch_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w">
    </span><span class="nl">"generated_tokens"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"mean_batch_elapsed_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.9469317257404328</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_batch_tokens_per_second"</span><span class="p">:</span><span class="w"> </span><span class="mf">131.49626546592532</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_decode_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.8595415592193603</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_generated_tokens"</span><span class="p">:</span><span class="w"> </span><span class="mi">64</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_prefill_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.030785535275936127</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_prompt_tokens"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_tpot_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.029516532686021592</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_ttft_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.033043819665908816</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"warmup"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="err">Loading</span><span class="w"> </span><span class="err">checkpoint</span><span class="w"> </span><span class="err">shards:</span><span class="w"> </span><span class="mi">100</span><span class="err">%|██████████████|</span><span class="w"> </span><span class="mi">5</span><span class="err">/</span><span class="mi">5</span><span class="w"> </span><span class="p">[</span><span class="mi">00</span><span class="err">:</span><span class="mi">00</span><span class="err">&lt;</span><span class="mi">00</span><span class="err">:</span><span class="mi">00</span><span class="p">,</span><span class="w"> </span><span class="mf">141.55</span><span class="err">it/s</span><span class="p">]</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"batch_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w">
  </span><span class="nl">"device"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cuda"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"dtype"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bfloat16"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"kv_cache_decode"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"generated_tokens"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"mean_decode_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.8693151980638505</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_elapsed_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.9508710712194444</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_prefill_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.031239084899425507</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_tokens_per_second"</span><span class="p">:</span><span class="w"> </span><span class="mf">32.80601897231402</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_tpot_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.02967166981053731</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_ttft_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.0333852082490921</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"model"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/data2/nanoLLMServe/models/Qwen3-8B"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"prompt_tokens"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w">
  </span><span class="nl">"runs"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
  </span><span class="nl">"static_batch"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"batch_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w">
    </span><span class="nl">"generated_tokens"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="p">,</span><span class="w">
      </span><span class="mi">64</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"mean_batch_elapsed_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.989836023747921</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_batch_tokens_per_second"</span><span class="p">:</span><span class="w"> </span><span class="mf">257.34328723459373</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_decode_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.8920533761382103</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_generated_tokens"</span><span class="p">:</span><span class="w"> </span><span class="mi">64</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_prefill_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.031170780956745147</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_prompt_tokens"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_tpot_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.030032593272035085</span><span class="p">,</span><span class="w">
    </span><span class="nl">"mean_ttft_seconds"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.03355635851621628</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"warmup"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="err">(base)</span><span class="w"> </span><span class="err">gpu-A</span><span class="mi">100-05</span><span class="w"> </span><span class="err">nanoLLMServe</span><span class="w"> </span><span class="err">#</span><span class="w">
</span></code></pre></div></div> <p>解读一下，跑了4次，分别是batch为1、2、4、8的场景</p> <table> <thead> <tr> <th><strong>Batch</strong></th> <th><strong>batch elapsed</strong></th> <th><strong>total tokens/s</strong></th> <th><strong>单请求等效 tokens/s</strong></th> </tr> </thead> <tbody> <tr> <td>1</td> <td>~1.93s</td> <td>~33.1 tok/s</td> <td>~33.1 tok/s</td> </tr> <tr> <td>2</td> <td>~1.99s</td> <td>~64.3 tok/s</td> <td>~32.2 tok/s</td> </tr> <tr> <td>4</td> <td>~1.95s</td> <td>~131.5 tok/s</td> <td>~32.9 tok/s</td> </tr> <tr> <td>8</td> <td>~1.99s</td> <td>~257.3 tok/s</td> <td>~32.2 tok/s</td> </tr> </tbody> </table> <p>可以看出，吞吐是变多了，单个请求的时候是33tokens/s，8个请求一批的时候，系统整体的吞吐达到了257token/s，也就是每个请求得到的吞吐一样的情况下，并行的去推理导致系统整体吞吐量得到的极大的提升。这个就是批处理带来的提升！</p> <h1 id="总结">总结</h1> <p>这一波聊了Batching技术，这个特性的出发点就是从infra的角度去提升系统整体的吞吐量并减少接口调用时延。因为batch了，所以GPU的利用率也得到了提升。</p> <p>但是Static Batching也留下了一下问题，比如固定批次，导致已经结束的req也还是不断被带着一起forward，GPU的显存也要保留已经结束的req对应的KV Cache等不能释放，需要等到这批请求都结束后才能被释放，显存利用率降低了。这些都会在下一章Continuous Batching里解决</p>]]></content><author><name></name></author><category term="AI"/><category term="AI"/><category term="LLMInfra-101"/><summary type="html"><![CDATA[系列的第二集，前面的可以看：]]></summary></entry><entry xml:lang="zh"><title type="html">System vs Goal</title><link href="https://ifuryst.github.io/blog/2026/system-vs-goal/" rel="alternate" type="text/html" title="System vs Goal"/><published>2026-05-21T00:00:00+00:00</published><updated>2026-05-21T00:00:00+00:00</updated><id>https://ifuryst.github.io/blog/2026/system-vs-goal</id><content type="html" xml:base="https://ifuryst.github.io/blog/2026/system-vs-goal/"><![CDATA[<p>这两天在看Atomic Habits</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-21-system-vs-goal/1779340784_8-480.webp 480w,/assets/img/2026-05-21-system-vs-goal/1779340784_8-800.webp 800w,/assets/img/2026-05-21-system-vs-goal/1779340784_8-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-21-system-vs-goal/1779340784_8.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>里面关于Goal和System的描述，表述的很好。</p> <p>就好像我们在应试教育的年代里，大部分人追求的东西都是目标Goal，因此很多人通过考试，或者毕业升学，或者毕业入社会后，就抛弃掉一切，学习本身已经不再重要，因为学习只是为了取得成绩、获得入场券、获得绩点、获得学位。</p> <p>这也是我很痛恨应试教育的一个原因。它扭曲了我们对于一个美好的事物的认知，本该是好的东西，经过这个工厂化的流水线后，留在心里的只有不停打螺丝的那些瞬间。</p> <p>但是总有人是可以对抗这种固有体制的，有人反骨，有人看透，有人笑谈。我想这些大概都是构建出了一个属于自己的系统System。从此，学习不再是为了那个永无止境的Goal，而是服务自己的System，自我体系的建立，自洽性产生，正向循环闭环</p> <p>我们进入社会后，开始追去事业的发展，追求权利，追求金钱。或许这也是一个永无止境的Goal，最后经常是一地鸡毛，自己的System却一塌糊涂。我觉得我父亲就是一个非常典型的例子，终其一生都在追寻那寻而不可得的Goal，却忽略掉了最重要的东西。</p> <p>有些人是在构建自己的System的时候，别人眼中的goal自然而然的就来了，goal变成了副产物。我觉得这是更加棒的处世态度和方式。不过批判一波就是知易行难，很多动作在现实中很容易因为外物和内心而变形。</p> <p>我也在努力Build System中，不断的学习，不断的输入输出，不断的RL自己。</p>]]></content><author><name></name></author><category term="Opinion"/><category term="Opinion"/><summary type="html"><![CDATA[这两天在看Atomic Habits]]></summary></entry><entry xml:lang="zh"><title type="html">LLM Infra 101 v0.2: KV Cache</title><link href="https://ifuryst.github.io/blog/2026/llm-infra-101-v0-2-kv-cache-decode/" rel="alternate" type="text/html" title="LLM Infra 101 v0.2: KV Cache"/><published>2026-05-19T00:00:00+00:00</published><updated>2026-05-19T00:00:00+00:00</updated><id>https://ifuryst.github.io/blog/2026/llm-infra-101-v0-2-kv-cache-decode</id><content type="html" xml:base="https://ifuryst.github.io/blog/2026/llm-infra-101-v0-2-kv-cache-decode/"><![CDATA[<p>系列的第二集，前面的可以看：</p> <ol> <li> <p><a href="https://www.ifuryst.com/blog/2026/llm-infra-101-model-inference/">LLM Infra 101 v0.0: 推理模型</a></p> </li> <li> <p><a href="https://www.ifuryst.com/blog/2026/llm-infra-101-v0-1-openai-compatible-api/">LLM Infra 101 v0.1: API调用</a></p> </li> </ol> <p>这一期的代码在 <a href="https://github.com/iFurySt/nanoLLMServe/tree/release/v0.2.0">https://github.com/iFurySt/nanoLLMServe/tree/release/v0.2.0</a></p> <p>上一期过完，能通过API调用模型了。这期我们来支持KV Cache。在第一集的时候我们发现，每次forward 的时候都会重复计算：</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>prompt
-&gt; forward(prompt)
-&gt; 采样 token1
-&gt; forward(prompt + token1)
-&gt; 采样 token2
-&gt; forward(prompt + token1 + token2)
-&gt; ...
</code></pre></div></div> <p>这里每次推入的序列都会重新计算一遍，Transformer的计算就贵在Attention的计算：</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Q = xW_Q
K = xW_K
V = xW_V
Attention(Q, K, V)
</code></pre></div></div> <p>所以当我们没有KV缓存的时候，大概流程是这样的：</p> <ol> <li> <p>forward(prompt)</p> </li> <li> <p>计算prompt里每个token的Q/K/V</p> </li> <li> <p>计算prompt内部的Attention</p> </li> <li> <p>采样得到token1</p> </li> <li> <p>forward(prompt+token1)</p> </li> <li> <p>计算prompt里每个token的Q/K/V</p> </li> <li> <p>计算prompt内部的Attention</p> </li> <li> <p>采样得到token2</p> </li> <li> <p>forward(prompt+token1+token2)</p> </li> <li> <p>计算prompt里每个token的Q/K/V</p> </li> <li> <p>计算prompt内部的Attention</p> </li> <li> <p>采样得到token3</p> </li> </ol> <p>如果有了KV Cache，那流程会是这样的：</p> <ol> <li> <p>forward(prompt)</p> </li> <li> <p>计算prompt里每个token的Q/K/V</p> </li> <li> <p>计算prompt内部的Attention</p> </li> <li> <p>保存K/V到KV Cache</p> </li> <li> <p>采样得到token1</p> </li> <li> <p>forward(token1+past_kv(也就是prompt的))</p> </li> <li> <p>只计算token1的Q/K/V</p> </li> <li> <p>读取prompt的K/V</p> </li> <li> <p>计算Attention(Q_token1, K_prompt+token1, V_prompt+token1)</p> </li> <li> <p>保存token1的K/V</p> </li> <li> <p>采样得到token2</p> </li> <li> <p>forward(token2+past_kv(也就是prompt+token1的))</p> </li> <li> <p>只计算token2的Q/K/V</p> </li> <li> <p>读取prompt+token1的K/V</p> </li> <li> <p>计算Attention(Q_token2, K_prompt+token1+token2, V_prompt+token1+token2)</p> </li> <li> <p>保存token2的K/V</p> </li> <li> <p>采样得到token3</p> </li> </ol> <p>本质上KV Cache就是为了后续计算可以重复利用，我们来看一个实际推理过程中的环节：</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Token
 ↓
Attention（看上下文）
 ↓
FFN（自己思考）
 ↓
下一层
</code></pre></div></div> <p>可以看到Attention里的K/V都Cache里，但是FFN里没有任何Cache的，这个是因为Attention的计算都是依赖于之前计算的，但是FFN都是针对当前token自己去做计算（非线性变换）的</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>token3
 ↓
Linear Up Projection（升维，高纬空间有更复杂的表达能力）
 ↓
Activation (GELU / SwiGLU)
 ↓
Linear Down Projection（降维）
 ↓
output
</code></pre></div></div> <p>这个过程中只涉及到token3本身的计算，输出的FFN(hidden3)只会在当前layer使用一次，后续就没用了，所以没办法做Cache</p> <p>知道了原理后，来看看实现</p> <h1 id="实现">实现</h1> <p>改动文件涉及这些：</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">.</span>
├── benchmarks/
│   └── benchmark_generate.py      <span class="c"># 增加 KV cache vs v0.0 naive baseline 对比，输出 TTFT/TPOT</span>
├── src/
│   └── nanollmserve/
│       ├── cli/
│       │   └── generate.py        <span class="c"># show-stats 新增 TTFT / TPOT</span>
│       └── engine/
│           ├── engine.py          <span class="c"># 核心改动：prefill + decode + past_key_values 复用</span>
│           └── request.py         <span class="c"># 新增 GenerationRequestState，保存单请求生成状态</span>
└── tests/
    ├── test_engine.py             <span class="c"># 验证 decode 阶段只喂单 token，且复用 past_key_values</span>
    ├── test_request_state.py      <span class="c"># 验证 request state 的 token 统计和 TPOT</span>
    └── test_benchmark_generate.py <span class="c"># 验证 benchmark 汇总字段和 speedup 计算</span>
</code></pre></div></div> <h2 id="prefill">Prefill</h2> <p><code class="language-plaintext highlighter-rouge">src/nanollmserve/engine/engine.py:160</code></p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">model</span><span class="p">.</span><span class="nf">eval</span><span class="p">()</span>
<span class="k">with</span> <span class="n">torch</span><span class="p">.</span><span class="nf">inference_mode</span><span class="p">():</span>
    <span class="n">prefill_start</span> <span class="o">=</span> <span class="nf">perf_counter</span><span class="p">()</span>
    <span class="n">outputs</span> <span class="o">=</span> <span class="nf">model</span><span class="p">(</span><span class="n">input_ids</span><span class="o">=</span><span class="n">input_ids</span><span class="p">,</span> <span class="n">attention_mask</span><span class="o">=</span><span class="n">state</span><span class="p">.</span><span class="n">attention_mask</span><span class="p">,</span> <span class="n">use_cache</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
    <span class="n">state</span><span class="p">.</span><span class="n">prefill_seconds</span> <span class="o">=</span> <span class="nf">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">prefill_start</span>
    <span class="n">state</span><span class="p">.</span><span class="n">past_key_values</span> <span class="o">=</span> <span class="nf">getattr</span><span class="p">(</span><span class="n">outputs</span><span class="p">,</span> <span class="sh">"</span><span class="s">past_key_values</span><span class="sh">"</span><span class="p">,</span> <span class="bp">None</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">state</span><span class="p">.</span><span class="n">past_key_values</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
        <span class="k">raise</span> <span class="nc">RuntimeError</span><span class="p">(</span><span class="sh">"</span><span class="s">model did not return past_key_values; KV cache decode requires use_cache support</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div> <p>这边的model是基于transformers加载进来的模型对象</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">loaded</span> <span class="o">=</span> <span class="nf">load_model_and_tokenizer</span><span class="p">(...)</span>
<span class="n">result</span> <span class="o">=</span> <span class="nf">generate_one</span><span class="p">(</span>
    <span class="n">loaded</span><span class="p">.</span><span class="n">model</span><span class="p">,</span>
    <span class="n">loaded</span><span class="p">.</span><span class="n">tokenizer</span><span class="p">,</span>
    <span class="n">prompt</span><span class="p">,</span>
    <span class="bp">...</span>
<span class="p">)</span>
</code></pre></div></div> <p>传入<code class="language-plaintext highlighter-rouge">use_cache=True</code> 参数后，会要求模型forward后返回<code class="language-plaintext highlighter-rouge">past_key_values</code> ，后续decode的时候再把这个KV Cache传回去。</p> <p>这里做的就是预填充Prefill，简单说就是把传入的prompt完整的处理一遍，建立KV Cache，后续就只要做新的token的Q计算，然后就可以服用之前的KV Cache做Attention的计算了</p> <h2 id="decode">Decode</h2> <p><code class="language-plaintext highlighter-rouge">src/nanollmserve/engine/engine.py:179</code></p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">next_token</span> <span class="o">=</span> <span class="nf">_sample_from_outputs</span><span class="p">(</span><span class="n">outputs</span><span class="p">,</span> <span class="n">temperature</span><span class="o">=</span><span class="n">temperature</span><span class="p">,</span> <span class="n">generator</span><span class="o">=</span><span class="n">generator</span><span class="p">)</span>
<span class="k">yield</span> <span class="nf">_record_step</span><span class="p">(</span>
    <span class="n">tokenizer</span><span class="p">,</span>
    <span class="n">state</span><span class="p">,</span>
    <span class="n">next_token</span><span class="p">,</span>
    <span class="n">eos_token_ids</span><span class="o">=</span><span class="n">eos_token_ids</span><span class="p">,</span>
    <span class="n">start</span><span class="o">=</span><span class="n">start</span><span class="p">,</span>
    <span class="n">max_new_tokens</span><span class="o">=</span><span class="n">max_new_tokens</span><span class="p">,</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">state</span><span class="p">.</span><span class="n">finished</span><span class="p">:</span>
    <span class="k">return</span>

<span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">max_new_tokens</span> <span class="o">-</span> <span class="mi">1</span><span class="p">):</span>
    <span class="n">decode_start</span> <span class="o">=</span> <span class="nf">perf_counter</span><span class="p">()</span>
    <span class="n">outputs</span> <span class="o">=</span> <span class="nf">model</span><span class="p">(</span>
        <span class="n">input_ids</span><span class="o">=</span><span class="n">next_token</span><span class="p">.</span><span class="nf">to</span><span class="p">(</span><span class="n">input_ids</span><span class="p">.</span><span class="n">device</span><span class="p">),</span>
        <span class="n">attention_mask</span><span class="o">=</span><span class="n">state</span><span class="p">.</span><span class="n">attention_mask</span><span class="p">,</span>
        <span class="n">past_key_values</span><span class="o">=</span><span class="n">state</span><span class="p">.</span><span class="n">past_key_values</span><span class="p">,</span>
        <span class="n">use_cache</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="n">state</span><span class="p">.</span><span class="n">past_key_values</span> <span class="o">=</span> <span class="nf">getattr</span><span class="p">(</span><span class="n">outputs</span><span class="p">,</span> <span class="sh">"</span><span class="s">past_key_values</span><span class="sh">"</span><span class="p">,</span> <span class="bp">None</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">state</span><span class="p">.</span><span class="n">past_key_values</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
        <span class="k">raise</span> <span class="nc">RuntimeError</span><span class="p">(</span><span class="sh">"</span><span class="s">model did not return past_key_values during decode</span><span class="sh">"</span><span class="p">)</span>

    <span class="n">next_token</span> <span class="o">=</span> <span class="nf">_sample_from_outputs</span><span class="p">(</span><span class="n">outputs</span><span class="p">,</span> <span class="n">temperature</span><span class="o">=</span><span class="n">temperature</span><span class="p">,</span> <span class="n">generator</span><span class="o">=</span><span class="n">generator</span><span class="p">)</span>
    <span class="k">yield</span> <span class="nf">_record_step</span><span class="p">(</span>
        <span class="n">tokenizer</span><span class="p">,</span>
        <span class="n">state</span><span class="p">,</span>
        <span class="n">next_token</span><span class="p">,</span>
        <span class="n">eos_token_ids</span><span class="o">=</span><span class="n">eos_token_ids</span><span class="p">,</span>
        <span class="n">start</span><span class="o">=</span><span class="n">start</span><span class="p">,</span>
        <span class="n">max_new_tokens</span><span class="o">=</span><span class="n">max_new_tokens</span><span class="p">,</span>
        <span class="n">decode_start</span><span class="o">=</span><span class="n">decode_start</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="k">if</span> <span class="n">state</span><span class="p">.</span><span class="n">finished</span><span class="p">:</span>
        <span class="k">break</span>
</code></pre></div></div> <p>后续的循环这里，可以看到进入的已经不再是不断拼接的input_ids了，而是<code class="language-plaintext highlighter-rouge">next_token</code> ，也就是前一次生成的token，然后会通过<code class="language-plaintext highlighter-rouge">past_key_values=state.past_key_values,</code>带上前面的KV</p> <h1 id="推理">推理</h1> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-19-llm-infra-101-v0-2-kv-cache-decode/1779196784_7-480.webp 480w,/assets/img/2026-05-19-llm-infra-101-v0-2-kv-cache-decode/1779196784_7-800.webp 800w,/assets/img/2026-05-19-llm-infra-101-v0-2-kv-cache-decode/1779196784_7-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-19-llm-infra-101-v0-2-kv-cache-decode/1779196784_7.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>因此这次改动是单个请求内的KV Cache Reuse，prefill后decode复用，所以没办法在多个请求之间命中缓存，就没办法做那种演示了，但是bench是可以看出来<code class="language-plaintext highlighter-rouge">kv_cache_decode.mean_prefill_seconds</code>是非0</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="sh">"</span><span class="s">elapsed_speedup</span><span class="sh">"</span><span class="p">:</span> <span class="mf">1.066</span>
<span class="sh">"</span><span class="s">tpot_speedup</span><span class="sh">"</span><span class="p">:</span> <span class="mf">1.073</span>
</code></pre></div></div> <p>现在总耗时和TOPT（Time per Output Token）都变快了，但是因为输入的prompt很短，没有更明显的差距体现</p> <h1 id="总结">总结</h1> <p>这些大概就是引入KV Cache带来的变化，代码改动不多，也相对简洁，因为transformers这类框架帮我屏蔽了很多实现细节。</p> <p>另外这里的KV Cache在GPU显存里，会涉及到<code class="language-plaintext highlighter-rouge">每层</code> 和<code class="language-plaintext highlighter-rouge">每个token</code> 都要存K/V，KV Cache的大小近似于：</p> <p><code class="language-plaintext highlighter-rouge">2*L*T*H*dtype</code></p> <table> <thead> <tr> <th><strong>参数</strong></th> <th><strong>含义</strong></th> </tr> </thead> <tbody> <tr> <td>L</td> <td>layer 数</td> </tr> <tr> <td>T</td> <td>sequence length</td> </tr> <tr> <td>H</td> <td>hidden size</td> </tr> <tr> <td>2</td> <td>K+V</td> </tr> </tbody> </table> <p>比如我们简单算一个Qwen3 32B的：</p> <p>2<em>64</em>128k<em>5120</em>2bytes/1024^3=~156.25GB</p> <p>但是实际上Qwen3走了GQA（attention heads是40，kv heads是8，head_dim是128），所以实际大概会是33.5GB左右（GQA这些技术的意义来了）</p> <p>可以看出大模型在推理的时候，显存会被大量的KV Cache占满！这个也是Infra里需要解决的一个重要课题。现在很多模型使用一些技术来降低KV Cache，列举几个，比如模型层可以做的有：</p> <ul> <li> <p>GQA（Grouped Query Attention）这种技术，Q Heads很多KV Heads很少，这样可以大量降低KV Cache</p> </li> <li> <p>MQA（Multi-Query Attention）：比GQA更激进，所有的Q共享同一组KV，但是效果会下降比较多</p> </li> <li> <p>MLA（Multi-head Latent Attention）：是DeepSeek很关键的方向，不直接存完整的KV，而是存压缩的latent（KV Compression），需要的时候再恢复</p> </li> <li> <p>Sliding Window Attention：只看最近的窗口，比如看最近4k，而不是完整的1M上下文</p> </li> <li> <p>Sparse Attention：不是所有的token都两两attention（比如只关注附近的token、少量关键的token以及一些summary token等）</p> </li> </ul> <p>Inference Engine层可以做的有：</p> <ul> <li> <p>PagedAttention：vllm主要的特性，kv cache做分页</p> </li> <li> <p>Prefix Cache：共享相同前缀的prompt的kv，不重复做prefill</p> </li> <li> <p>KV Quantization：KV不存bf16，改成存int8/int4，但是伴随量化也会带来精度下降</p> </li> <li> <p>Distributed KV Cache：KV分布到多GPU，按head/layer/sequence去做shard</p> </li> <li> <p>PD分离（Prefill-Decode Disaggregation）：Prefill和Decode分不同机器，因为前者是Compute-bound型，后者是Memory-bound型，这也可以有不同的机器支撑</p> </li> </ul> <p>这些手段或多或少都在解决KV Cache相关的问题，只不过关注的角度不太一样。后续我们也会接触到里面的某些内容，其他的有价值值得写的也会单独有文章来聊</p>]]></content><author><name></name></author><category term="Blog"/><category term="Blog"/><category term="微信公众号"/><category term="Substack"/><summary type="html"><![CDATA[系列的第二集，前面的可以看：]]></summary></entry><entry xml:lang="zh"><title type="html">LLM Infra 101 v0.1: API调用</title><link href="https://ifuryst.github.io/blog/2026/llm-infra-101-v0-1-openai-compatible-api/" rel="alternate" type="text/html" title="LLM Infra 101 v0.1: API调用"/><published>2026-05-18T00:00:00+00:00</published><updated>2026-05-18T00:00:00+00:00</updated><id>https://ifuryst.github.io/blog/2026/llm-infra-101-v0-1-openai-compatible-api</id><content type="html" xml:base="https://ifuryst.github.io/blog/2026/llm-infra-101-v0-1-openai-compatible-api/"><![CDATA[<p>系列的第二集，前面的可以看：</p> <ol> <li><a href="https://www.ifuryst.com/blog/2026/llm-infra-101-model-inference/">LLM Infra 101 v0.0: 推理模型</a></li> </ol> <p>这一期的代码在 <a href="https://github.com/iFurySt/nanoLLMServe/tree/release/v0.1.0">https://github.com/iFurySt/nanoLLMServe/tree/release/v0.1.0</a></p> <p>上一期过完，有了一个能通过CLI调用的，这一期我们做个新的特性，我们做一个兼容OpenAI的API调用接口，这样现有的大部分sdk都可以无缝接入了。大体上会支持这样的内容：</p> <ol> <li> <p>HTTP Server</p> </li> <li> <p>OpenAI-Compatible endpoint</p> </li> <li> <p>支持stream参数，可以全量返回也可以流式返回</p> </li> <li> <p>支持chat接口，暂时不支持response接口</p> </li> </ol> <h1 id="实现">实现</h1> <p>这次改动的不多，基本上就是包装了API，相关改动情况：</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">.</span>
└── src/
    └── nanollmserve/
        ├── api/
        │   ├── __init__.py        <span class="c"># API 包入口，占位导出用</span>
        │   ├── openai_server.py   <span class="c"># OpenAI-compatible HTTP server：/v1/models、/v1/responses、/v1/chat/completions、/v1/completions</span>
        │   └── protocol.py        <span class="c"># OpenAI-compatible 协议模型：请求/响应 schema、prompt 转换、usage/response 构造</span>
        └── engine/
            └── engine.py          <span class="c"># 增加 streaming generation：GenerationStep、stream_generate_one，并让 generate_one 复用流式路径</span>
</code></pre></div></div> <p>新增接口如下：</p> <table> <thead> <tr> <th><strong>接口</strong></th> <th><strong>用途</strong></th> </tr> </thead> <tbody> <tr> <td>/v1/models</td> <td>返回当前 server 暴露的模型列表</td> </tr> <tr> <td>/v1/responses</td> <td>OpenAI 新推荐的 Responses API，支持 text-only create/stream/retrieve</td> </tr> <tr> <td>/v1/chat/completions</td> <td>兼容传统 chat messages 格式</td> </tr> <tr> <td>/v1/completions</td> <td>兼容 legacy prompt completion 格式</td> </tr> </tbody> </table> <p>实现里关注Chat和Response API，一个原因是这两个接口都是主流使用，另一个是这两个接口是典型对比。比如现在很多AI Agent都接入Response了，因为可以走前缀缓存，命中缓存后，不管速度和费用都能得到收益</p> <p>整体基本就是兼容OpenAI的接口协议，比如Chat Completion：</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"model"</span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"messages"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w"> </span><span class="nl">"role"</span><span class="p">:</span><span class="w"> </span><span class="s2">"user"</span><span class="p">,</span><span class="w"> </span><span class="nl">"content"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hello"</span><span class="w"> </span><span class="p">}]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div> <p>Response类似：</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"model"</span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"input"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hello"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div> <p>之后就是数据转换，转成Engine能识别的格式。其他就没有太多值得展开讲的</p> <h1 id="推理">推理</h1> <p>过一下整体的推理过程，首先是启动HTTP服务</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">CUDA_VISIBLE_DEVICES</span><span class="o">=</span>0 <span class="nv">PYTHONPATH</span><span class="o">=</span>src /data/anaconda3/bin/python <span class="nt">-m</span> nanollmserve.api.openai_server <span class="se">\</span>
  <span class="nt">--model</span> /data2/nanoLLMServe/models/Qwen3-8B <span class="se">\</span>
  <span class="nt">--served-model-name</span> Qwen3-8B <span class="se">\</span>
  <span class="nt">--local-files-only</span> <span class="se">\</span>
  <span class="nt">--host</span> 127.0.0.1 <span class="se">\</span>
  <span class="nt">--port</span> 18080 <span class="se">\</span>
  <span class="nt">--device</span> cuda <span class="se">\</span>
  <span class="nt">--dtype</span> bfloat16
</code></pre></div></div> <p>启动后可以看看模型列表</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-s</span> http://127.0.0.1:18080/v1/models | jq <span class="nb">.</span>
</code></pre></div></div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115783_1-480.webp 480w,/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115783_1-800.webp 800w,/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115783_1-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115783_1.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>请求Response API</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-sS</span> http://127.0.0.1:18080/v1/responses <span class="se">\</span>
  <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{
    "model": "Qwen3-8B",
    "instructions": "Answer briefly.",
    "input": "Explain KV cache in one sentence.",
    "max_output_tokens": 100,
    "temperature": 0,
    "store": true
  }'</span> | jq <span class="nb">.</span>
</code></pre></div></div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115783_2-480.webp 480w,/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115783_2-800.webp 800w,/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115783_2-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115783_2.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>Response API开启Stream流式返回</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-sS</span> http://127.0.0.1:18080/v1/responses <span class="se">\</span>
  <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{
    "model": "Qwen3-8B",
    "instructions": "Answer briefly.",
    "input": "Explain KV cache in one sentence.",
    "max_output_tokens": 100,
    "temperature": 0,
    "stream": true,
    "store": true
  }'</span>
</code></pre></div></div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115784_3-480.webp 480w,/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115784_3-800.webp 800w,/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115784_3-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115784_3.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>尝试用resp id请求之前已经请求过的</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-sS</span> http://127.0.0.1:18080/v1/responses/resp-1770dd64b5d44d1bbd93fc7dc5857bda | jq <span class="nb">.</span>
</code></pre></div></div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115784_4-480.webp 480w,/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115784_4-800.webp 800w,/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115784_4-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115784_4.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>请求Chat Completion API</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-sS</span> http://127.0.0.1:18080/v1/chat/completions <span class="se">\</span>
  <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{
    "model": "Qwen3-8B",
    "messages": [
      {"role": "user", "content": "Explain KV cache in one sentence."}
    ],
    "max_tokens": 100,
    "temperature": 0
  }'</span> | jq <span class="nb">.</span>
</code></pre></div></div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115784_5-480.webp 480w,/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115784_5-800.webp 800w,/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115784_5-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115784_5.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>Chat Completion API开启stream流式返回</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-sS</span> http://127.0.0.1:18080/v1/chat/completions <span class="se">\</span>
  <span class="nt">-H</span> <span class="s1">'Content-Type: application/json'</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{
    "model": "Qwen3-8B",
    "messages": [
      {"role": "user", "content": "Explain KV cache in one sentence."}
    ],
    "max_tokens": 100,
    "temperature": 0,
    "stream": true
  }'</span>
</code></pre></div></div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115785_6-480.webp 480w,/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115785_6-800.webp 800w,/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115785_6-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-18-llm-infra-101-v0-1-openai-compatible-api/1779115785_6.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <h1 id="总结">总结</h1> <p>这波很简单，包了一下API，没有太多东西需要讲。总体而言这些Infra对外其实也是一个HTTP Server，屏蔽掉下面模型调度推理的细节，调用方无需感知那么多，从最早的无状态化Chat Completion API慢慢过渡到现在有一些状态信息的Response API，背后也反应了行业在模型推理层面的变化。</p>]]></content><author><name></name></author><category term="AI"/><category term="AI"/><category term="LLMInfra-101"/><summary type="html"><![CDATA[系列的第二集，前面的可以看：]]></summary></entry><entry xml:lang="zh"><title type="html">日常Harness</title><link href="https://ifuryst.github.io/blog/2026/daily-harness/" rel="alternate" type="text/html" title="日常Harness"/><published>2026-05-17T00:00:00+00:00</published><updated>2026-05-17T00:00:00+00:00</updated><id>https://ifuryst.github.io/blog/2026/daily-harness</id><content type="html" xml:base="https://ifuryst.github.io/blog/2026/daily-harness/"><![CDATA[<p>是时候写点Harness相关的了，此前零零散散在各种地方输出，都是碎片化的。加之Public后得到的反馈都是正向的，我想这个东西对于很多人来说，是个有价值的东西。牺牲自己的2小时时间，送给有缘人</p> <h1 id="解放思想">解放思想</h1> <p>我的东西不新，甚至不一定适合你的工作流或产品，但是我的想法一定会让你有所得，这也是我想要通过这篇文章传达的。</p> <p>相信还有很多人并不相信大模型的能力已经严重溢出了，一个可能是认知不够，一个是尝试的不够，后者是被前者驱动着的，因此从根源（fancy的说法：第一性原理）来看待，认知是需要首先解决的。</p> <p>最近我反复和身边的人传播着敢想，敢于去挑战AI的能力边界，努力摸到天花板，读完这篇文章或许你会对于这句话有更好的理解。</p> <h1 id="harness-template">Harness Template</h1> <p>就从这个开源的Repo开始吧。相关的repo有三个：</p> <ul> <li><a href="https://github.com/iFurySt/harness-template">https://github.com/iFurySt/harness-template</a></li> <li><a href="https://github.com/iFurySt/harness-template-cn">https://github.com/iFurySt/harness-template-cn</a></li> <li><a href="https://github.com/iFurySt/harness-cli">https://github.com/iFurySt/harness-cli</a></li> </ul> <p>前面两个是一样的，中英文差异，我个人大部分会喜欢用中文的，少部分开源项目会用英文的。第三个是是用来快速new以项目的cli，本质也是是用了前面两个。用法如下：</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-17-daily-harness/1778989811_1-480.webp 480w,/assets/img/2026-05-17-daily-harness/1778989811_1-800.webp 800w,/assets/img/2026-05-17-daily-harness/1778989811_1-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-17-daily-harness/1778989811_1.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>直接在GitHub上使用Template来创建一个仓库。这个方式对于我整体要new新项目来说属于脱裤子放屁，多此一举了，所以我一般就是直接cli跑一下：</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-17-daily-harness/1778989811_2-480.webp 480w,/assets/img/2026-05-17-daily-harness/1778989811_2-800.webp 800w,/assets/img/2026-05-17-daily-harness/1778989811_2-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-17-daily-harness/1778989811_2.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>然后就可以快乐的开始与AI共舞了</p> <p>开始更深入之前，先来说下Harness这个东西，AI Agent的发展有这么几个重要阶段：</p> <ol> <li>Prompt Engineering</li> <li>Context Engineering</li> <li>Harness</li> </ol> <p>遥想去年我还在写CE101这本书，往事不堪回首，毫不客气的讲，现在看到那些东西感觉就应该直接丢带垃圾桶里去。但是我依然觉得当时我花时间写是值得的，虽然现在我需要打自己的脸</p> <p>直到大半年来Harness已经红遍大街小巷了，我不知道这个词的源头是哪里来的，我也不感兴趣，但是我觉得这个词用的太精妙了。</p> <p>我们知道模型是非确定性的，我们不断写Prompt、Skills就是去约束模型的行为，让其朝着我们期望的方向去走，就好像骑马一样，马鞍、马勒、缰绳、马镫等这些东西都是未来去约束马朝着我们想要的方向行走、奔跑或停住。但是马只是动物，听不懂人话，只能通过训练和这些外部的工具去约束，大模型也是，模型无法做出确定性的结果，我们无法控制它，只能约束他，这就是为什么我们用Harness而不用Control之类的词的缘故</p> <p>关于Harness，其实根据不同的语境，我觉得可以分为两种：</p> <ol> <li>针对产品/服务的Harness：比如配套的工具、配套的Memory机制、配套的Sandbox等，服务于模型，让其更好的发挥效果</li> <li>针对研发、创造等生产过程中的Harnes：配套的脚手架、环境、上下文等，是为了更好的创作</li> </ol> <p>针对1型Harness，没啥好说的，去clone一下codex、claude code、openclaw或hermes的源码看看，借助ai分析一下，都真相大白了，没有什么太多的magic，号称自己harness牛逼的人，无外乎这几种可能：</p> <ol> <li>能力和眼界一般</li> <li>吹牛逼的骗子</li> <li>系统过于复杂（让他在各种努力下打造出一个高于SOTA baseline的产品，然后很高兴）</li> </ol> <p>现有的这些harness技术，很早都有了，那为什么2年前没有这样呢？回头看看2年前的大模型能力。因此现阶段回归模型即产品，会是一个更加客观和正确的认知。所以关于1型Harness，我不再展开，各家产品好烂自己门清，要怎么集成应该也都心里有数</p> <p>现在回归2型Harness，这个也是我的Harness Template的主战场，也是我相信能给个人、团队和组织带来实打实提升的点。展开之前，有几篇前面写的文章，有时间我觉得也值得看看：</p> <ul> <li><a href="https://www.ifuryst.com/blog/2026/speedrunning-the-ai-era/"><strong>我们是如何在AI Era飙车的</strong></a></li> <li><a href="https://www.ifuryst.com/blog/2026/the-urge-to-solve/"><strong>解决问题的原始冲动</strong></a></li> <li><a href="https://www.ifuryst.com/blog/2026/open-browser-use/"><strong>Browser Use详解</strong></a></li> </ul> <p>里面或多或少的都讲了一些我在摸索和实践Harness的过程中的一些想法和实际的实践。</p> <p>这套方法论的核心其实就在于让一切的东西始于AI也终于AI，AI产AI用，一切信息不出Repo。有点虚，一步步来，先看张图：</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-17-daily-harness/1778989811_3-480.webp 480w,/assets/img/2026-05-17-daily-harness/1778989811_3-800.webp 800w,/assets/img/2026-05-17-daily-harness/1778989811_3-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-17-daily-harness/1778989811_3.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>这个是我自己写的，我觉得这些点能较好的表达出我的想法。</p> <h2 id="agentsmd">AGENTS.md</h2> <p>接着看看项目的目录：</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-17-daily-harness/1778989813_4-480.webp 480w,/assets/img/2026-05-17-daily-harness/1778989813_4-800.webp 800w,/assets/img/2026-05-17-daily-harness/1778989813_4-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-17-daily-harness/1778989813_4.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>非常简单，进来首先是到AGENTS.md和CLAUDE.md，基本能Cover主流的Agent了，然后这边的AGNETS.md通常是做目录（TOC）的，会把东西打散到docs里，这样能按需取用，减少上下文损耗</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-17-daily-harness/1778989813_5-480.webp 480w,/assets/img/2026-05-17-daily-harness/1778989813_5-800.webp 800w,/assets/img/2026-05-17-daily-harness/1778989813_5-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-17-daily-harness/1778989813_5.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>通常我们会按一些关键的节点或领域来划分，比如：</p> <ul> <li>开始前要读：通常是一些协作规范、Repo的简要情况和一些指导性的东西</li> <li>工作完要读：一般是一些收尾的东西，比如写历史记录、测试覆盖、验收等动作</li> <li>提交前要读：通常会做一些比如全量本地测试或一些分支操作等动作</li> <li>领域读取：最常见无外乎前后端了，再细化也有按照模块或者DDD之类的去选择性读取</li> </ul> <p>这些基本上就是一个大型的自然语言Harness现场，用现在流行的说法，就是写了好多非标Skills，然后按需加载使用。</p> <h2 id="docs">docs</h2> <p>接下去docs，这里就很关键了，套用现在的说法，可以理解这里就是Karpathy大佬的LLM Wiki的思想了，只能说大佬（AI头部网红）的传播度比较高。</p> <p>这里的目录划分没什么讲究的，只是我自己拍脑袋了一套出来，实际可以按照自己项目的情况和需求来调整，大概是这么一些想法点：</p> <ul> <li>AGENTS.md拆分出来的一些独立文件可以落在这里，可以单md文件，也可以上目录，里面更细化</li> <li>histories：这个我自己想出来的天才之作，有了这个，整个repo从Day1开始的一切Query和变化的历史都在案，带来的好处是，不需要写文档了，新人onboarding上AI就能知道过往的一切了。当一个功能出问题后也可以快速回溯哪里改了，哪里导致的，哪里退化的。也可以借机让一些跑得比较慢的人学习其他高手怎么Vibe的。某种意义可以当作是这个repo的记忆</li> <li>milestone/feature规划：一般是用文件系统来跟踪TODO的，哪怕在有了/goal（Ralph Loop）的今天，这个依然很有用，在上下文里跟踪TODO属于找死，更别说大feat，在文件里跟踪的好处是可以支撑长时任务的进行，也可以在进行中通过调整文件来动态修改milestone和目标（后续还会提到这部分）</li> <li>其他都是什么产品定义、设计规范、参考文件、release文档等等</li> </ul> <h2 id="其他">其他</h2> <p>其他的不多了，举几个重要的：</p> <ul> <li>scripts，通常是一些可复用的脚本，这部分AI也可以沉淀，这样后续可以持续服用</li> <li>skills，我没包含在template里，但是这部分其实是会有很多skills的，比如操作浏览器，登陆堡垒机或开发机，部署的指导等等</li> <li>敏感文件，我一般会用诸如.harness或.agents之类的目录来存放一些敏感文件，这个目录会加到gitignore，目录里的文件也可以套用环境变量来做加密，避免key之类的明文罗盘，AI是可以在取用的时候执行一个脚本实时从env读取密钥解密拿到内容的。</li> </ul> <h1 id="用法">用法</h1> <p>讲完了，或许很一般，但就好像我前面截图里那句名言<code class="language-plaintext highlighter-rouge">**Less is more**</code>一样，当我们使用起来，魔法就来了。我用实际的例子来讲吧。</p> <h2 id="open-computer-use">Open Computer Use</h2> <p>先来看看<a href="https://github.com/iFurySt/open-codex-computer-use">Open Computer Use</a>。在开始这个项目之前，我需要先对现有的机制去做分析，如果是裸的直接用codex分析的话，很多内容无法沉淀的，最终会在一次次上下文compact中丢失很多分析的细节，因此我就通过harness template的机制让我在分析的过程中把得到的信息不断沉淀到docs里</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-17-daily-harness/1778989814_6-480.webp 480w,/assets/img/2026-05-17-daily-harness/1778989814_6-800.webp 800w,/assets/img/2026-05-17-daily-harness/1778989814_6-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-17-daily-harness/1778989814_6.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>而且因为逆向分析的过程是持续性的，需要用很多不同的工具搭配着来分析（<del>买不起IDA Pro</del>），有时候后面收集到的最新信息会覆盖掉之前错误的结论，因此这个持久化可<strong>复利</strong>的体系就显得非常有必要了。</p> <p>有了这些信息，在后面的实现过程中，可以不断参阅这些信息去做实现，哪怕后续官方的版本升级后，也可以持续增量更新新内容，永续性就来了</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-17-daily-harness/1778989814_7-480.webp 480w,/assets/img/2026-05-17-daily-harness/1778989814_7-800.webp 800w,/assets/img/2026-05-17-daily-harness/1778989814_7-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-17-daily-harness/1778989814_7.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <h2 id="aifi">AIFi</h2> <p>再来到<a href="https://github.com/iFurySt/aifi">AIFi</a>这个项目，这是一个金融分析的项目，项目名的灵感来源于DeFi之于非中心化金融，AIFi之于AI金融。这个项目0代码需求，本身就是大量的Skills组成的产品，目录即产品。</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-17-daily-harness/1778989814_8-480.webp 480w,/assets/img/2026-05-17-daily-harness/1778989814_8-800.webp 800w,/assets/img/2026-05-17-daily-harness/1778989814_8-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-17-daily-harness/1778989814_8.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>新时代开源，就是开源Skill。这边就是通过把传统的投资理财领域的专家建模成一个一个Skill，来达到让AI可以按需扮演不同的人来做不同的工作。经典的来了，这次我就不是使用docs了，我直接让AI在根目录的research里持久化每次调研分析的结果，这样可以实现调研<strong>复利</strong></p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-17-daily-harness/1778989815_9-480.webp 480w,/assets/img/2026-05-17-daily-harness/1778989815_9-800.webp 800w,/assets/img/2026-05-17-daily-harness/1778989815_9-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-17-daily-harness/1778989815_9.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>这里让架构已经不是软件的架构了，而是整个体系的架构。</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-17-daily-harness/1778989815_10-480.webp 480w,/assets/img/2026-05-17-daily-harness/1778989815_10-800.webp 800w,/assets/img/2026-05-17-daily-harness/1778989815_10-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-17-daily-harness/1778989815_10.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>看一些分析效果</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-17-daily-harness/1778989815_11-480.webp 480w,/assets/img/2026-05-17-daily-harness/1778989815_11-800.webp 800w,/assets/img/2026-05-17-daily-harness/1778989815_11-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-17-daily-harness/1778989815_11.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>整体可以说是和以前的deep research类似了，但是现在我们还需要单独打造一个deep research么？codex/claude code打开，套上这样一个harness，不管是什么领域的，都可以沉淀出很好的内容</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-17-daily-harness/1778989816_12-480.webp 480w,/assets/img/2026-05-17-daily-harness/1778989816_12-800.webp 800w,/assets/img/2026-05-17-daily-harness/1778989816_12-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-17-daily-harness/1778989816_12.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>上点<a href="https://github.com/iFurySt/visual-html-gen-ui">Gen UI的Skill</a>就可以产出一些可视化的内容了。</p> <h1 id="解放认知">解放认知</h1> <p>提到的这些都是希望以小见大，让你的认知打开。当遇到任何的问题时都尝试去挑战一下AI，不断扩宽它的边界，看看它的天花板在那里，这会决定你在这个时代的天花板在哪里。</p> <p>再举个简单的例子，最近我在实践一个叫<a href="https://github.com/iFurySt/nanoLLMServe">Nano LLM Serve</a>的项目，主要是想build from scratch，从实践层面去撬动自己对于模型和Infra的认知，在这期间，我不断和ChatGPT/Codex去Co-Learning，去Co-Work，我一个毫无名气的破本科生，看公式如看天书的人，现在我可以和别人讨论Speculative Decoding，讨论Steering Vectors，讨论KV Cache Network。而且还能自己探索最新的Interaction Model，探索Diffusion Language Model。从应用出发，反推理论，和AI一起，不断打碎旧认知里为自己设下的边界，这不仅是这个时代的生存法则，更是任何时代都应该具备的生存法则。</p> <p>说到生存法则有点硬，从Just For Fun的角度来阐述或更好点，我昨天才发了一条消息给朋友：</p> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-17-daily-harness/1778989816_13-480.webp 480w,/assets/img/2026-05-17-daily-harness/1778989816_13-800.webp 800w,/assets/img/2026-05-17-daily-harness/1778989816_13-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-17-daily-harness/1778989816_13.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <div class="row mt-3"> <div class="col-sm mt-0 mb-0"> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/2026-05-17-daily-harness/1778989817_14-480.webp 480w,/assets/img/2026-05-17-daily-harness/1778989817_14-800.webp 800w,/assets/img/2026-05-17-daily-harness/1778989817_14-1400.webp 1400w," sizes="95vw" type="image/webp"/> <img src="/assets/img/2026-05-17-daily-harness/1778989817_14.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" data-zoomable="" loading="eager" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> </figure> </div> </div> </div> </div> <p>或许对于喜欢整活的人来说，有了AI放大的能力是无限的，能限制的只有碳基身体的脆弱以及24h的时间限制</p> <h1 id="尾声">尾声</h1> <p>我相信如果你能把这个harness template用起来，那肯定会开始慢慢认知到一些原来无法认知到的东西。我并不觉得这个东西一定会适合你，但是可以陆续去调整成适合自己的harness</p> <p>最近最有感触的依然还是学会与AI共舞，就像在AI时代长大的年轻一代，会顺其自然的就把AI当作工具使用，就好像我们以前使用PC、互联网、手机等东西一样。只不过因为习以为常，加上年龄增长带来的阅历提升，开始慢慢丧失了不断尝试、不断试错、不断失败最后有所得的能力了。时代其实一直在变，只是人从变开始慢慢寻求不变，短暂的一生无法拥抱太多的变化。</p>]]></content><author><name></name></author><category term="AI"/><category term="AI"/><category term="thoughts"/><summary type="html"><![CDATA[是时候写点Harness相关的了，此前零零散散在各种地方输出，都是碎片化的。加之Public后得到的反馈都是正向的，我想这个东西对于很多人来说，是个有价值的东西。牺牲自己的2小时时间，送给有缘人]]></summary></entry></feed>