<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>推理部署 on 搬砖程序员带你飞</title>
        <link>https://lpflpf.cn/tags/%E6%8E%A8%E7%90%86%E9%83%A8%E7%BD%B2/</link>
        <description>Recent content in 推理部署 on 搬砖程序员带你飞</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-CN</language>
        <managingEditor>lipengfei634626165@163.com (lpflpf)</managingEditor>
        <webMaster>lipengfei634626165@163.com (lpflpf)</webMaster>
        <lastBuildDate>Mon, 19 Oct 2026 07:40:00 +0800</lastBuildDate><atom:link href="https://lpflpf.cn/tags/%E6%8E%A8%E7%90%86%E9%83%A8%E7%BD%B2/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>默认保留思考的代价：Qwen3.8-27B 一条 64 KiB/token 的账</title>
            <link>https://lpflpf.cn/posts/qwen38-preserve-thinking-2026-10-19/</link>
            <pubDate>Mon, 19 Oct 2026 07:40:00 +0800</pubDate><author>lipengfei634626165@163.com (lpflpf)</author>
            <guid>https://lpflpf.cn/posts/qwen38-preserve-thinking-2026-10-19/</guid>
            <description>&lt;img src=&#34;https://cdn.lpflpf.cn/covers/qwen38-preserve-thinking-1019-v1-plain.png&#34; alt=&#34;Featured image of post 默认保留思考的代价：Qwen3.8-27B 一条 64 KiB/token 的账&#34; /&gt;&lt;p&gt;上周有读者在群里贴了一段 vLLM 的启动日志，问为什么 24GB 卡跑 8K 上下文还行，跑到两万 token 就开始 OOM。&lt;/p&gt;&#xA;&lt;p&gt;他压了 4-bit 权重，盘算得很清楚：27B 的模型压到四分之一，17GB 上下，24GB 卡绰绰有余。剩下的几个 GB，他以为够放上下文了。&lt;/p&gt;&#xA;&lt;p&gt;不够。而且他漏算的那部分，跟参数量的关系不大。&lt;/p&gt;&#xA;&lt;p&gt;Qwen3.8-27B 的模型卡里有一句很容易读过去的话：&lt;code&gt;preserve_thinking&lt;/code&gt; 对&lt;strong&gt;所有 workload 默认开启&lt;/strong&gt;。它的意思是，你多轮对话里每一轮模型想过的那些东西——&lt;code&gt;&amp;lt;think&amp;gt;&lt;/code&gt; 里的内容——默认会被原样塞回上下文。这个默认值同时改动了三笔预算：上下文窗口、KV cache 容量、前缀缓存命中率。三笔账各算各的，但都被同一个开关拴着。&lt;/p&gt;&#xA;&lt;p&gt;这篇文章就干三件事：把默认值到底做了什么说清楚，把 64 KiB 这个数字推一遍，然后告诉你什么情况下该关、什么情况下别关。&lt;/p&gt;&#xA;&lt;p&gt;先说结论，方便你先判断要不要往下读：&lt;strong&gt;开关本身不是重点，重点是你的回传链路是否一致&lt;/strong&gt;。全局开或全局关都能跑，最糟糕的是同一个会话里中途切换。&lt;/p&gt;&#xA;&lt;h2 id=&#34;默认值不是多想一会是把上一轮的草稿纸留在桌上&#34;&gt;默认值不是“多想一会”，是把上一轮的草稿纸留在桌上&#xA;&lt;/h2&gt;&lt;p&gt;很多人看到“保留思考”，第一反应是模型会想得更深。不会。它想多深由 &lt;code&gt;reasoning_effort&lt;/code&gt; 管，那是另一回事。&lt;code&gt;preserve_thinking&lt;/code&gt; 管的是&lt;strong&gt;过去&lt;/strong&gt;：前几轮已经想完的内容，要不要在下一轮请求里再喂一遍。&lt;/p&gt;&#xA;&lt;p&gt;官方模型卡把默认行为写得很直白，大意是 Qwen3.8 会保留所有历史消息里的 thinking 块，在整个对话中维持一条完整的推理轨迹。真正定义行为的其实是 chat template 里的一行 Jinja：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;{%- if preserve_thinking is undefined or preserve_thinking is true or loop.index0 &amp;gt; ns.last_query_index %}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    {{- &amp;#39;&amp;lt;|im_start|&amp;gt;&amp;#39; + message.role + &amp;#39;\n&amp;lt;think&amp;gt;\n&amp;#39; + reasoning_content + &amp;#39;\n&amp;lt;/think&amp;gt;\n\n&amp;#39; + content }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;{%- else %}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    {{- &amp;#39;&amp;lt;|im_start|&amp;gt;&amp;#39; + message.role + &amp;#39;\n&amp;#39; + content }}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;{%- endif %}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;三态，读一遍就够：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;不传这个参数&lt;/strong&gt; —— 走 &lt;code&gt;is undefined&lt;/code&gt; 那一支，历史 thinking 全部渲染；&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;显式传 &lt;code&gt;True&lt;/code&gt;&lt;/strong&gt; —— 同上，全部渲染；&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;传 &lt;code&gt;False&lt;/code&gt;&lt;/strong&gt; —— 只有 &lt;code&gt;loop.index0 &amp;gt; ns.last_query_index&lt;/code&gt; 的 assistant 消息（即最后一条用户消息之后产生的那条）保留思考，更早的都被剥掉。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;默认开启的准确含义是“模板默认渲染历史思考”，不是“你的历史思考一定会被渲染”。中间的差别，在客户端。&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;这里有个容易被跳过的前提：模板只负责&lt;strong&gt;渲染&lt;/strong&gt;，它不负责&lt;strong&gt;保存&lt;/strong&gt;。&lt;code&gt;reasoning_content&lt;/code&gt; 得由你的客户端存下来、放进下一轮的 &lt;code&gt;messages&lt;/code&gt; 里，模板才有东西可渲染。如果框架在解析响应时只取了 &lt;code&gt;content&lt;/code&gt;，把 &lt;code&gt;reasoning_content&lt;/code&gt; 丢了，那开不开 &lt;code&gt;preserve_thinking&lt;/code&gt; 完全等价——模板渲染出来的是空字符串，&lt;code&gt;&amp;lt;think&amp;gt;\n\n&amp;lt;/think&amp;gt;&lt;/code&gt; 里什么都没有。&lt;/p&gt;&#xA;&lt;p&gt;这个故障最麻烦的地方是它&lt;strong&gt;不报错&lt;/strong&gt;。请求成功，回答也正常，只是那条“保留的推理轨迹”从头到尾都不存在。&lt;/p&gt;&#xA;&lt;p&gt;我这边的做法是先打印一次请求体，看清楚再说别的。不用装工具，十行代码：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;m&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;messages&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;role&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;assistant&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;continue&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;has_reasoning&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;reasoning_content&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;tool&amp;#34;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;tool_calls&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;reasoning&amp;#34;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;has_reasoning&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;NO-REASONING&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;reasoning_content&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;or&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;or&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;跑第二、第三轮的时候看输出。全是 &lt;code&gt;NO-REASONING&lt;/code&gt;，说明你的调用链在某处把字段丢了，这时候调 &lt;code&gt;preserve_thinking&lt;/code&gt; 是白费功夫，先修回传。&lt;/p&gt;&#xA;&lt;p&gt;字段确实在，再往下算钱。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Qwen3.8-27B 模型卡中关于默认保留历史思考的说明，以及自托管与 Qwen Cloud 两种关闭写法的差异&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;268px&#34; data-flex-grow=&#34;111&#34; height=&#34;1860&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://cdn.lpflpf.cn/images/posts/qwen38-preserve-thinking-mc-1019.png&#34; srcset=&#34;https://lpflpf.cn/qwen38-preserve-thinking-mc-1019_9303684185767770482_hu_386ac9d412189d5f.png 800w, https://lpflpf.cn/qwen38-preserve-thinking-mc-1019_9303684185767770482_hu_2d584c87d8f81d7e.png 1600w, https://cdn.lpflpf.cn/images/posts/qwen38-preserve-thinking-mc-1019.png 2080w&#34; width=&#34;2080&#34;&gt;&#xA;&#xA;&lt;em&gt;官方模型卡原文：默认保留全部历史 thinking 块，并称其能改善 KV cache 利用率；关闭写法在自托管与托管 API 上不一致。图源：Qwen 官方模型卡，访问于 2026-10-19&lt;/em&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;每-token-64-kib省显存的是那-48-层不是这个开关&#34;&gt;每 token 64 KiB：省显存的是那 48 层，不是这个开关&#xA;&lt;/h2&gt;&lt;p&gt;模型卡说保留思考“改善 KV cache 利用率”。这句话没错，但它说的是&lt;strong&gt;命中率&lt;/strong&gt;，不是&lt;strong&gt;容量&lt;/strong&gt;。两者被太多人混成一句“开了更省显存”。&lt;/p&gt;&#xA;&lt;p&gt;先把容量这块拆开，因为它有确定答案。&lt;/p&gt;&#xA;&lt;p&gt;模型卡里的结构描述是 &lt;code&gt;16 × (3 × (Gated DeltaNet → FFN) → 1 × (Gated Attention → FFN))&lt;/code&gt;，总共 64 层。其中 48 层是 Gated DeltaNet——线性注意力，只维护一个固定大小的递归状态，&lt;strong&gt;不管你上下文多长，它的状态大小不变&lt;/strong&gt;。剩下 16 层是 Gated Attention，真正的完整注意力，KV cache 只在这 16 层产生。&lt;/p&gt;&#xA;&lt;p&gt;这 16 层的配置是：Q 头 24 个，&lt;strong&gt;KV 头 4 个&lt;/strong&gt;，head dim 256。用 GQA，所以 K 和 V 各只有 4 个头。&lt;/p&gt;&#xA;&lt;p&gt;于是每 token 的 KV 字节数可以一行算完：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;per_token = 16 × 2 × 4 × 256 × bytes&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          = BF16 65,536 B = 64 KiB&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          = FP8  32 KiB&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;2 是 K 和 V 两份，4 是 KV 头数，256 是 head dim，bytes 是每个元素的字节数（BF16 两个字节，FP8 一个字节）。乘上上下文长度：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;3.2 万 token&lt;/strong&gt;：2 GiB（FP8 1 GiB）&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;12.8 万 token&lt;/strong&gt;：8 GiB（FP8 4 GiB）&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;26.2 万 token 原生上限&lt;/strong&gt;：16 GiB（FP8 8 GiB）&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Qwen3.8-27B 的 KV cache 预算推导：64 层中只有 16 层完整注意力产生 KV，每 token 64 KiB，262K 上下文约 16 GiB&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;313px&#34; data-flex-grow=&#34;130&#34; height=&#34;1716&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://cdn.lpflpf.cn/images/posts/qwen38-kv-budget-1019.png&#34; srcset=&#34;https://lpflpf.cn/qwen38-kv-budget-1019_11451906211151960388_hu_6f38c10f6785b7dc.png 800w, https://lpflpf.cn/qwen38-kv-budget-1019_11451906211151960388_hu_418ce94347470c23.png 1600w, https://cdn.lpflpf.cn/images/posts/qwen38-kv-budget-1019.png 2240w&#34; width=&#34;2240&#34;&gt;&#xA;&#xA;&lt;em&gt;按模型卡公开配置推算的单序列 KV 占用，不含框架缓冲、对齐与投机解码开销。&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;回到开头那位读者：17GB 权重 + 26 万 token 的 16 GiB 缓存 = 33 GiB，24GB 卡根本装不下；即便他只用 8K 上下文，也还有运行时、CUDA graph、对齐填充这几项没算。所以“4-bit 权重能塞进 24GB”这句话本身没错，但它只回答了问题的一半。&lt;/p&gt;&#xA;&lt;p&gt;这里我得诚实说一句：上面是&lt;strong&gt;按公开配置做的算术&lt;/strong&gt;，不是实测。估算值不含 KV 分块对齐、Mamba 状态块、投机解码草稿和框架缓冲，真实占用一定比这个大。有第三方按整卡档位给的参考更保守——低比特量化下 8K 到 32K 上下文大概落在 19 到 21GB，跑到 26 万上下文约 34GB。方向是一致的，具体数字按你自己的框架实测。&lt;/p&gt;&#xA;&lt;p&gt;反过来，这套 3:1 结构确实削弱了一个老经验。以前我们说“上下文越长越吃显存”，在纯 Transformer 上这条经验几乎线性成立；在这里，增长的只有那 16 层，48 层线性注意力的状态是常数。这就是为什么 27B 敢标 26 万原生上下文——&lt;strong&gt;省显存的是架构，不是这个开关&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;preserve_thinking&lt;/code&gt; 在这里的作用是间接的：它决定历史 thinking 要不要占 token，而 token 直接乘进上面那个公式。开关本身不改每 token 的字节数，它改的是&lt;strong&gt;你有多少 token&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;h2 id=&#34;回传不一致的代价省下的是输入-token丢掉的是整条前缀&#34;&gt;回传不一致的代价：省下的是输入 token，丢掉的是整条前缀&#xA;&lt;/h2&gt;&lt;p&gt;现在说命中率。这一节是全文最该记住的地方。&lt;/p&gt;&#xA;&lt;p&gt;vLLM 的前缀缓存不是模糊匹配，是块级哈希链：每个块的哈希由&lt;strong&gt;父块哈希 + 本块 token 元组 + 额外哈希&lt;/strong&gt;（LoRA ID、多模态输入等）共同决定。父块一变，后面所有块的哈希全变——这不是实现细节，是链条的定义。&lt;/p&gt;&#xA;&lt;p&gt;也就是说，前缀里任何一个位置的内容不同，从那个位置往后的缓存全部作废。&lt;/p&gt;&#xA;&lt;p&gt;于是“多轮时把历史 reasoning 删掉省 token”这个流传很广的写法，在开启保留思考的服务上会撞上同一个坑：&lt;strong&gt;第一轮你回传了 thinking，第二轮你把 thinking 删了，第二轮的 prompt 前缀和第一轮跑出来的前缀不一致，缓存命中率直接归零。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;你省下了历史 thinking 的 token，丢掉了整段历史的重算。哪个更贵，取决于两件事的比例。&lt;/p&gt;&#xA;&lt;p&gt;用变量写清楚临界点，别用某家报价，因为各家差得太远：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;省下的   = reasoning_tokens × 输入单价&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;丢掉的   = 命中token数 × (输入单价 − 缓存命中单价)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;划算的条件：省下的 &amp;gt; 丢掉的&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;命中折扣越大，丢缓存越亏。现在主流厂商的缓存命中价通常是输入价的一到四分之一，这意味着一百个被复用的历史 token，价值大概相当于二十五到一百个新 token。历史 thinking 撑到几百 token 的时候，你省的那点钱，很快就被断链吃掉。&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;一个会踩的坑：删除历史 reasoning 的动作发生在“会话中途”。前半段开了、后半段关了，等于每一轮都在重算。要么全程原样回传，要么从头就不回传，别混。&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;这条机制不是 Qwen 独有的推理，行业里已经有产品把它写成硬约束。Z.AI 的保留式思考文档要求必须把完整的、未经修改的 &lt;code&gt;reasoning_content&lt;/code&gt; 回传，所有连续的 reasoning 块必须与模型最初生成的序列完全一致，明说不得重排或编辑，否则性能和缓存命中率都会受影响。Kimi 的文档则从计费角度把同一件事讲明白：&lt;code&gt;reasoning_content&lt;/code&gt; 计入 token 消耗，开启保留式思考后历史思考会持续占用上下文长度并计费。&lt;/p&gt;&#xA;&lt;p&gt;两家说法不同，指向一个事实：&lt;strong&gt;保留思考的经济性建立在“逐字回传”上&lt;/strong&gt;。省 token 和省缓存，在这里是对立的。&lt;/p&gt;&#xA;&lt;p&gt;还有一个容易被当成结论的数字要打个问号。有第三方在 Qwen3.6 上做过一次三轮 A/B：开与不开 &lt;code&gt;preserve_thinking&lt;/code&gt;，prompt 分别是 55 和 51，completion 分别是 355 和 382。看着像“保留思考能省生成”，但这是 n=1、单任务的观测，样本小到不能外推。我自己也不会拿它当结论——Qwen3.8 上任务复杂度一变，方向完全可能反过来。&lt;/p&gt;&#xA;&lt;h2 id=&#34;按轮次决策而不是给整个应用一个布尔值&#34;&gt;按轮次决策，而不是给整个应用一个布尔值&#xA;&lt;/h2&gt;&lt;p&gt;到这里三笔预算都清楚了。最后落到怎么用。&lt;/p&gt;&#xA;&lt;p&gt;先说一条硬规则，比任何精细调优都重要：&lt;strong&gt;同一个会话内，回传方式保持一致。&lt;/strong&gt; 这不是优化建议，是正确性边界。中途切换会让你既拿不到“推理连贯”的好处，又付掉断链的代价。&lt;/p&gt;&#xA;&lt;p&gt;在这条规则之下，按轮次分档是划算的：&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;首轮&lt;/strong&gt;。没有历史，开关无意义。这里真正要设的是 &lt;code&gt;reasoning_effort&lt;/code&gt; 和输出上限。顺带说一个模型卡明确提醒过的点：在多轮 agent 任务里，降低 &lt;code&gt;reasoning_effort&lt;/code&gt; 不一定减少总耗时。单轮响应可能更快，但分析不足会带来更多失败和重试，总延迟和总 token 反而上升。所以别为了省钱一上来就压到 low。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;探索轮&lt;/strong&gt;（读文件、试命令、翻目录）。历史思考的复用价值低。这一阶段模型想的是“先看看这个文件写了什么”，结论在两三轮后就没用了，留着只是占窗口。这是最值得考虑不回传的时段。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;执行轮&lt;/strong&gt;（改代码、连续工具调用）。这是保留思考真正有价值的地方。模型上一轮已经确认了“这个函数签名在哪、为什么不能动那个字段”，这些决策链如果被剥掉，它会重新推一遍，或者推出一个不一样的结论——后者更糟。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;收尾轮&lt;/strong&gt;（写总结、生成最终答复）。几乎不需要历史思考，但注意：你的总结质量依赖前面执行轮的结论，所以别在这一轮删前面的 thinking 来省窗口，那等于让模型凭记忆复述。&lt;/p&gt;&#xA;&lt;p&gt;分档策略定好了，接下来是两套环境的写法差异。这个坑不报错，只是悄悄不生效：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;自托管 / OpenAI 兼容接口&lt;/strong&gt;：&lt;code&gt;extra_body={&amp;quot;chat_template_kwargs&amp;quot;: {&amp;quot;preserve_thinking&amp;quot;: False}}&lt;/code&gt;，参数要包一层；&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Qwen Cloud&lt;/strong&gt;：直接传 &lt;code&gt;&amp;quot;preserve_thinking&amp;quot;: False&lt;/code&gt;，模型卡明确说不要包进 &lt;code&gt;chat_template_kwargs&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;同一个坑在 &lt;code&gt;enable_thinking&lt;/code&gt; 上重复出现，模型卡两处都提了。我的建议是把这两个参数写进同一份配置注释里，别让它们分散在两个模块。&lt;/p&gt;&#xA;&lt;p&gt;自托管这边，把服务端默认值显式化，比依赖“默认开启”更安全：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;vllm serve Qwen/Qwen3.8-27B &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  --default-chat-template-kwargs &lt;span class=&#34;s1&#34;&gt;&amp;#39;{&amp;#34;preserve_thinking&amp;#34;: true}&amp;#39;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  --kv-cache-dtype fp8 &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  --max-model-len &lt;span class=&#34;m&#34;&gt;32768&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;--kv-cache-dtype fp8&lt;/code&gt; 把上面那 64 KiB 砍成 32 KiB，代价是质量影响得自己测——文档没说可以无损，我也不打算替它背书。&lt;code&gt;--max-model-len&lt;/code&gt; 则是把缓存容量钉死，避免某天一个长会话把整卡吃光。&lt;/p&gt;&#xA;&lt;p&gt;代码/配置/网关改动，做之前按这份清单过一遍：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 参数位置对不对：自托管包 &lt;code&gt;chat_template_kwargs&lt;/code&gt;，Qwen Cloud 直传&lt;/li&gt;&#xA;&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 请求体里 &lt;code&gt;reasoning_content&lt;/code&gt; 字段在不在（打印一次就知道）&lt;/li&gt;&#xA;&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 同一会话内没有混用“删/不删”&lt;/li&gt;&#xA;&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 中途从别的模型切进来时，历史消息结构有没有被改写（不同厂商对回传要求不同，切模型是高危时刻）&lt;/li&gt;&#xA;&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; &lt;code&gt;reasoning_effort&lt;/code&gt; 没有被当成纯粹的省钱旋钮&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;聊到这儿，把话收回来一点。这篇的适用范围是&lt;strong&gt;多轮、带工具调用的 agent 场景&lt;/strong&gt;。单轮问答、把思考对终端用户完全隐藏的客服机器人、纯离线的批处理评测——这几类保留思考没有收益，只是多付输入 token，缓存本来也不命中，关掉干脆。&lt;/p&gt;&#xA;&lt;p&gt;还有一个二手说法我保留态度：有报告称长会话里窗口被旧计划占满后，会出现“看起来无关的空输出”。这是别人的描述，我没复现过，写在这儿只是提醒你留意这个失败形态，别当结论。&lt;/p&gt;&#xA;&lt;p&gt;真正该做的一件事，是去看一眼你自己的请求体。跑一次，打印 &lt;code&gt;messages&lt;/code&gt; 里每条 assistant 消息的字段名，看 &lt;code&gt;reasoning_content&lt;/code&gt; 到底在不在、有多长。这个数字比任何估算都准——它是你这套系统里，思考到底占了多少上下文的第一手答案。&lt;/p&gt;&#xA;&lt;p&gt;拿到这个比例之后，再决定你的探索轮要不要回传。剩下的，交给监控。&lt;/p&gt;&#xA;</description>
        </item></channel>
</rss>
