[{"content":"\n引言 过去一个月，我连续写了两篇决策模型的文章：Jev vs Laya 对比了闭源的 Jev 和开源的 Laya，Microsoft-Decision-1 详解 又加入了微软。写完之后，被问得最多的问题是：能不能自己训练一个？\n答案是可以，而且门槛比想象中低。看看第三方榜单 JevBench 就知道：截至 10 月中旬，开源权重榜上已经有 160 多个参与排名的系统，绝大多数出自社区和中小团队之手。排在前面的也不神秘：综合分第一的 H2O-Lightning-4B 是在阿里开源的 Qwen3.5-4B 上微调出来的，能力分第一的 Quyet-1.0-Large 则是 Gemma-4-31B 加一个适配器；微软的 Microsoft-Decision-1，底座同样是开源的 Qwen3.5-9B。\n这说明一件事：决策模型的核心竞争力不在模型结构，而在数据、训练目标和评测体系。 这三样东西，普通团队完全可以自己掌握。\n本文按一个真实项目的顺序，把训练一个“自己的 Jev”的全过程走一遍：\n原理：决策模型到底在学什么，为什么训练目标必须是“严格适当评分规则” 数据：去哪里找，怎么统一格式、清洗、增强和切分 算力：需要多少显存、多少 GPU 小时、多少钱 底座：怎么选基础模型 训练：从零写一套可以直接运行的 LoRA 训练脚本 评测：用哪些指标打分，怎么和 JevBench 上的模型同场比较 调优：分数不理想时，按什么思路改进 文中的脚本我都在 Qwen3.5-0.8B 上实际跑通过（CPU 冒烟测试，只用来验证代码正确性），文中引用的榜单和模型数据均来自公开资料，链接列在文末。\n一、先想清楚：决策模型到底在学什么 1. 本质是一个“条件概率” 先回顾一下决策模型的接口：调用方给出一段状态（state）和一个问题，问题的候选答案由调用方提前定义好，模型返回每个候选答案的概率。三种问题原语都可以统一成同一件事：\n原语 候选答案 本质 choice 调用方定义的 K 个选项 K 分类 noul 是 / 否 2 分类 score K 个有序等级 有序的 K 分类 所以，训练一个决策模型，就是让模型学会估计 P(选项 | 状态, 问题)。和普通的文本分类模型相比，它有两个关键区别：\n类别不是固定的：每次请求的问题和选项都不一样，模型必须读懂选项的含义，而不是记住一组固定的标签。这就是它需要“零样本泛化”能力的原因。 概率本身就是产品：下游程序会直接拿概率做 if 判断，所以概率必须“诚实”，也就是校准过的。这决定了训练目标的选择，后面会专门讲。 2. 三条技术路线 目前社区的做法大致分成三类：\n路线 代表 做法 优点 缺点 编码器 + 决策头 Laya 在每个选项前放一个 [MASK]，经过双向编码器后，取这些位置的隐状态打分 极快，4 亿参数，T4 上单题 33～40 ms 零样本能力弱，上下文短，必须针对任务微调 解码器 + 选项字母 Quyet-1.0-Large、H2O-Lightning-4B 把选项编号成 A、B、C……，模型读完提示词后，直接取下一个 token 在这些字母上的概率 复用大模型的知识和语言模型头，零样本能力强 参数量大，选项数受单 token 标签数量限制 解码器 + 决策头 jiwo、ClassOne 取解码器的隐状态，接一个单独训练的打分头 选项数和输出形式更灵活 打分头从零开始训练，更依赖数据 JevBench 的数据给出了很清楚的信号：\n几个编码器路线的系统，零样本智能分都只有个位数（Laya 1.8、modernbert-jev v2 2.6、open-jev-deberta-v3-large 2.8），和随机猜测差不多； 完全不做决策微调、直接读取 Qwen3-4B-Instruct-2507 的 logits，智能分就有 34.0； 在 Qwen3.5-4B 上做过决策微调的 H2O-Lightning-4B 达到 60.0，接近 Jev 1.13.0 的 63.6； 更大的 Gemma-4-31B 底座（Quyet-1.0-Large）达到 73.4。 结论很明确：如果目标是 Jev 那样的“零样本通才”，应该走解码器路线，大模型预训练时学到的知识是零样本能力的来源，决策微调则把这些知识“对齐”到选项概率上；如果只服务一两个固定任务，对延迟要求极高，而且有足够的标注数据，Laya 这样的编码器路线依然是性价比最高的选择。\n本文的主线是第二条路线：解码器 + 选项字母。它的推理过程是这样的：\n把状态、问题和带字母编号的选项拼成一段对话提示词，以助手回复的开头结束； 模型做一次前向计算（只有预填充，没有逐 token 生成）； 在最后一个位置（答案位）取出 A、B、C……这几个字母 token 的 logits，只在这 K 个 logits 上做 softmax，得到每个选项的概率。 H2O-Lightning-4B 的模型卡把这概括为“一次前向，一个输出 token”（one forward pass and one output token）。因为不生成文本，成本几乎只有输入 token；又因为输出空间被限定在 K 个字母上，模型不可能答出选项以外的东西。这条路线还有一个先天优势：大模型在选择题格式下给出的字母概率，本身就有不错的校准性（Kadavath 等人，2022），决策微调是在一个好的起点上继续打磨。\n3. 训练目标：为什么必须是“严格适当评分规则” 决策模型输出的概率要直接进入程序的分支判断，所以我们要的不只是“答对”，还要校准：在所有给出 80% 把握的回答里，应该恰好有大约 80% 是对的。\n第一个坑：只奖励“答对”会毁掉校准。 假设一道题的真实分布是 p（比如 70% 选 A、30% 选 B），模型报告的分布是 q。如果把模型当成策略，按 q 抽一个答案，答对得 1 分，期望奖励就是 Σ p_k · q_k，它对 q 是线性的，最优解是把全部概率押在最可能的选项上，也就是让 A 的概率等于 1。模型被训练成“永远 100% 确定”，准确率没变，概率却失去了意义。\n解决办法是用严格适当评分规则（strictly proper scoring rule）做目标。 它的定义是：只有当模型报告的分布 q 等于真实分布 p 时，期望得分才能达到最大值，说真话是唯一的最优策略（经典论述见 Gneiting 和 Raftery 2007 年的论文）。常用的有四种：\n记 q = (q_1, …, q_K) 为模型给出的分布，y 为标签（one-hot 或软标签） 对数评分（越大越好） S_log(q, y) = Σ y_k · log q_k 就是负的交叉熵 Brier 分数（越小越好） B(q, y) = Σ (q_k − y_k)² 球面评分（越大越好） S_sph(q, y) = Σ y_k · q_k / ‖q‖ 取值 0～1，梯度比对数评分温和 排序概率分（越小越好） RPS(q, y) = Σ (Q_i − Y_i)² / (K − 1) Q、Y 是累积分布，用于有序的 score 这里有一个经常被忽略的事实：交叉熵本身就是严格适当的（它就是对数评分）。那为什么用交叉熵训练出来的神经网络普遍过度自信？原因不在损失函数，而在于有限的训练数据：模型会把训练样本“背”下来，把它们的概率推向 1，到了没见过的数据上，自信程度就高于实际准确率（Guo 等人 2017 年的论文对此有系统的分析）。所以有两点要做到：\n训练时：用严格适当评分规则做目标；标签本身有不确定性时（标注员意见不一、多个教师模型投票不一致），用软标签代替 one-hot，让模型学会“诚实地不确定”；不要训练过多 epoch。 训练后：在留出的开发集上做温度缩放（temperature scaling），即 p = softmax(z / T)，按问题类型拟合一个温度 T。它不改变排序，所以不影响准确率，只修正自信程度。 温度缩放的效果往往非常明显。Laya 的出厂检查点按“问题类型 × 选项数”重新拟合温度后，平均 ECE（期望校准误差）从 0.466 降到了 0.081。而 H2O-Lightning-4B 随模型发布的温度是 choice 0.75、noul 0.8、score 0.65，都小于 1，说明它的原始输出反而偏保守，需要“锐化”。方向因模型而异，只能实测，不能拍脑袋。\n4. RLCD：Laya 公开的强化学习做法 Jev 把自己的训练方法称为 RLCD（Reinforcement Learning for Calibrated Decisions，面向校准决策的强化学习），但没有公开细节。Laya 沿用了这个名字，并公开了一个具体实现：\n奖励：三种严格适当评分的组合，Reward = S_log + 0.5 × S_sph − RPS，其中 RPS 只对有序的 score 题生效； 探索：每道题对 logits 加 8 组高斯噪声（噪声在选项维度上减去均值，因为给所有 logits 加同一个常数不会改变 softmax），噪声的标准差从 1.0 逐步衰减到 0.3； 优势：8 个样本的奖励减去组内均值、除以组内标准差，和 GRPO 的组内基线是同一个思路（GRPO 的原理可以参考我之前写的 PPO vs GRPO）； 更新：用高斯分布的对数密度做 REINFORCE 式的策略梯度，完全不使用交叉熵损失。 我的理解是：当模型直接输出完整的概率分布时，评分规则本身就是可微的，直接对它求梯度（也就是把评分规则当损失函数）和用强化学习去估计这个梯度，在期望上指向同一个方向，前者方差更小、更省事。 强化学习的形式真正不可替代的场景，是奖励无法对输出直接求导的时候，比如奖励来自多步 Agent 任务最终是否成功、用户的真实反馈，或者 Laya 处理的多轮销售对话：奖励是对话最终是否成交，需要用 TD(λ) 把这个结果分摊到每一轮。\n为了验证这个判断，我用后文训练脚本里的 reward() 函数做了两个小实验：一是把目标分布设为 [0.7, 0.2, 0.1]，直接对组合评分做梯度上升，模型输出的分布最终收敛到 [0.7, 0.2, 0.1]，说明这个组合评分确实“只奖励说真话”；二是对同一组 logits，用 RLCD 的方式（2 万组扰动，σ = 0.05）估计梯度，它和直接求导得到的梯度方向几乎完全一致（余弦相似度 0.999）。\n所以本文的训练脚本两种都支持：默认直接优化评分规则（--objective proper），也可以切换成 Laya 式的 RLCD（--objective rlcd），自己对比。\n5. 从概率到动作：阈值由代价决定 概率校准好之后，“什么时候自动执行、什么时候转人工”就不必拍脑袋了。Laya 给它的 Act / Escalate 头设计了一个代价矩阵：判对 +1，判错 −3，转人工 −0.5。自动执行的期望收益是 p × 1 + (1 − p) × (−3)，它大于 −0.5 的条件是 p \u0026gt; 0.625。一般地：\n自动执行的阈值 = (转人工的收益 − 判错的收益) / (判对的收益 − 判错的收益) = (−0.5 − (−3)) / (1 − (−3)) = 0.625 只要概率是校准的，这个阈值可以直接从业务代价算出来，单独训练一个“该不该执行”的头反而不是必需的（Laya 的 README 也坦承它的 Act 头目前几乎没有信号，建议直接用置信度做门控）。H2O-Lightning-4B 在 3474 道留出题上给出了阈值的实际效果：\n置信度阈值 转交比例 其余题目的准确率 放行但答错（占全部题目） 不设阈值 0% 约 83% 约 17% 0.8 37.7% 95.8% 2.6% 0.9 53.1% 98.5% 0.7% 这张表就是你训练完之后要为自己的模型画出来的东西。\n6. 全流程一览 把上面的原理落到工程上，一个完整的训练项目大致是这样的循环：\n① 定义题目和验收标准：指标、阈值、上线门槛，先写下来再看结果 │ ② 找数据 → 统一格式 → 去重、去污染 → 按任务切分 │ ③ 零样本基线：直接读底座模型的选项字母概率，先知道起点在哪里 │ ④ LoRA 微调：选项随机乱序 + 严格适当评分规则（或 RLCD） │ ⑤ 在开发集上拟合温度，在留出任务、公开集和私有封存集上打分 │ ⑥ 按切片做误差分析 → 补数据 → 回到 ④ │ ⑦ 合并权重，接入 /v1/systemone 接口，灰度上线，持续监控 接下来按这个顺序逐一展开。\n二、数据：从哪里找，怎么准备 数据决定了决策模型的上限。H2O-Lightning-4B 在模型卡里总结的几条经验，几乎都和数据有关：留一份从不参与训练的封存评测集、上线标准先写下来再看结果、训练数据和所有评测集做精确和近似去重、按主题和题型找短板再补数据、标签只来自人工标注或多个独立强模型的一致意见。\n1. 数据从哪里来 来源 例子 优点 注意事项 公开的人工标注数据集 意图：Banking77、CLINC150、MASSIVE（51 种语言）；主题：AG News、TNEWS；推理：BoolQ、MNLI、XNLI、OCNLI；情感与打分：SST-5、Yelp 评论星级、HelpSteer2；安全：毒性检测、提示词注入 标签可靠，数量大，覆盖面广 逐一核对许可证，不少学术数据集只允许研究用途；格式各异，需要改写成决策题 自己的业务数据 工单、邮件、审核记录、Agent 执行日志，加上人工复核的结论 和线上分布一致，价值最高 脱敏；早期标签可能有噪声，需要抽检 合成数据 用开源大模型批量生成状态和问题，再由多个独立的模型分别作答，只保留意见一致的样本，或把投票比例作为软标签 能快速补齐长尾场景和短板 只用一个模型打标，学到的就是那个模型的错误；部分商业 API 的条款限制用其输出训练竞争模型，使用前务必确认 程序生成的可验证数据 日期计算、计数、按规则判断（如“金额超过阈值且无审批则拒绝”）、游戏和模拟中的状态追踪 标签由代码算出，100% 正确，数量不受限 题目容易模板化，要在措辞和结构上多做变化 几个经验值得参考：\nLaya 坚持“零合成”：训练数据全部来自人工标注的公开数据集（客服意图、自然语言推理、内容安全、越狱与注入、评分细则打分、多轮对话结果等），理由是用模型生成的标签训练校准模型，只会把模型校准到“老师”的错误上。 H2O-Lightning-4B 的数据配比：按场景分，文档、政策与规则 37%，数字、日期与概率 22%，游戏与模拟 12%，日常语言与知识 10%，客服与运营 10%，安全与护栏 7%，代码与工具调用 2%；按题型分，choice 54%、noul 29%、score 16%；超过 99% 是英文。训练分三个阶段：先广泛覆盖，再集中强化长文档和数字推理，最后把所有类型重新混合。 任务的多样性比单个任务的样本量更重要：FLAN 系列研究发现，指令微调时覆盖的任务种类越多，模型在没见过的任务上表现越好，这一点对决策模型同样成立。在总量相同的情况下，覆盖更多不同的任务，通常比在少数几个任务上堆样本更有利于零样本泛化。 如果只是做某个垂直领域的专用模型，几千到几万条高质量标注就能有明显提升：Laya 的 typed-decisions 检查点用约 3 万道题微调，准确率从 0.362 提升到 0.766；社区有人用单张 16 GB 显卡把 Laya 微调成浏览器 Agent 的操作决策器，约 45 个候选元素中选对目标的比例从 0.10 提升到 0.66。\n2. 统一的数据格式 建议直接沿用 Jev 的请求格式，每行一道决策题：\n{\u0026#34;task\u0026#34;: \u0026#34;ticket_routing\u0026#34;, \u0026#34;state\u0026#34;: \u0026#34;Checkout returns HTTP 500 for every customer since 09:10.\u0026#34;, \u0026#34;question\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;choice\u0026#34;, \u0026#34;instructions\u0026#34;: \u0026#34;Which team should handle this ticket?\u0026#34;, \u0026#34;criteria\u0026#34;: {\u0026#34;billing\u0026#34;: \u0026#34;Charges, invoices and refunds\u0026#34;, \u0026#34;engineering\u0026#34;: \u0026#34;Bugs, errors and outages\u0026#34;, \u0026#34;support\u0026#34;: \u0026#34;How-to and account questions\u0026#34;}}, \u0026#34;answer\u0026#34;: \u0026#34;engineering\u0026#34;} {\u0026#34;task\u0026#34;: \u0026#34;refund_check\u0026#34;, \u0026#34;state\u0026#34;: {\u0026#34;subject\u0026#34;: \u0026#34;Charged twice\u0026#34;, \u0026#34;body\u0026#34;: \u0026#34;Please refund the duplicate.\u0026#34;}, \u0026#34;question\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;noul\u0026#34;, \u0026#34;instructions\u0026#34;: \u0026#34;Does the customer ask for a refund?\u0026#34;, \u0026#34;criteria\u0026#34;: {\u0026#34;true\u0026#34;: \u0026#34;yes, a refund is requested\u0026#34;, \u0026#34;false\u0026#34;: \u0026#34;no refund is requested\u0026#34;}}, \u0026#34;answer\u0026#34;: true} {\u0026#34;task\u0026#34;: \u0026#34;urgency\u0026#34;, \u0026#34;state\u0026#34;: \u0026#34;Payroll export fails and payroll runs today.\u0026#34;, \u0026#34;question\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;score\u0026#34;, \u0026#34;instructions\u0026#34;: \u0026#34;How urgent is this request?\u0026#34;, \u0026#34;criteria\u0026#34;: [\u0026#34;not urgent\u0026#34;, \u0026#34;soon\u0026#34;, \u0026#34;today\u0026#34;, \u0026#34;blocking right now\u0026#34;]}, \u0026#34;answer\u0026#34;: 2} {\u0026#34;task\u0026#34;: \u0026#34;intent_soft\u0026#34;, \u0026#34;state\u0026#34;: \u0026#34;I was billed twice and the app keeps crashing.\u0026#34;, \u0026#34;question\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;choice\u0026#34;, \u0026#34;instructions\u0026#34;: \u0026#34;What is the main topic?\u0026#34;, \u0026#34;criteria\u0026#34;: {\u0026#34;billing\u0026#34;: \u0026#34;payments\u0026#34;, \u0026#34;technical\u0026#34;: \u0026#34;bugs\u0026#34;}}, \u0026#34;target\u0026#34;: {\u0026#34;billing\u0026#34;: 0.6, \u0026#34;technical\u0026#34;: 0.4}} 几点约定：\n为了便于阅读，上面的示例做了换行，实际文件中每条记录占一行（JSONL）； answer 对 choice 是选项名，对 noul 是布尔值，对 score 是等级下标；有不确定性的样本用 target 给出软标签； task 字段记录样本来自哪个任务，后面切分和按切片分析都要用到； 这个格式和 JevBench 公开题集的格式几乎一致（它用 expected 字段存答案），后面的评测脚本可以直接读取 JevBench 的公开题。 3. 清洗与增强 清洗要做三件事：\n去重：先做规范化后的精确去重，再用 MinHash 之类的方法做近似去重，避免同一条数据换个标点就出现好几遍； 去污染：把所有评测集（自己的封存集、JevBench 公开题等）都和训练集做比对，有重叠的从训练集里删掉。JevBench 要求在公开题上训练过的模型必须主动声明，而且公开题成绩明显高于封存题的模型还会被扣分，“刷”公开题没有意义； 配平：给每个任务设上限，防止某个大数据集主导训练；同时控制题型、语言、选项数的分布。 增强的目标是让模型学会“看内容，而不是看形式”：\n增强方式 要防止的问题 每次训练都随机打乱选项顺序 位置偏差：Laya 实测，5 个文字完全相同的选项，仅仅因为位置不同，logit 就从 +1.68 一路降到 −1.91 改写问题的措辞，选项名在有语义的词和中性标签之间切换 只认关键词，或被 yes、true 之类的选项名带偏 state 在纯文本和嵌套 JSON 之间切换，字段名同义替换 只认识某一种输入格式 在 state 里加入无关字段和干扰段落 被无关内容稀释注意力 加入“无法判断”选项，并构造信息不足的样本 信息不够时也被迫硬选 构造否定问题对：同一状态分别问 Q 和“非 Q” 两个概率之和不等于 1 在 state 中插入提示词注入文本，标签保持不变 被 state 里的指令劫持 从大量选项中随机抽取“正确答案 + 若干干扰项” 选项数量单一，也解决了选项数超过字母数的问题 要注意顺序：先切分，再增强，而且增强只作用于训练集，否则同一道题的不同变体会同时出现在训练集和评测集里，评测分数就虚高了。\n4. 切分：一定要按“任务”留出 决策模型最重要的能力是泛化到没见过的问题，所以评测集要分层次：\n评测集 怎么来 回答的问题 dev（开发集） 训练任务中随机留出 5% 左右 训练是否收敛，拟合温度 held-out tasks（留出任务） 整个任务都不参与训练 零样本泛化能力，最接近 Jev 的使用方式 公开基准 JevBench 公开的 231 道题 和其他模型同场比较 lockbox（封存集） 自己写的、从不参与任何调参的题 最终上线前的一次性验收，防止对评测集过拟合 真实项目里，每种题型下面都应该有多个任务，留出整个任务之后，训练集里仍然有同题型的其他任务。\n下面是一个最小的数据准备脚本，把 Banking77（意图，choice）、BoolQ（是否题，noul）、SST-5（情感，score）和 OCNLI（中文推理，choice）四个公开数据集改写成决策题，完成去重、去污染和按任务切分：\n# prepare_data.py \u0026#34;\u0026#34;\u0026#34;Turn a few public datasets into decision records, remove duplicates and eval overlap, split by task.\u0026#34;\u0026#34;\u0026#34; import hashlib import json import random from pathlib import Path from datasets import load_dataset random.seed(13) SST5_LEVELS = [\u0026#34;very negative\u0026#34;, \u0026#34;negative\u0026#34;, \u0026#34;neutral\u0026#34;, \u0026#34;positive\u0026#34;, \u0026#34;very positive\u0026#34;] OCNLI = {\u0026#34;entailment\u0026#34;: \u0026#34;前提为真时，假设一定为真\u0026#34;, \u0026#34;neutral\u0026#34;: \u0026#34;前提为真时，假设可能为真也可能为假\u0026#34;, \u0026#34;contradiction\u0026#34;: \u0026#34;前提为真时，假设一定为假\u0026#34;} def banking77(): ds = load_dataset(\u0026#34;mteb/banking77\u0026#34;, split=\u0026#34;train\u0026#34;) intents = sorted(set(ds[\u0026#34;label_text\u0026#34;])) for row in ds: # 77 intents do not fit 26 letters: show the gold intent among 9 random distractors shown = random.sample([i for i in intents if i != row[\u0026#34;label_text\u0026#34;]], 9) + [row[\u0026#34;label_text\u0026#34;]] random.shuffle(shown) yield {\u0026#34;task\u0026#34;: \u0026#34;banking77\u0026#34;, \u0026#34;state\u0026#34;: row[\u0026#34;text\u0026#34;], \u0026#34;question\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;choice\u0026#34;, \u0026#34;instructions\u0026#34;: \u0026#34;Which banking intent does the customer\u0026#39;s message express?\u0026#34;, \u0026#34;criteria\u0026#34;: {i: i.replace(\u0026#34;_\u0026#34;, \u0026#34; \u0026#34;) for i in shown}}, \u0026#34;answer\u0026#34;: row[\u0026#34;label_text\u0026#34;]} def boolq(): for row in load_dataset(\u0026#34;google/boolq\u0026#34;, split=\u0026#34;train\u0026#34;): yield {\u0026#34;task\u0026#34;: \u0026#34;boolq\u0026#34;, \u0026#34;state\u0026#34;: row[\u0026#34;passage\u0026#34;], \u0026#34;question\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;noul\u0026#34;, \u0026#34;instructions\u0026#34;: row[\u0026#34;question\u0026#34;].capitalize() + \u0026#34;?\u0026#34;, \u0026#34;criteria\u0026#34;: {\u0026#34;true\u0026#34;: \u0026#34;yes, according to the passage\u0026#34;, \u0026#34;false\u0026#34;: \u0026#34;no, according to the passage\u0026#34;}}, \u0026#34;answer\u0026#34;: row[\u0026#34;answer\u0026#34;]} def sst5(): for row in load_dataset(\u0026#34;SetFit/sst5\u0026#34;, split=\u0026#34;train\u0026#34;): yield {\u0026#34;task\u0026#34;: \u0026#34;sst5\u0026#34;, \u0026#34;state\u0026#34;: row[\u0026#34;text\u0026#34;], \u0026#34;question\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;score\u0026#34;, \u0026#34;instructions\u0026#34;: \u0026#34;How positive is this movie review?\u0026#34;, \u0026#34;criteria\u0026#34;: SST5_LEVELS}, \u0026#34;answer\u0026#34;: row[\u0026#34;label\u0026#34;]} def ocnli(): ds = load_dataset(\u0026#34;clue/clue\u0026#34;, \u0026#34;ocnli\u0026#34;, split=\u0026#34;train\u0026#34;) names = ds.features[\u0026#34;label\u0026#34;].names for row in ds: if row[\u0026#34;label\u0026#34;] \u0026gt;= 0: # -1 marks unlabeled rows yield {\u0026#34;task\u0026#34;: \u0026#34;ocnli\u0026#34;, \u0026#34;state\u0026#34;: {\u0026#34;前提\u0026#34;: row[\u0026#34;sentence1\u0026#34;], \u0026#34;假设\u0026#34;: row[\u0026#34;sentence2\u0026#34;]}, \u0026#34;question\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;choice\u0026#34;, \u0026#34;instructions\u0026#34;: \u0026#34;假设和前提是什么关系？\u0026#34;, \u0026#34;criteria\u0026#34;: OCNLI}, \u0026#34;answer\u0026#34;: names[row[\u0026#34;label\u0026#34;]]} def key(record): text = json.dumps([record[\u0026#34;state\u0026#34;], record[\u0026#34;question\u0026#34;][\u0026#34;instructions\u0026#34;]], ensure_ascii=False) return hashlib.sha1(\u0026#34; \u0026#34;.join(text.lower().split()).encode()).hexdigest() def write(path, rows): with open(path, \u0026#34;w\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: f.writelines(json.dumps(r, ensure_ascii=False) + \u0026#34;\\n\u0026#34; for r in rows) def main(): held_out_tasks = {\u0026#34;banking77\u0026#34;} # whole tasks never trained on (choice is still covered by ocnli) banned = {key(json.loads(line)) for p in Path(\u0026#34;eval\u0026#34;).glob(\u0026#34;*.jsonl\u0026#34;) for line in open(p, encoding=\u0026#34;utf-8\u0026#34;)} # JevBench public files, your lockbox, ... seen, train, dev, held_out = set(), [], [], [] for source in (banking77, boolq, sst5, ocnli): rows = list(source()) random.shuffle(rows) for r in rows[:20000]: # cap every task so no single dataset dominates the mix k = key(r) if k in seen or k in banned: continue seen.add(k) if r[\u0026#34;task\u0026#34;] in held_out_tasks: held_out.append(r) elif random.random() \u0026lt; 0.05: dev.append(r) else: train.append(r) random.shuffle(train) Path(\u0026#34;data\u0026#34;).mkdir(exist_ok=True) write(\u0026#34;data/train.jsonl\u0026#34;, train) write(\u0026#34;data/dev.jsonl\u0026#34;, dev) write(\u0026#34;data/heldout_tasks.jsonl\u0026#34;, held_out) print(f\u0026#34;train {len(train)} dev {len(dev)} held-out tasks {len(held_out)}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() 把 JevBench 的公开题文件和自己的封存集放进 eval/ 目录后运行，就会得到 data/ 下的三个文件。我实际运行的结果是：训练集 36,044 条，开发集 1,917 条，留出任务（Banking77）9,993 条。这只是一个演示，真正的项目需要几十到几百个任务，但流程完全一样：每加一个数据源，就是再写一个这样的转换函数。\n三、GPU 资源：到底需要多少卡 1. 显存怎么估 训练时的显存主要由三部分组成：模型权重、梯度和优化器状态、激活值。前两部分可以按“每个参数占多少字节”来估：\n训练方式 每个参数约占 构成 全参微调 16 字节 bf16 权重 2 + bf16 梯度 2 + fp32 主权重 4 + AdamW 两个动量 8 LoRA 2 字节 冻结的 bf16 权重；适配器及其优化器状态通常不到 1 GB QLoRA 0.5～0.6 字节 冻结的 4-bit 权重 按这个规则，不含激活值的显存大约是：\n底座 全参微调 LoRA QLoRA Qwen3.5-0.8B 约 13 GB 约 1.6 GB 约 0.5 GB Qwen3.5-2B 约 32 GB 约 4 GB 约 1.1 GB Qwen3.5-4B 约 64 GB 约 8 GB 约 2.2 GB Qwen3.5-9B 约 144 GB 约 18 GB 约 5 GB Gemma-4-31B 约 500 GB 约 62 GB 约 17 GB 激活值和 batch 大小、序列长度成正比。开启梯度检查点后，主要剩下每一层边界的隐状态：以 Qwen3.5-4B（32 层，隐藏维度 2560）为例，一条 4K token 的样本约 32 × 4096 × 2560 × 2 字节 ≈ 0.7 GB，batch 为 4 时约 3 GB，再加上单层重算的峰值和 CUDA 自身的开销。所以 4B 模型做 LoRA，单张 24 GB 的卡通常就能跑（序列在 4K 以内、batch 不大的情况下），80 GB 的卡则可以放开 batch 和序列长度。\n这里有一个决策模型特有的大坑：语言模型头的 logits。Qwen3.5 的词表有 248,320 个 token，如果像普通 SFT 那样对每个位置都算 logits，一条 4K token 的样本光 fp32 的 logits 就要 4096 × 248320 × 4 字节 ≈ 4 GB，反向传播时还要再来一份。而决策模型只需要最后一个位置的 logits，前向时传入 logits_to_keep=1，这部分就只剩 1 MB 左右。后面的训练脚本就是这么做的。\n2. 时间怎么估 训练的计算量可以用一个经验公式估算：每个训练 token 约 6 × 参数量 次浮点运算（LoRA 加梯度检查点：前向 2N、重算 2N、反向 2N；全参微调加梯度检查点约为 8N）。举个例子，用 Qwen3.5-4B 做 LoRA，20 万道题、平均每题 600 token、训练 2 个 epoch：\n训练 token 数 ≈ 200,000 × 600 × 2 = 2.4e8 训练计算量 ≈ 6 × 4e9 × 2.4e8 ≈ 5.8e18 FLOPs H100 实际吞吐 ≈ 989 TFLOPS（bf16 稠密峰值）× 35% 利用率 ≈ 350 TFLOPS 训练时间 ≈ 5.8e18 / 3.5e14 ≈ 16,500 秒 ≈ 4.6 小时 同样的任务在 A100 上大约要 3 倍时间。实际利用率和序列长度、padding 比例、算子实现都有关系，要以自己的 profiler 数据为准。Qwen3.5 是“Gated DeltaNet 线性注意力 + 门控全注意力”的混合架构，训练前一定要装上 flash-linear-attention 和 causal-conv1d，否则会退回到纯 PyTorch 实现，慢很多（加载模型时会打印提示）。\n3. 推荐配置 场景 底座与训练方式 推荐硬件 说明 编码器路线 ModernBERT-large / mmBERT + 决策头 2 张 T4（Kaggle 免费额度）或任意 16 GB 显卡 Laya 的官方 Notebook：约 3 万道题、4 个 epoch，耗时 4～5 小时 快速验证 Qwen3.5-0.8B / 2B + LoRA 单张 24 GB（RTX 4090、L4、A10） 跑通流程、做数据实验 主力方案 Qwen3.5-4B + LoRA 1 张 H100 / A100 80 GB 性价比最高的档位，短序列时 24 GB 也能跑 进阶 Qwen3.5-9B + LoRA 1～2 张 H100 长上下文训练时用 2 张 全参微调 4B～9B 全参 8 张 H100（FSDP 或 ZeRO-3） 数据量很大、追求极致效果时 成本方面，按 H100 按需价格每小时 2～3 美元估算（JevBench 的 GPU 成本计算器默认按 H100 PCIe 每小时 2.89 美元），一次 4B 的 LoRA 训练大约 15 美元。真正花钱的是迭代：一个项目通常要跑几十轮“改数据、重训、评测”，GPU 总花费在几百美元量级。算力不是最贵的，数据和评测才是。\n部署的成本更低：H2O-Lightning-4B 的 bf16 权重约 9.1 GB，在一张 32 GB 的 RTX PRO 4500 上用 vLLM 服务，标准题单次判断的 p50 约 30 ms；按 JevBench 计算器的估算，在 RTX 5090 上自托管，每千次决策约 0.008 美元，大约是 Jev 在 JevBench 上实测成本（每千次 0.032 美元）的四分之一。\n四、基础模型怎么选 1. 选择标准 零样本底子：优先选指令版（经过对齐的对话模型），它们本来就会做选择题，起点高；Base 版需要更多数据才能追上。JevBench 专门放了几个“底座对照组”（不做任何决策微调，直接读 logits），能直接看出不同底座的起点。 语言：中文场景优先考虑 Qwen；注意 H2O-Lightning-4B 的训练数据 99% 以上是英文，直接拿来处理中文要先自测。 尺寸与延迟：决策模型的定位是几十毫秒级的快速判断，4B 左右是目前延迟、成本和能力的甜点。 上下文长度：JevBench 里有 23 道 7.7 万～8.1 万 token 的超长题，Jev 和不少模型因为超出上下文而拒答，按答错计分。Qwen3.5 原生支持 262K，但序列越长，训练的显存和时间成本上涨得越快。 许可证：Qwen3.5 全系列、Gemma 4、ModernBERT 都是 Apache 2.0，mmBERT 是 MIT，商用比较省心。 生态：训练要有 Transformers 和 PEFT 支持，部署要有 vLLM、SGLang、llama.cpp 支持；另外，选项字母必须各自是一个独立的 token（训练脚本里有断言检查）。 2. 候选清单 底座 规模 许可证 适合 社区代表作 Qwen3.5-0.8B / 2B 0.8B / 2B Apache 2.0 端侧、超低延迟、流程验证 jiwo 0.8b、ClassOne Qwen 3.5 2B Qwen3.5-4B 4B Apache 2.0 主力推荐，中文友好 H2O-Lightning-4B、Kahn1 4B Qwen3.5-9B 9B Apache 2.0 更强的零样本能力 Microsoft-Decision-1、ClassOne Qwen 3.5 9B Gemma-4-12B-it 12B Apache 2.0 多语言，能力与成本平衡 decider-12b、torchcast-decision-12b Gemma-4-31B-it / Qwen3.5-27B 27B～31B Apache 2.0 冲击榜单头部 Quyet-1.0-Large、deck-31B ModernBERT-large / mmBERT 0.3B～0.4B Apache 2.0 / MIT 编码器路线，单一任务极速推理 Laya 3. 我的建议 起步用 Qwen3.5-4B（指令版）+ LoRA：中文能力好，社区已经证明它能做到接近 Jev 的水平，单卡就能训练和部署； 先在 0.8B / 2B 上把流程跑通：数据格式、评测脚本、阈值策略都在小模型上验证完，再换大底座，可以省掉大量试错成本； 想冲击更高的能力分，再上 9B 到 31B：收益是真实的（Quyet-1.0-Large 的智能分比 H2O-Lightning-4B 高 13 分以上），但训练和推理成本也会成倍增加； 使用 Qwen3.5 时关闭思考模式：它默认会先输出思考过程，构造提示词时要传 enable_thinking=False，让助手回复的第一个 token 就是答案字母。 五、训练脚本：一步步写出来 这一节给出一套完整、可以直接运行的代码。它刻意保持精简，没有依赖任何专门的训练框架，方便读懂每一步在做什么。\n1. 准备环境 pip install torch transformers peft accelerate datasets fastapi uvicorn # GPU 机器上再装 Qwen3.5 线性注意力层的加速算子 pip install flash-linear-attention causal-conv1d 项目结构如下：\nmy-jev/ ├── prepare_data.py # 数据准备（上一节） ├── decision_format.py # 提示词渲染与一次前向打分，训练、评测、服务共用 ├── train.py # LoRA 训练 ├── evaluate.py # 打分测试与温度拟合 ├── serve.py # /v1/systemone 服务 ├── eval/ # JevBench 公开题、自己的封存集 └── data/ # prepare_data.py 的输出 2. 共用模块：把一道题变成一次前向 训练、评测和线上服务必须用完全相同的提示词格式，否则训练出来的概率到了线上就不再准确。所以先把这部分抽成一个共用模块：\n# decision_format.py \u0026#34;\u0026#34;\u0026#34;Shared by training, evaluation and serving: render a decision as a prompt and read option probabilities.\u0026#34;\u0026#34;\u0026#34; import json import string import torch from transformers import AutoModelForCausalLM, AutoTokenizer LETTERS = string.ascii_uppercase # up to 26 options per question SYSTEM = (\u0026#34;You are a decision model. Read the state and the question, \u0026#34; \u0026#34;then answer with the letter of exactly one option.\u0026#34;) def load(base, dtype=torch.bfloat16): tok = AutoTokenizer.from_pretrained(base) tok.padding_side = \u0026#34;left\u0026#34; # every row then ends at its answer slot model = AutoModelForCausalLM.from_pretrained(base, dtype=dtype) # Qwen3.5 loads text-only here ids = [tok.encode(c, add_special_tokens=False) for c in LETTERS] assert all(len(i) == 1 for i in ids), \u0026#34;every option letter must be a single token\u0026#34; return tok, model, torch.tensor([i[0] for i in ids]) def options(question): \u0026#34;\u0026#34;\u0026#34;[(key, description)] in the caller\u0026#39;s order; noul is shown as yes / no.\u0026#34;\u0026#34;\u0026#34; kind, crit = question[\u0026#34;type\u0026#34;], question.get(\u0026#34;criteria\u0026#34;) if kind == \u0026#34;noul\u0026#34;: crit = crit or {} return [(\u0026#34;yes\u0026#34;, crit.get(\u0026#34;true\u0026#34;, \u0026#34;\u0026#34;)), (\u0026#34;no\u0026#34;, crit.get(\u0026#34;false\u0026#34;, \u0026#34;\u0026#34;))] if kind == \u0026#34;score\u0026#34;: return [(str(i), desc) for i, desc in enumerate(crit)] return list(crit.items()) if isinstance(crit, dict) else [(c, \u0026#34;\u0026#34;) for c in crit] def target(record, opts): \u0026#34;\u0026#34;\u0026#34;Target distribution over opts: a soft label if the record has one, else one-hot.\u0026#34;\u0026#34;\u0026#34; keys = [k for k, _ in opts] if \u0026#34;target\u0026#34; in record: # e.g. {\u0026#34;billing\u0026#34;: 0.7, \u0026#34;technical\u0026#34;: 0.3} dist = [float(record[\u0026#34;target\u0026#34;].get(k, 0.0)) for k in keys] else: ans = record.get(\u0026#34;answer\u0026#34;, record.get(\u0026#34;expected\u0026#34;)) if record[\u0026#34;question\u0026#34;][\u0026#34;type\u0026#34;] == \u0026#34;noul\u0026#34;: ans = \u0026#34;yes\u0026#34; if ans in (True, \u0026#34;yes\u0026#34;, \u0026#34;true\u0026#34;) else \u0026#34;no\u0026#34; dist = [1.0 if k == str(ans) else 0.0 for k in keys] if sum(dist) \u0026lt;= 0: raise ValueError(f\u0026#34;answer is not one of the options: {record.get(\u0026#39;id\u0026#39;, record[\u0026#39;question\u0026#39;])}\u0026#34;) return [d / sum(dist) for d in dist] def prompt(tok, record, order, max_state_tokens=6000): \u0026#34;\u0026#34;\u0026#34;Chat prompt whose next token is the answer letter; slot s shows option order[s].\u0026#34;\u0026#34;\u0026#34; q, opts, state = record[\u0026#34;question\u0026#34;], options(record[\u0026#34;question\u0026#34;]), record[\u0026#34;state\u0026#34;] if not isinstance(state, str): state = json.dumps(state, ensure_ascii=False, indent=1) ids = tok.encode(state, add_special_tokens=False) if len(ids) \u0026gt; max_state_tokens: # cut the state, never the options or the answer slot state = tok.decode(ids[:max_state_tokens]) + \u0026#34;\\n[truncated]\u0026#34; lines = [f\u0026#34;{LETTERS[s]}. {opts[i][0]}\u0026#34; + (f\u0026#34;: {opts[i][1]}\u0026#34; if opts[i][1] else \u0026#34;\u0026#34;) for s, i in enumerate(order)] user = (f\u0026#34;\u0026lt;state\u0026gt;\\n{state}\\n\u0026lt;/state\u0026gt;\\n\\nQuestion ({q[\u0026#39;type\u0026#39;]}): {q[\u0026#39;instructions\u0026#39;]}\\n\u0026#34; \u0026#34;Options:\\n\u0026#34; + \u0026#34;\\n\u0026#34;.join(lines) + \u0026#34;\\n\\nAnswer with the letter of one option.\u0026#34;) messages = [{\u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: SYSTEM}, {\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: user}] return tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True, enable_thinking=False) def option_logits(model, tok, letter_ids, prompts, ks, device): \u0026#34;\u0026#34;\u0026#34;One forward pass for a batch: logits of the shown option letters at the answer slot.\u0026#34;\u0026#34;\u0026#34; enc = tok(prompts, return_tensors=\u0026#34;pt\u0026#34;, padding=True, add_special_tokens=False).to(device) out = model(input_ids=enc.input_ids, attention_mask=enc.attention_mask, logits_to_keep=1) z = out.logits[:, -1, letter_ids[: max(ks)].to(device)].float() mask = torch.arange(z.shape[1], device=device)[None, :] \u0026lt; torch.tensor(ks, device=device)[:, None] return z.masked_fill(~mask, -1e4), mask 几个关键点：\n左侧 padding：一个 batch 里的提示词长短不一，左侧补齐之后，每一行的最后一个位置都是答案位，配合 logits_to_keep=1，语言模型头只需要算一个位置。Qwen3.5 有线性注意力层，我专门验证过：同一道题单独计算和放进左侧补齐的 batch 里计算，logits 的最大差异只有 6×10⁻⁵； 只加载文本部分：Qwen3.5 是原生多模态模型，但用 AutoModelForCausalLM 加载时得到的是纯文本的 Qwen3_5ForCausalLM，不会带上视觉编码器，省显存，LoRA 也不会挂到视觉层上； 只在 K 个字母上做 softmax：取出 A、B、C……对应的 logits，多余的槽位填一个很小的值，模型的输出永远落在合法选项里； state 过长时只截断 state：问题、选项和答案位必须完整保留； noul 统一渲染成“yes / no”两个选项，返回的 P(yes) 就是 noul 的答案；score 的各个等级按顺序排列，便于计算 RPS； 断言每个字母都是单独的 token：换其他底座时，这一行能第一时间发现问题。在 Qwen3.5 的分词器上，A 到 Z 都是单个 token，而且在答案位后面追加一个字母，恰好多出一个 token。 3. 训练主脚本 # train.py \u0026#34;\u0026#34;\u0026#34;LoRA fine-tuning of a decision model with strictly proper scoring rules, or Laya-style RLCD.\u0026#34;\u0026#34;\u0026#34; import argparse import json import math import random import torch from accelerate import Accelerator from peft import LoraConfig, get_peft_model from torch.utils.data import DataLoader from transformers import get_cosine_schedule_with_warmup from decision_format import load, option_logits, options, prompt, target def reward(z, mask, y, ordinal): \u0026#34;\u0026#34;\u0026#34;Laya\u0026#39;s composite of strictly proper scores: log + 0.5 * spherical - RPS (RPS for score only).\u0026#34;\u0026#34;\u0026#34; logq = z.log_softmax(-1) q = logq.exp() s_log = (y * logq).sum(-1) # log score = minus cross-entropy s_sph = (y * q).sum(-1) / q.norm(dim=-1) # spherical score, bounded in [0, 1] k = mask.sum(-1).clamp(min=2) rps = ((q.cumsum(-1) - y.cumsum(-1)) ** 2).sum(-1) / (k - 1) # ranked probability score return s_log + 0.5 * s_sph - ordinal * rps def rlcd_loss(z, mask, y, ordinal, sigma, group=8): \u0026#34;\u0026#34;\u0026#34;Policy gradient over Gaussian-perturbed logits with a group-mean baseline, as Laya describes RLCD.\u0026#34;\u0026#34;\u0026#34; m = mask.float() eps = torch.randn(group, *z.shape, device=z.device) * m eps = (eps - eps.sum(-1, keepdim=True) / m.sum(-1, keepdim=True)) * m # softmax ignores a common shift sample = z.detach() + eps with torch.no_grad(): r = reward(sample, mask, y, ordinal) # [group, batch] adv = (r - r.mean(0)) / (r.std(0) + 1e-6) logp = -(((sample - z) ** 2) * m).sum(-1) / (2 * sigma**2) # Gaussian log-density up to a constant return -(adv * logp).mean() class Collate: def __init__(self, tok, max_state_tokens): self.tok, self.max_state_tokens = tok, max_state_tokens def __call__(self, records): prompts, ks, ys, ordinal = [], [], [], [] for r in records: opts = options(r[\u0026#34;question\u0026#34;]) order = list(range(len(opts))) if r[\u0026#34;question\u0026#34;][\u0026#34;type\u0026#34;] != \u0026#34;score\u0026#34;: # score levels keep their order, RPS relies on it random.shuffle(order) y = target(r, opts) prompts.append(prompt(self.tok, r, order, self.max_state_tokens)) ys.append([y[i] for i in order]) ks.append(len(opts)) ordinal.append(float(r[\u0026#34;question\u0026#34;][\u0026#34;type\u0026#34;] == \u0026#34;score\u0026#34;)) y = torch.zeros(len(records), max(ks)) for row, t in enumerate(ys): y[row, : len(t)] = torch.tensor(t) return {\u0026#34;prompts\u0026#34;: prompts, \u0026#34;ks\u0026#34;: ks, \u0026#34;y\u0026#34;: y, \u0026#34;ordinal\u0026#34;: torch.tensor(ordinal)} def main(): ap = argparse.ArgumentParser() ap.add_argument(\u0026#34;--base\u0026#34;, default=\u0026#34;Qwen/Qwen3.5-4B\u0026#34;) ap.add_argument(\u0026#34;--train\u0026#34;, default=\u0026#34;data/train.jsonl\u0026#34;) ap.add_argument(\u0026#34;--out\u0026#34;, default=\u0026#34;out/my-jev\u0026#34;) ap.add_argument(\u0026#34;--objective\u0026#34;, choices=[\u0026#34;proper\u0026#34;, \u0026#34;rlcd\u0026#34;], default=\u0026#34;proper\u0026#34;) ap.add_argument(\u0026#34;--epochs\u0026#34;, type=int, default=2) ap.add_argument(\u0026#34;--lr\u0026#34;, type=float, default=1e-4) ap.add_argument(\u0026#34;--batch-size\u0026#34;, type=int, default=4) ap.add_argument(\u0026#34;--grad-accum\u0026#34;, type=int, default=8) ap.add_argument(\u0026#34;--lora-r\u0026#34;, type=int, default=32) ap.add_argument(\u0026#34;--max-state-tokens\u0026#34;, type=int, default=3000) ap.add_argument(\u0026#34;--mixed-precision\u0026#34;, default=\u0026#34;bf16\u0026#34;) args = ap.parse_args() acc = Accelerator(mixed_precision=args.mixed_precision, gradient_accumulation_steps=args.grad_accum) tok, model, letter_ids = load(args.base) model.gradient_checkpointing_enable(gradient_checkpointing_kwargs={\u0026#34;use_reentrant\u0026#34;: False}) model = get_peft_model(model, LoraConfig( r=args.lora_r, lora_alpha=2 * args.lora_r, lora_dropout=0.05, target_modules=\u0026#34;all-linear\u0026#34;)) if acc.is_main_process: model.print_trainable_parameters() records = [json.loads(line) for line in open(args.train, encoding=\u0026#34;utf-8\u0026#34;)] loader = DataLoader(records, batch_size=args.batch_size, shuffle=True, collate_fn=Collate(tok, args.max_state_tokens)) opt = torch.optim.AdamW([p for p in model.parameters() if p.requires_grad], lr=args.lr, weight_decay=0.0) total = args.epochs * math.ceil(len(loader) / args.grad_accum) sched = get_cosine_schedule_with_warmup(opt, max(1, int(0.03 * total)), total) model, opt, loader, sched = acc.prepare(model, opt, loader, sched) step, n_batches = 0, args.epochs * len(loader) for epoch in range(args.epochs): model.train() for batch in loader: with acc.accumulate(model): z, mask = option_logits(model, tok, letter_ids, batch[\u0026#34;prompts\u0026#34;], batch[\u0026#34;ks\u0026#34;], acc.device) if args.objective == \u0026#34;rlcd\u0026#34;: sigma = 1.0 - 0.7 * step / n_batches # exploration decays from 1.0 to 0.3 loss = rlcd_loss(z, mask, batch[\u0026#34;y\u0026#34;], batch[\u0026#34;ordinal\u0026#34;], sigma) else: loss = -reward(z, mask, batch[\u0026#34;y\u0026#34;], batch[\u0026#34;ordinal\u0026#34;]).mean() acc.backward(loss) if acc.sync_gradients: acc.clip_grad_norm_(model.parameters(), 1.0) opt.step() sched.step() opt.zero_grad() step += 1 if step % 20 == 0: acc.print(f\u0026#34;epoch {epoch + 1} step {step}/{n_batches} loss {loss.item():.4f}\u0026#34;) acc.wait_for_everyone() if acc.is_main_process: acc.unwrap_model(model).save_pretrained(f\u0026#34;{args.out}/epoch{epoch + 1}\u0026#34;) acc.end_training() if __name__ == \u0026#34;__main__\u0026#34;: main() 这段代码做了这几件事：\n训练目标：reward() 就是前面介绍的 Laya 组合评分（对数评分 + 0.5 × 球面评分 − RPS），默认的 --objective proper 直接把它的相反数当损失；--objective rlcd 则按 Laya 的方式，用 8 组高斯扰动和组内基线做策略梯度，探索噪声从 1.0 衰减到 0.3。因为加了球面评分，choice 和 noul 题的损失下限是 −0.5（完全确定且答对），日志里出现负数是正常的；RLCD 的损失则是策略梯度的代理目标，数值本身没有意义，两种目标都要以评测指标为准； 选项乱序：Collate 每次取 batch 都重新打乱 choice 和 noul 的选项顺序，标签跟着一起换位，这是对抗位置偏差最简单有效的办法；score 题保持等级顺序； LoRA：target_modules=\u0026quot;all-linear\u0026quot; 会覆盖注意力、Gated DeltaNet 和 MLP 中的所有线性层。在 Qwen3.5-0.8B 上，r=8 时可训练参数约 541 万，只占 0.71%； 省显存：开启梯度检查点，只计算答案位的 logits； 单卡、多卡同一份代码：Accelerate 负责设备、混合精度和梯度累积，多卡时自动切换成数据并行。 4. 启动训练 # 单卡：有效 batch = 4 × 8 = 32 道题 python train.py --base Qwen/Qwen3.5-4B --train data/train.jsonl --out out/my-jev # 8 卡数据并行：有效 batch = 4 × 2 × 8 = 64 道题 accelerate launch --num_processes 8 train.py --base Qwen/Qwen3.5-4B \\ --train data/train.jsonl --out out/my-jev --grad-accum 2 # 对比实验：换成 Laya 式的 RLCD python train.py --base Qwen/Qwen3.5-4B --train data/train.jsonl --out out/my-jev-rlcd --objective rlcd 每个 epoch 结束会保存一份 LoRA 适配器（out/my-jev/epoch1、epoch2……），挑哪一份由下一节的评测决定。超参数可以从这里起步：\n参数 起点 说明 学习率 LoRA 1e-4，全参 1e-5 余弦衰减，3% 步数预热 LoRA rank / alpha 32 / 64 数据量大、任务多时可以加到 64 有效 batch 32～64 道题 batch-size × grad-accum × GPU 数 epoch 1～3 以留出任务的 log loss 为准，开始变差就停 state 截断长度 3000 token 长文档任务单独加大，并在训练数据里放足长样本 选项顺序 每个 batch 重新打乱 score 题保持等级顺序 我在 Qwen3.5-0.8B 上用 CPU 跑过这份脚本的冒烟测试（几十道题、两种训练目标各跑一遍），确认能正常训练和保存。真正的训练请在 GPU 上进行。\n六、打分测试：怎么知道模型好不好 1. 看哪些指标 只看准确率是远远不够的。一个合格的决策模型评测，至少要包含下面这些指标：\n指标 含义 怎么看 准确率 概率最高的选项是否正确 一定要和“永远猜最常见答案”的基线一起看 去随机准确率 (准确率 − 随机猜中率) / (1 − 随机猜中率) JevBench 的智能分就是这个思路，0 相当于瞎猜，100 为全对 对数损失 正确答案概率取对数的负值的均值 训练目标本身，越小越好，对“自信地答错”惩罚最重 Brier 分数 概率与标签的平方误差之和 越小越好，比对数损失更稳健 ECE 按置信度分成 10 个区间，置信度与准确率之差的加权平均 校准的核心指标，越接近 0 越好 RPS score 题累积分布的误差 等级题专用，差一级比差三级惩罚小 选项倒序翻转率 把选项顺序倒过来，答案发生变化的比例 位置偏差的直接度量 阈值曲线 不同置信度阈值下的转交比例和放行准确率 用来确定上线阈值，也就是前面 H2O 的那张表 延迟 单题 p50 / p95 决策模型的生命线 位置偏差绝不是小问题。学术界早有研究表明，大模型做选择题时会偏好特定的选项位置和选项字母（Zheng 等人，2023）。JevBench 也记录过一个例子：open-alternative-jev 在“判断回答是否合格”这类题上，按作者给的选项顺序（A. yes, B. no）准确率是 72%，把两个选项倒过来就只剩 21%。除了脚本里的倒序翻转率，上线前最好再补几项一致性检查：同一问题换种问法答案是否一致、问题和它的否定形式概率之和是否接近 1、state 里插入注入文本后答案是否被带偏。\n2. 评测脚本 # evaluate.py \u0026#34;\u0026#34;\u0026#34;Evaluate a decision model: accuracy, chance-corrected accuracy, calibration, order robustness, latency.\u0026#34;\u0026#34;\u0026#34; import argparse import json import statistics import time import torch from peft import PeftModel from decision_format import load, option_logits, options, prompt, target @torch.no_grad() def predict(model, tok, letter_ids, records, batch_size=8, reverse=False): \u0026#34;\u0026#34;\u0026#34;Logits over each record\u0026#39;s options, in the caller\u0026#39;s order.\u0026#34;\u0026#34;\u0026#34; result = [] for i in range(0, len(records), batch_size): chunk, orders = records[i : i + batch_size], [] for r in chunk: order = list(range(len(options(r[\u0026#34;question\u0026#34;])))) if reverse and r[\u0026#34;question\u0026#34;][\u0026#34;type\u0026#34;] != \u0026#34;score\u0026#34;: order.reverse() orders.append(order) prompts = [prompt(tok, r, o) for r, o in zip(chunk, orders)] z, _ = option_logits(model, tok, letter_ids, prompts, [len(o) for o in orders], model.device) for row, order in enumerate(orders): logits = torch.empty(len(order)) logits[order] = z[row, : len(order)].cpu() # slot s showed option order[s] result.append(logits) return result def ece(conf, correct, bins=10): groups = {} for c, ok in zip(conf, correct): groups.setdefault(min(int(c * bins), bins - 1), []).append((c, ok)) return sum(len(g) / len(conf) * abs(statistics.mean(o for _, o in g) - statistics.mean(c for c, _ in g)) for g in groups.values()) def summary(rows): conf = [p.max().item() for _, p, _ in rows] correct = [float(p.argmax() == y.argmax()) for _, p, y in rows] acc = statistics.mean(correct) chance = statistics.mean(1 / len(p) for _, p, _ in rows) out = {\u0026#34;n\u0026#34;: len(rows), \u0026#34;accuracy\u0026#34;: acc, \u0026#34;chance_corrected\u0026#34;: (acc - chance) / (1 - chance), \u0026#34;log_loss\u0026#34;: statistics.mean(-(y * p.clamp_min(1e-12).log()).sum().item() for _, p, y in rows), \u0026#34;brier\u0026#34;: statistics.mean(((p - y) ** 2).sum().item() for _, p, y in rows), \u0026#34;ece\u0026#34;: ece(conf, correct)} ordinal = [(p, y) for kind, p, y in rows if kind == \u0026#34;score\u0026#34;] if ordinal: out[\u0026#34;rps\u0026#34;] = statistics.mean((((p.cumsum(0) - y.cumsum(0)) ** 2).sum() / (len(p) - 1)).item() for p, y in ordinal) return out def metrics(records, logits, temps): rows = [] for r, z in zip(records, logits): kind = r[\u0026#34;question\u0026#34;][\u0026#34;type\u0026#34;] p = (z / temps.get(kind, 1.0)).softmax(-1) rows.append((kind, p, torch.tensor(target(r, options(r[\u0026#34;question\u0026#34;]))))) report = {\u0026#34;all\u0026#34;: summary(rows)} for kind in (\u0026#34;choice\u0026#34;, \u0026#34;noul\u0026#34;, \u0026#34;score\u0026#34;): if any(row[0] == kind for row in rows): report[kind] = summary([row for row in rows if row[0] == kind]) return report def fit_temperatures(records, logits): \u0026#34;\u0026#34;\u0026#34;One temperature per question type, chosen to minimise log loss (fit on dev data only).\u0026#34;\u0026#34;\u0026#34; grid, temps = [round(0.3 + 0.05 * i, 2) for i in range(55)], {} for kind in (\u0026#34;choice\u0026#34;, \u0026#34;noul\u0026#34;, \u0026#34;score\u0026#34;): pairs = [(z, torch.tensor(target(r, options(r[\u0026#34;question\u0026#34;])))) for r, z in zip(records, logits) if r[\u0026#34;question\u0026#34;][\u0026#34;type\u0026#34;] == kind] if pairs: temps[kind] = min(grid, key=lambda t: sum(-(y * (z / t).log_softmax(-1)).sum().item() for z, y in pairs)) return temps @torch.no_grad() def latency_ms(model, tok, letter_ids, records, n=50): times = [] for r in records[:n]: k = len(options(r[\u0026#34;question\u0026#34;])) text = prompt(tok, r, list(range(k))) if torch.cuda.is_available(): torch.cuda.synchronize() start = time.perf_counter() option_logits(model, tok, letter_ids, [text], [k], model.device) if torch.cuda.is_available(): torch.cuda.synchronize() times.append((time.perf_counter() - start) * 1000) times.sort() return {\u0026#34;p50\u0026#34;: times[len(times) // 2], \u0026#34;p95\u0026#34;: times[min(len(times) - 1, int(0.95 * len(times)))]} def main(): ap = argparse.ArgumentParser() ap.add_argument(\u0026#34;--base\u0026#34;, default=\u0026#34;Qwen/Qwen3.5-4B\u0026#34;) ap.add_argument(\u0026#34;--adapter\u0026#34;, help=\u0026#34;LoRA directory; omit it to score the raw base model\u0026#34;) ap.add_argument(\u0026#34;--data\u0026#34;, nargs=\u0026#34;+\u0026#34;, required=True) ap.add_argument(\u0026#34;--fit-temperature\u0026#34;, help=\u0026#34;fit temperatures on --data (dev only) and save them here\u0026#34;) ap.add_argument(\u0026#34;--temperatures\u0026#34;, help=\u0026#34;temperatures fitted earlier\u0026#34;) ap.add_argument(\u0026#34;--batch-size\u0026#34;, type=int, default=8) args = ap.parse_args() tok, model, letter_ids = load(args.base) if args.adapter: model = PeftModel.from_pretrained(model, args.adapter).merge_and_unload() model.to(\u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34;).eval() records = [json.loads(line) for path in args.data for line in open(path, encoding=\u0026#34;utf-8\u0026#34;)] logits = predict(model, tok, letter_ids, records, args.batch_size) temps = json.load(open(args.temperatures)) if args.temperatures else {} if args.fit_temperature: temps = fit_temperatures(records, logits) json.dump(temps, open(args.fit_temperature, \u0026#34;w\u0026#34;), indent=2) flipped = predict(model, tok, letter_ids, records, args.batch_size, reverse=True) flips = [bool(a.argmax() != b.argmax()) for r, a, b in zip(records, logits, flipped) if r[\u0026#34;question\u0026#34;][\u0026#34;type\u0026#34;] != \u0026#34;score\u0026#34;] report = metrics(records, logits, temps) report[\u0026#34;flip_rate_reversed_options\u0026#34;] = sum(flips) / max(1, len(flips)) report[\u0026#34;temperatures\u0026#34;] = temps report[\u0026#34;latency_ms\u0026#34;] = latency_ms(model, tok, letter_ids, records) print(json.dumps(report, indent=2)) if __name__ == \u0026#34;__main__\u0026#34;: main() 典型的用法分三步：\n# 1) 零样本基线：不加适配器，直接读底座模型的字母概率，先知道起点 python evaluate.py --base Qwen/Qwen3.5-4B --data data/dev.jsonl data/heldout_tasks.jsonl # 2) 在开发集上评测训练好的模型，并按题型拟合温度 python evaluate.py --base Qwen/Qwen3.5-4B --adapter out/my-jev/epoch2 \\ --data data/dev.jsonl --fit-temperature out/my-jev/temperatures.json # 3) 带上温度，在留出任务和 JevBench 公开题上打分（温度只能在开发集上拟合） python evaluate.py --base Qwen/Qwen3.5-4B --adapter out/my-jev/epoch2 \\ --temperatures out/my-jev/temperatures.json --data data/heldout_tasks.jsonl python evaluate.py --base Qwen/Qwen3.5-4B --adapter out/my-jev/epoch2 \\ --temperatures out/my-jev/temperatures.json --data eval/easy.jsonl eval/original.jsonl eval/hard.jsonl 作为示例，下面是 Qwen3.5-0.8B 原始模型（不加适配器）在 JevBench 公开的 easy 和 original 两组、共 120 道题上的结果（节选，保留三位小数）。这是在我的 Mac 上用 CPU 跑的，延迟没有参考意义，就不列出来了：\n{ \u0026#34;all\u0026#34;: {\u0026#34;n\u0026#34;: 120, \u0026#34;accuracy\u0026#34;: 0.642, \u0026#34;chance_corrected\u0026#34;: 0.488, \u0026#34;log_loss\u0026#34;: 1.326, \u0026#34;brier\u0026#34;: 0.523, \u0026#34;ece\u0026#34;: 0.108}, \u0026#34;choice\u0026#34;: {\u0026#34;n\u0026#34;: 72, \u0026#34;accuracy\u0026#34;: 0.694, \u0026#34;chance_corrected\u0026#34;: 0.614, \u0026#34;ece\u0026#34;: 0.117}, \u0026#34;noul\u0026#34;: {\u0026#34;n\u0026#34;: 36, \u0026#34;accuracy\u0026#34;: 0.583, \u0026#34;chance_corrected\u0026#34;: 0.167, \u0026#34;ece\u0026#34;: 0.220}, \u0026#34;score\u0026#34;: {\u0026#34;n\u0026#34;: 12, \u0026#34;accuracy\u0026#34;: 0.500, \u0026#34;chance_corrected\u0026#34;: 0.333, \u0026#34;rps\u0026#34;: 0.151}, \u0026#34;flip_rate_reversed_options\u0026#34;: 0.620 } 几个值得注意的地方：\nnoul 题的准确率只有 58.3%，去掉随机猜中的部分只剩 16.7%，基本是在猜； 选项倒序翻转率高达 62%：只是把选项顺序倒过来，六成以上题目的答案就变了。我还单独试了一道题：state 是“结账页面对所有客户返回 HTTP 500”，问题是“是否有生产系统宕机”，选项按 yes、no 排列时 P(yes) 是 0.18，换成 no、yes 排列就变成了 0.45。小模型的位置偏差比想象中严重得多，这正是训练时要不断打乱选项、评测时一定要报告这个指标的原因； 作为对照，H2O-Lightning-4B 在同样这 120 道题上答对了 119 道。两者规模不同，差距不能全部归功于微调，但足以说明决策微调要补的课有多少。 3. 和 JevBench 上的模型同场比较 JevBench 的 GitHub 仓库公开了 231 道题（datasets/public/ 下的 easy 48 道、original 72 道、hard 111 道），格式和本文的数据格式一致，evaluate.py 可以直接读取。作为参照，H2O-Lightning-4B 在这 231 道题上答对 205 道（88.7%）：easy 48/48，original 71/72，hard 86/111。\n不过要注意三点：\n公开题只是参考：榜单分数基于每期 1500 道题（1200 道不公开的封存题加 300 道公开题，智能分里两部分各占一半），还做了去随机、分难度加权和公开 / 封存差距惩罚，公开题上的准确率不能直接换算成榜单分数； 绝不能拿公开题训练：JevBench 要求在公开题上训练过的模型必须主动声明，prepare_data.py 里的去污染就是为此准备的； 想上榜就提交：在 JevBench 网站提交模型后，会由维护者在他们自己的 GPU 上跑完整的封存题。 如果想完全按 JevBench 官方的方式测，可以先把模型包装成和 Jev 一样的 HTTP 接口，再用官方的评测工具去调用它。\n4. 服务化：兼容 Jev 的 /v1/systemone # serve.py \u0026#34;\u0026#34;\u0026#34;A minimal /v1/systemone server (Jev wire format) in front of the fine-tuned model.\u0026#34;\u0026#34;\u0026#34; import json import os import torch from fastapi import FastAPI, HTTPException from peft import PeftModel from decision_format import LETTERS, load, option_logits, options, prompt BASE = os.environ.get(\u0026#34;BASE_MODEL\u0026#34;, \u0026#34;Qwen/Qwen3.5-4B\u0026#34;) ADAPTER = os.environ.get(\u0026#34;ADAPTER\u0026#34;, \u0026#34;out/my-jev/epoch2\u0026#34;) TEMPS = json.load(open(os.environ[\u0026#34;TEMPERATURES\u0026#34;])) if os.environ.get(\u0026#34;TEMPERATURES\u0026#34;) else {} tok, model, letter_ids = load(BASE) model = PeftModel.from_pretrained(model, ADAPTER).merge_and_unload() model.to(\u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34;).eval() app = FastAPI() @app.post(\u0026#34;/v1/systemone\u0026#34;) @torch.no_grad() def systemone(body: dict): questions = body.get(\u0026#34;questions\u0026#34;) if not isinstance(questions, dict) or not 0 \u0026lt; len(questions) \u0026lt;= 32: raise HTTPException(422, \u0026#34;questions must be an object with 1 to 32 entries\u0026#34;) names, prompts, ks = list(questions), [], [] for name in names: q = questions[name] try: k = len(options(q)) ok = q[\u0026#34;type\u0026#34;] in (\u0026#34;choice\u0026#34;, \u0026#34;noul\u0026#34;, \u0026#34;score\u0026#34;) and isinstance(q[\u0026#34;instructions\u0026#34;], str) except (KeyError, TypeError, AttributeError): ok = False if not ok or not 2 \u0026lt;= k \u0026lt;= len(LETTERS): raise HTTPException(422, f\u0026#34;{name}: malformed question or unsupported option count\u0026#34;) prompts.append(prompt(tok, {\u0026#34;state\u0026#34;: body.get(\u0026#34;state\u0026#34;, \u0026#34;\u0026#34;), \u0026#34;question\u0026#34;: q}, list(range(k)))) ks.append(k) z, _ = option_logits(model, tok, letter_ids, prompts, ks, model.device) answers = {} for row, name in enumerate(names): q, k = questions[name], ks[row] p = (z[row, :k] / TEMPS.get(q[\u0026#34;type\u0026#34;], 1.0)).softmax(-1).tolist() if q[\u0026#34;type\u0026#34;] == \u0026#34;noul\u0026#34;: answers[name] = {\u0026#34;type\u0026#34;: \u0026#34;noul\u0026#34;, \u0026#34;noul\u0026#34;: p[0]} # P(yes) elif q[\u0026#34;type\u0026#34;] == \u0026#34;score\u0026#34;: answers[name] = {\u0026#34;type\u0026#34;: \u0026#34;score\u0026#34;, \u0026#34;score\u0026#34;: sum(i * x for i, x in enumerate(p)), \u0026#34;probabilities\u0026#34;: {str(i): x for i, x in enumerate(p)}} else: probs = {key: x for (key, _), x in zip(options(q), p)} best = max(probs, key=probs.get) answers[name] = {\u0026#34;type\u0026#34;: \u0026#34;choice\u0026#34;, \u0026#34;choice\u0026#34;: best, \u0026#34;probabilities\u0026#34;: probs, \u0026#34;confidence\u0026#34;: (k * probs[best] - 1) / (k - 1)} used = sum(len(ids) for ids in tok(prompts, add_special_tokens=False).input_ids) return {\u0026#34;answers\u0026#34;: answers, \u0026#34;usage\u0026#34;: {\u0026#34;input_tokens\u0026#34;: used, \u0026#34;output_tokens\u0026#34;: 0}} 启动服务，然后用 curl 验证一下：\nADAPTER=out/my-jev/epoch2 TEMPERATURES=out/my-jev/temperatures.json \\ uvicorn serve:app --host 127.0.0.1 --port 8741 curl -s http://127.0.0.1:8741/v1/systemone -H \u0026#39;Content-Type: application/json\u0026#39; -d \u0026#39;{ \u0026#34;state\u0026#34;: \u0026#34;Checkout returns HTTP 500 for every customer since 09:10.\u0026#34;, \u0026#34;questions\u0026#34;: { \u0026#34;team\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;choice\u0026#34;, \u0026#34;instructions\u0026#34;: \u0026#34;Which team should handle this?\u0026#34;, \u0026#34;criteria\u0026#34;: {\u0026#34;billing\u0026#34;: \u0026#34;charges and refunds\u0026#34;, \u0026#34;engineering\u0026#34;: \u0026#34;bugs and outages\u0026#34;}}, \u0026#34;outage\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;noul\u0026#34;, \u0026#34;instructions\u0026#34;: \u0026#34;Is a production system down?\u0026#34;} } }\u0026#39; 返回结构沿用 Jev 的格式：每个答案都带 type，choice 返回 choice、probabilities 和按 Jev 公式 (K·p_max − 1)/(K − 1) 计算的 confidence，score 返回期望等级和以等级下标为键的概率，noul 直接返回 P(yes)，usage 里只有输入 token。这正是 JevBench 官方评测工具（typesafe 适配器）解析的字段，所以可以直接拿它来测，已有的 Jev 客户端通常也只需要改一下 baseUrl：\ngit clone https://github.com/fstandhartinger/jevbench \u0026amp;\u0026amp; cd jevbench python -m jevbench.cli run \\ --tasks datasets/public/easy.jsonl,datasets/public/original.jsonl,datasets/public/hard.jsonl \\ --adapter typesafe --endpoint http://127.0.0.1:8741 --key-env \u0026#39;\u0026#39; --model my-jev \\ --cost-basis self_hosted_gpu --reserve-usd 0 --delay-s 0 \\ --results RUN/results.jsonl --raw-dir RUN/raw --ledger RUN/ledger.jsonl --manifest RUN/manifest.json # 汇总成绩 python -m jevbench.cli summarize \\ --tasks datasets/public/easy.jsonl,datasets/public/original.jsonl,datasets/public/hard.jsonl \\ --results RUN/results.jsonl --public-export RUN/summary.json 这个服务只是为了评测和演示：它一次处理一个请求，没有认证。正式上线时，推荐像 H2O-Lightning-4B 那样把合并后的模型交给 vLLM（连续批处理、前缀缓存），前面放一个很薄的协议转换层，并加上认证、限流和请求大小限制，不要直接暴露在公网上。\n七、调整思路：分数不理想怎么办 1. 先有基线，再按切片找问题 动手调参之前，先确认两件事：一是零样本基线（不微调、直接读底座的字母概率）是多少，二是“永远猜最常见答案”能拿多少分。训练后的模型必须和这两个数字比，否则很容易被虚高的准确率骗到。\n然后按切片拆开看：题型、任务、语言、选项数、state 长度、置信度区间。总分掩盖的问题，往往在切片里一目了然。H2O 把这叫作“找短板”（gap hunting）：按主题和题型和其他系统对比，跑对抗性题集，然后针对性地补充经过验证的新数据。\n2. 症状与对策 症状 可能的原因 对策 训练任务上很好，留出任务上很差 过拟合了训练任务的措辞和模板 增加任务种类和问法；减少 epoch、降低学习率；用留出任务的 log loss 做早停 准确率还行，但 ECE 很高 训练过头导致过度自信，或标签有噪声 温度缩放；减少 epoch；有争议的样本改用软标签 选项倒序后答案改变 位置偏差 训练时每步打乱选项；推理时用多种顺序取平均（Laya 实测可把顺序导致的答案变化从 16.3% 降到 6.1%）；或改用选项之间互不可见的并行布局 noul 总是偏向“是”或“否” 标签词偏置、正反样本不均衡 平衡正反样本；构造“问题 + 否定问题”成对的数据；检查 criteria 的措辞是否带有倾向 score 误差大 有序数据少，等级描述模糊 增加有序打分数据，提高 RPS 的权重；每个等级写清楚判定标准 选项一多就不准 选项超过字母数，或选项彼此相似 先用 embedding 或分组淘汰缩小候选（Laya 的分组淘汰把 BANKING77 从 0.430 提到 0.610）；或拆成“粗分类 + 细分类”两级问题 长文档准确率下降 训练时没见过长样本，或被截断 加一个专门的长文档训练阶段（H2O 的第二阶段就是这么做的）；检查截断比例，调大 max_state_tokens 计数、日期、算术题出错 语言模型的固有短板 补充程序生成的可验证数据；更根本的办法是把计算交给代码，只把语义判断留给模型 中文等非英语表现差 训练数据以英文为主 补充对应语言的数据；选多语言能力更强的底座；分语言看指标 微调后零样本能力反而下降 灾难性遗忘 降低学习率和 LoRA rank；混入少量通用指令数据；对比微调前后在留出任务上的分数 延迟太高 底座太大，提示词太长 换更小的底座；精简 state；开启前缀缓存；做 FP8 量化（H2O 实测 FP8 版本只少答对 3 道题，差异在统计上不显著） 3. 让迭代形成闭环 最后是几条流程上的建议，它们比任何一个超参数都重要：\n上线标准先写下来：比如“封存集上的去随机准确率不低于上一版，温度缩放后 ECE 不高于 0.05，选项倒序翻转率不高于 5%，p95 延迟不超过 100 ms”。先定标准再看结果，才能避免“看着结果改标准”。 封存集只在发版时用一次：日常调参只看开发集和留出任务，封存集一旦被反复使用，它就变成了另一个开发集。 每次只改一个变量：数据、超参数、底座同时改，就无法判断哪一个起了作用。 固定版本，持续监控：底座或数据变化之后，温度和阈值都要重新标定；上线后持续记录概率分布并抽样复核，一旦分布漂移就回到飞轮的起点。 总结 把全文浓缩成几句话：\n路线：想要 Jev 那样的零样本通才，走“解码器 + 选项字母”，从 Qwen3.5-4B + LoRA 起步；只服务固定任务、追求极致速度，编码器路线依然划算。 目标：用严格适当评分规则训练，再用温度缩放校准。RLCD 是其中一种实现方式，而不是魔法。 数据：任务的多样性、标签的质量、严格的去污染和按任务切分，比模型结构重要得多。 算力：4B 模型的一次 LoRA 训练，单张 H100 几个小时、十几美元，钱主要花在一轮又一轮的迭代上。 评测：准确率之外还要看校准、位置偏差、阈值曲线和延迟；用 JevBench 公开题对标，用自己的封存集验收。 Jev 定义了“决策模型”这个品类，而短短一个月里，开源社区已经复刻出了上百个替代品，其中最好的一批在榜单上已经和 Jev 不相上下。训练一个自己的决策模型，已经从“能不能做到”变成了“值不值得做”。如果你有自己的数据、有合规要求，或者对延迟和成本格外敏感，答案多半是值得的。\n参考资料 JevBench：开源权重决策模型榜单 JevBench：评测方法说明 JevBench：评测工具与公开题集（GitHub） H2O-Lightning-4B 模型卡 Laya GitHub 仓库 Nandakishor M：I Built Non-Autoregressive Decision Models a Year Ago. Then a Frontier Lab Called It a \u0026ldquo;Breakthrough\u0026rdquo;. Qwen3.5-4B 模型卡 Gneiting \u0026amp; Raftery：Strictly Proper Scoring Rules, Prediction, and Estimation（2007） Guo et al.：On Calibration of Modern Neural Networks（2017） Kadavath et al.：Language Models (Mostly) Know What They Know（2022） Zheng et al.：Large Language Models Are Not Robust Multiple Choice Selectors（2023） Wei et al.：Finetuned Language Models Are Zero-Shot Learners（2021） Hu et al.：LoRA: Low-Rank Adaptation of Large Language Models（2021） Jev vs Laya：闭源与开源 System One 决策模型的对比与选型 Microsoft-Decision-1 详解：微软入局决策模型，与 Jev、Laya 的三方对比 玩转大语言模型：大语言模型（LLM）微调主流方式和使用场景全面解析对比 PPO vs GRPO：两种主流 LLM 强化学习范式的对比与选择 ","permalink":"https://wilsonwu.me/zh/blog/2026/train-your-own-jev/","summary":"\u003cp\u003e\u003cimg alt=\"训练一个自己的 Jev\" loading=\"lazy\" src=\"/zh/blog/2026/train-your-own-jev/1-train-your-own-jev.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"引言\"\u003e引言\u003c/h2\u003e\n\u003cp\u003e过去一个月，我连续写了两篇决策模型的文章：\u003ca href=\"/zh/blog/2026/jev-vs-laya/\"\u003eJev vs Laya\u003c/a\u003e 对比了闭源的 Jev 和开源的 Laya，\u003ca href=\"/zh/blog/2026/microsoft-decision-1/\"\u003eMicrosoft-Decision-1 详解\u003c/a\u003e 又加入了微软。写完之后，被问得最多的问题是：\u003cstrong\u003e能不能自己训练一个？\u003c/strong\u003e\u003c/p\u003e","title":"训练一个自己的 Jev：决策模型的原理、数据、算力、训练与评测全流程"},{"content":"\n引言 上个月我写了一篇 Jev vs Laya，对比了 TypeSafe AI 的闭源决策模型 Jev 和社区开源的 Laya。那时“决策模型”还是一个刚刚诞生的新品类：9 月 15 日 Jev 发布，三天后 Laya 开源。\n仅仅三周之后，这个赛道就挤满了玩家：10 月 6 日，OpenAI 开放了基于 GPT-6 Luna 的 Decisions API；Liquid AI、Inception 等公司也陆续推出了自己的决策 API；第三方榜单 JevBench 上参与排名的开源权重系统已经超过 140 个。10 月 9 日，微软也正式入局，发布了 Microsoft-Decision-1。\n和其他入局者相比，Microsoft-Decision-1 有几个特别值得关注的地方：\n出自大厂，底座却是开源模型：它基于阿里开源的 Qwen3.5-9B 后训练而来，微软还表示后续会迁移到自家的 MAI 系列和 OpenAI 的模型上； 价格与 Jev 完全一致：输入每百万 token 0.042 美元，输出免费； 接口几乎与 Jev 兼容：同样是 state + questions，同样是 noul / choice / score 三种问题类型； 官方数据很亮眼：在 36 项基准、近 15 万道题的对比中平均准确率最高，中位延迟比 GPT-6 Sol 快约 35 倍。 但官方数据只是故事的一半。微软发布的第二天，第三方榜单 JevBench 就完成了独立实测，结论并不完全一致。本文将从以下几个方面展开：\nMicrosoft-Decision-1 是什么，又是怎么做出来的 它与 Jev、Laya 的技术路线和接口有什么不同 官方基准与第三方实测应该怎么看 Microsoft-Decision-1 的优势与劣势 适用场景、选型建议与代码示例 一分钟回顾：什么是决策模型 决策模型（System One Model）不聊天，也不写文章，它只做一件事：接收一段状态（state）和一组预先定义好的问题（questions），直接返回带概率的类型化决策。三家都支持同样的三种问题原语：\n原语 回答的问题 返回内容 noul 是不是？ “是”的概率（0～1） choice 选哪一个？ 选中的选项，以及每个选项的概率 score 有多少？ 有序等级上的分数，以及每个等级的概率 由于答案空间被提前限定，模型不会返回格式错误或凭空编造的选项；校准过的概率则可以直接写进程序的 if 语句：高置信度自动执行，中置信度交给更强的模型复核，低置信度转人工。在一个 AI 系统里，比较合理的分工是：代码负责骨架，决策模型负责快速判断，大模型负责深度思考。\n更详细的概念介绍和置信度分流的思路，可以参考上一篇，这里不再展开。\nMicrosoft-Decision-1 是什么 1. 基本信息 2026 年 10 月 9 日，微软首席技术官办公室（Office of the CTO）软件工程副总裁 Achint Srivastava 在微软的技术博客 Command Line 上发布了 Microsoft-Decision-1。文章开头是这样定义决策模型的：\nUnlike LLMs, which are designed to generate text or reason through complex problems, decision models are purpose-built to deliver structured outputs that software can immediately act on.\n与生成文本、推理复杂问题的大模型不同，决策模型专门用来输出软件可以立即据此行动的结构化结果。\n微软给它的定位是一个“快速决策打分”（fast decision-scoring）模型，面向路由、分类、优先级排序、结果校验和工作流控制。主要信息如下：\n项目 说明 发布时间 2026 年 10 月 9 日 发布方 微软首席技术官办公室（Office of the CTO） 底座模型 Qwen3.5-9B（后训练），计划迁移到 MAI 和 OpenAI 的模型 模型类型 决策、文本分类、零样本分类 问题类型 noul（是 / 否）、choice（多选一）、score（有序打分），以及基于评分细则（rubric）评估 AI 回答和 Agent 行为 输入 仅支持文本（字符串或 JSON），单次请求最多 32K token，不支持图像、音频和视频 输出 选中项和每个候选项的校准概率，不生成解释或理由 获取方式 Microsoft Foundry 模型目录（模型名 Microsoft-Decision-1，版本 1），也可以通过 OpenRouter 调用 部署类型 GlobalStandard，部分区域支持 DataZoneStandard 调用端点 {Foundry 资源端点}/providers/microsoft/v1/systemone 认证方式 Microsoft Entra ID（推荐）或 API Key 价格 输入 0.042 美元 / 百万 token，输出免费 按这个价格，如果每次判断平均消耗 1000 个输入 token，100 万次判断的费用约为 42 美元，和 Jev 完全相同。\n2. 它是怎么做出来的 微软公开的技术细节不多，但有几个关键点：\n在开源底座上后训练：微软在 Qwen3.5-9B 上做了后训练（post-training），让它学会“单次前向完成决策打分”（fast, single-pass decision scoring）。给定一组固定的候选答案，模型直接为每个选项输出一个校准过的概率，而不是逐 token 生成文字。 底座可以替换：微软明确表示，很快会把它迁移（rebase）到其他底座上，包括微软自研的 MAI 系列和 OpenAI 的模型。 没有公开的部分：训练数据、后训练方法和打分机制的具体实现都没有公开，权重也没有开放，只能以托管 API 的形式使用。 微软在博客里把打造一个可靠决策模型的难点归纳为五个方面，并给出了相应的自测结果：\n挑战 为什么重要 微软的自测结果 速度 Agent 工作流中的决策往往是串行的，每个决策多 100 毫秒，20 个串行决策就会多出 2 秒 P50 延迟约为 GPT-6 Sol 的 1/35 泛化 模型很容易在单个基准上过拟合，必须验证它在没见过的任务上是否同样可靠 在数十个对训练保密的基准（路由、排序、长上下文、多语言、分布外任务、推理、安全）上评测；与 JevBench 榜单上的头部模型在另外 36 个公开和私有基准上对比，平均准确率最高 鲁棒性 生产环境里，state 和指令会被改写，选项会被重排，字段名会变化，这些等价变化不应该改变决策 对同一请求做 8 种扰动，平均决策翻转率为 1.3%；改写选项描述、颠倒或打乱选项顺序时，翻转率为 0 校准 概率本身就是 API 的一部分，应用要依据它决定是执行、推迟还是送审，“90% 的把握”应该在代表性样本上 10 次对 9 次 36 项基准上的校准分为 92.2（满分 100），在参评的 7 个模型中排第三，比 Jev 低 1.5 分 安全 既要识别有害请求，又不能误伤正常请求 在 11 个基准、5250 条请求（有害内容、越狱攻击、提示词注入）上测试，能够拒绝有害行为，同时保持较高的可用性 3. 微软内部的四个试点 微软还公开了几个内部试用的案例（均为微软自测）：\n团队 / 场景 任务 结果 Xbox Research：数据标注 把来自问卷、Steam 和 Twitter/X 的 1 万多条开放式反馈，归入研究员预先定义好的主题 质量与 GPT-6 Sol 相当，速度快 14 倍以上，成本低约 200 倍 Copilot：质量控制 评估聊天回答和 Agent 回答的质量 与 GPT-5.6 Luna 表现相当，速度快约 100 倍 事故响应 值班工程师处理线上事故时，从日志、工单、通话和消息中检索相关知识 比基于大模型的方案更准、更快 Microsoft Discovery：科学研究 Agent 按评分细则评估上一轮实验的结果，调整方案并反复迭代，直到完成目标 打分一致性是大模型打分的 46 倍，速度是 3 倍，自适应重规划整体提速近 4 倍 其中 Xbox 的三个数据集，微软给出了更详细的对比数据：\n数据集 样本量 平均每条耗时（GPT-6 Sol → Decision-1） 跑完一遍的成本（GPT-6 Sol → Decision-1） 某赛车游戏的评论 753 条 2.8 秒 → 159 毫秒（约 18 倍） 1.83 美元 → 0.009 美元（约 213 倍） 某第一人称射击游戏的评论 341 条 2.6 秒 → 188 毫秒（约 14 倍） 1.03 美元 → 0.005 美元（约 212 倍） 某工作室直播的帖子 1030 条 2.6 秒 → 143 毫秒（约 18 倍） 2.92 美元 → 0.013 美元（约 223 倍） 按 100 万条文本折算，GPT-6 Sol 约需 2434 美元，Microsoft-Decision-1 约需 11 美元（微软注明成本为估算值）。两者读取的是同一份任务说明，输入 token 数相同，差距来自单价，以及 GPT-6 Sol 还需要为输出付费。\n4. 适合哪些场景 微软在博客里列出了 16 个推荐场景，大致可以归为四类：\n类别 场景 Agent 控制 评估 Agent 提出的下一步，决定继续、停止、重试，还是交给其他模型、工具或人；按照 Skill 中的规则选择下一步动作，而不必反复处理一长串指令；在电脑和界面操作中选择下一个 UI 动作；在机器人场景中从预定义的动作里做选择 路由与分类 模型路由、意图识别、事故分级与派单、内容分类与过滤、数据标注 评估与校验 AI 评审（按标准决定接受、修改还是拒绝一个回答）、数据校验、代码扫描、安全风险筛查（放行、拦截或升级） 排序与推荐 推荐、搜索相关性打分，以及在科学研究中筛选假设、化合物和实验 其中最值得关注的是 Agent 控制。今天的 Agent 每走一步都要做大量“小判断”：上一步成功了吗？要不要重试？这个工具调用会不会动到生产数据？如果这些判断都交给大模型，延迟和成本会随着步数线性累积。用一个百毫秒级的决策模型给 Agent 加一道“闸门”，是这类模型最有想象空间的用法。微软还演示了一个电脑操作（computer use）的例子：在完成“买一个背包”的任务时，由 Microsoft-Decision-1 选择下一步界面操作，完成速度明显快于 GPT-6 Sol。\n另一个值得一提的场景是模型路由：根据请求的质量、成本和延迟要求，选择最合适的模型来处理。我之前写过一篇 让模型自己选模型：Embedding 驱动的 LLM 智能路由机制，决策模型提供了另一种思路：不再依赖向量相似度，而是直接把“该交给哪个模型”当成一道 choice 题来回答。\n三条技术路线 把三个模型放在一起，会发现它们代表了三条截然不同的技术路线：\n维度 Jev Laya Microsoft-Decision-1 模型结构 未公开（社区猜测接近 encoder 类模型） 双向编码器（ModernBERT / mmBERT）+ 决策头 解码器大模型 Qwen3.5-9B 后训练 参数规模 未公开 322M～421M 约 9B 训练方法 RLCD（面向校准决策的强化学习，细节未公开） RLCD（严格适当评分规则 + 组基线策略梯度，已公开） 面向单次决策打分的后训练（细节未公开） 推理方式 自研的并行采样器 每个选项前放一个 [MASK]，单次前向打分 单次前向打分 权重 闭源 Apache 2.0 开源 不开放（底座开源） 有几点值得展开说说：\n规模相差一个数量级以上。Laya 用 4 亿参数的编码器换取极致的速度和本地部署能力；Microsoft-Decision-1 用 90 亿参数的解码器换取更强的零样本泛化能力，代价是对算力的要求高得多，而且目前只能以云端托管的方式使用。 底座正在变成可替换的零件。从 JevBench 榜单看，排名靠前的开源决策模型大多也是在 Qwen 或 Gemma 上后训练出来的，比如基于 Qwen3.5-4B 的 H2O-Lightning-4B、基于 Gemma-4-31B 的 Quyet-1.0-Large。微软直接选用阿里的开源模型做底座，又公开表示后续会换成 MAI 或 OpenAI 的模型，说明这类模型的竞争力主要来自后训练数据、评测体系和校准方法，而不是底座本身。 “单次前向”已经是共识。无论是 Laya 的编码器还是 Microsoft-Decision-1 的解码器，核心思路都一样：不生成文本，一次前向计算就给出所有候选项的概率。这也是决策模型能比通用大模型快一到两个数量级的根本原因。 同一套接口，三种后端 对开发者来说，一个非常实用的变化是：三者的请求格式几乎一致。\nJev 的官方 API 是 POST https://api.typesafe.ai/v1/systemone； Microsoft-Decision-1 的端点是 Foundry 资源下的 /providers/microsoft/v1/systemone，微软文档也直接说明，它的请求模式与 System One API 类似； Laya 也提供了 laya-serve，在本地以同样的 POST /v1/systemone 协议对外服务，返回结构与 Jev 一致，已有的 Jev 客户端只需要修改 baseUrl。 三者在细节上的差异如下：\n项目 Microsoft-Decision-1 Jev Laya（laya-serve） 端点 {Foundry 资源端点}/providers/microsoft/v1/systemone https://api.typesafe.ai/v1/systemone http://{主机}:8000/v1/systemone 认证 Entra ID 的 Bearer Token，或 api-key 请求头 Authorization: Bearer {API Key} 默认无认证，设置 LAYA_API_KEY 后使用 Bearer Token model 字段 部署名称（deployment name） 模型版本或别名，如 jev-1.13.0 检查点名称，省略时由 Router 自动选择 单次请求上限 32K token 64K token，其中 state 加最长的单个问题不超过 32K 默认 512 / 1024 token，多语言检查点最高 8192 批量 同一个 state 可以带多个问题 同左 同左，另有 /v1/systemone/batch，一次最多 64 个 state 这意味着 state + questions 风格的接口正在成为决策模型的事实标准，就像当年 OpenAI 的 Chat Completions 接口之于大模型。微软把价格定得和 Jev 一模一样、接口也几乎一致，很难不让人认为这是在刻意降低 Jev 用户的迁移门槛。对用户来说，切换成本越来越低；对厂商来说，护城河只能来自效果、延迟、单次成本、合规能力和生态。\n核心差异一览 维度 Microsoft-Decision-1 Jev Laya 发布方 微软 TypeSafe AI（创业公司） Convai Innovations（独立研究者） 发布时间 2026 年 10 月 9 日 2026 年 9 月 15 日 2026 年 9 月 18 日 开源 权重不开放 闭源 Apache 2.0 部署 Azure 托管（Foundry），也可经 OpenRouter 调用 TypeSafe 云端（美国西海岸） 本地、私有云或离线环境 架构与规模 Qwen3.5-9B 后训练 未公开 编码器 + 决策头，322M～421M 问题原语 noul / choice / score + 评分细则 noul / choice / score noul / choice / score 上下文 32K token 64K token 默认 512 / 1024 token 零样本能力 强 强 弱，需要微调 定制方式 调整 state、instructions 和选项 调整 state、instructions 和 criteria 可在自有数据上微调，并拟合校准温度 官方延迟 同区域 p50 约 85 ms 70～500 ms T4 上单个问题约 33 ms 价格 输入 $0.042 / 百万 token，输出免费 同左 模型免费，自备算力 企业集成 Azure 订阅计费、Entra ID、RBAC、数据区域部署 API Key，零数据保留仅面向企业客户 完全自主可控 多语言 未列出支持的语言，评测中包含多语言任务 以英语为主 多语言检查点覆盖 100 多种语言 基准数据怎么看 1. 微软公布的对比数据 微软把 Microsoft-Decision-1 和 JevBench 榜单上的几个头部模型、Jev 以及 GPT-6 Sol 放在一起，在 36 个公开和私有基准、共 147,137 道题上做了对比（准确率和校准分越高越好，延迟越低越好）：\n模型 平均准确率 中位延迟（p50） 校准分（100 为完美） Microsoft-Decision-1 83.5% 85 ms 92.2 Jev 1.13.0 82.3% 240 ms 93.7 Quyet-1.0-Large 81.9% 380 ms 93.1 Surogate Rune 26B-A4B 79.7% 380 ms 91.8 GPT-6 Luna Decisions 79.4% 300 ms 89.9 deck-31B 77.8% 400 ms 83.5 H2O-Lightning-4B v1.1 77.2% 210 ms 91.8 GPT-6 Sol（参照） 未参与排名 3010 ms 未评分 注：Microsoft-Decision-1 的延迟是在 Foundry 同区域测得的，其余模型的延迟引用自 JevBench v1.6.1 的调整后中位延迟（10 月 7 日数据）。另外，AWS 的 Strands-Decider 2B 只能回答 36 个基准中的 23 个，平均准确率为 54.8%，未列入上表。微软的文章在发布后还专门更新了一次，补充了 Jev 的准确率和校准数据。\n按这组数据，Microsoft-Decision-1 的平均准确率第一，速度最快：比第二快的 H2O-Lightning-4B v1.1 快 2.5 倍，比 Jev 快约 2.8 倍，比 GPT-6 Sol 快约 35 倍；校准分排第三。\n需要注意的是，微软说的是 36 项基准的平均准确率最高，而不是每一项都排第一，一些报道中“36 项基准全部第一”的说法并不准确。\n2. 第三方 JevBench 的实测 JevBench 是一个独立于 TypeSafe 的社区榜单（个人维护的开源项目 Benchmark Heaven），用 1500 道决策题（1200 道封存题加 300 道公开题），从智能（Intelligence）、校准（Calibration）、速度和成本四个维度评测决策模型。Microsoft-Decision-1 发布的第二天（10 月 10 日），JevBench 就通过 Azure Foundry 的原生接口完成了完整测试。下面是 API 榜单上的部分结果：\n综合排名 系统 综合分 能力分 每千次决策成本 中位延迟 1 Sage 1.3.0（Levanto Labs） 74.0 78.6 约 $0.025 0.15 s 2 d1（Liquid AI） 73.0 74.4 $0.017 0.27 s 3 Mercury Decide（Inception） 72.4 73.6 约 $0.018 0.33 s 4 Jev 1.13.0 71.5 77.1 $0.032 0.24 s 5 wity-1 70.8 79.1 $0.024 1.57 s 6 Microsoft-Decision-1 69.1 70.8 $0.019 0.46 s 8 OpenAI Decisions（gpt-6-luna） 62.5 73.5 $0.052 0.30 s 把 Jev 和 Microsoft-Decision-1 单独拿出来比较：\n维度 Jev 1.13.0 Microsoft-Decision-1 智能分 63.6 57.2 校准分 90.6 84.4 每千次决策成本 $0.032 $0.019 中位延迟 0.24 s 0.46 s JevBench 的智能分是扣除随机猜测之后的得分（0 表示和随机猜测相当，100 表示全部答对），能力分是智能分和校准分的平均值，综合分则把智能、校准、速度和成本四个维度放在一起计算。它们和微软表格里的“平均准确率”不是同一个指标，不能直接换算。\n3. 解读数据时必须注意的五点 第一，自测与第三方的结论并不一致。 在微软的 36 项基准上，Microsoft-Decision-1 的平均准确率比 Jev 高 1.2 个百分点；但在 JevBench 上，Jev 的智能分（63.6 对 57.2）和能力分（77.1 对 70.8）都明显领先。两边的题目、评分方法和统计口径完全不同，谁也不能代表“绝对真相”。比较稳妥的理解是：Microsoft-Decision-1 已经进入第一梯队，但还谈不上全面超越 Jev。这也再次说明，最可靠的基准永远是你自己的数据。\n第二，校准仍是 Jev 的强项。 即使在微软自己的数据里，Microsoft-Decision-1 的校准分（92.2）也落后于 Jev（93.7）和 Quyet-1.0-Large（93.1）；在 JevBench 上差距更大（84.4 对 90.6）。对于依赖置信度阈值做分流的系统来说，校准质量往往比准确率高出一两个点更重要。\n第三，延迟主要取决于部署位置。 微软的 85 ms 是在 Foundry 同区域测得的，而表中其他模型的延迟来自 JevBench 自己的测试环境。JevBench 原生调用 Microsoft-Decision-1 测得的中位延迟是 459 ms，在同一个榜单上，Jev 是 240 ms。两组数字都没有错，只是测量条件不同。决策模型的计算本身只需要几十毫秒，真正决定体验的是网络距离：把 Foundry 资源建在离应用最近的区域，对数据驻留有要求时选择 DataZoneStandard，并且一定要用真实流量实测。\n第四，同样的单价，不同的单次成本。 两者的标价都是每百万输入 token 0.042 美元，但 JevBench 在同一批题目上实测的每千次决策成本，Microsoft-Decision-1 约为 0.019 美元，Jev 约为 0.032 美元，前者便宜约 40%。差异来自两者对同样的输入计出的 token 数不同。调用量很大时，这个差距值得算进总成本。\n第五，Laya 不在同一条赛道上。 微软的对比中没有 Laya。JevBench 对 Laya 的三个检查点做了零样本测试，智能分只有 0.3～5.5，几乎等同于随机猜测。这和 Laya 作者自己的定位是一致的：它是一个需要在自有数据上微调的快速底座，而不是零样本决策引擎。再加上默认只有 512 / 1024 token 的上下文，Laya 在 JevBench 这种包含长文本、覆盖开放领域的考卷上吃亏是必然的。它的价值在于本地部署，以及特化之后的速度和成本，这一点在上一篇中已经详细讨论过。\n此外，JevBench 中有 23 道约 7.7 万～8.1 万 token 的超长题。Microsoft-Decision-1 有 21 道因为超出上下文限制而失败，Jev 也拒绝了这 23 道超出其输入范围的题目，它们都按答错计分。\n4. 小结 比较公允的结论是：Microsoft-Decision-1 进入了决策模型的第一梯队，优势在于同区域延迟、单次成本和微软生态；Jev 在校准和长上下文上仍然领先；Laya 则是另一条赛道上的选手，适合有数据、需要本地化的团队。\nMicrosoft-Decision-1 的优势与劣势 优势 企业级集成：作为 Azure 直接销售的 Foundry 模型，它通过 Azure 订阅计费，受 Azure SLA 保障并由微软提供支持；支持 Entra ID 认证和 RBAC 权限控制，可以和现有的 Azure 网络、监控、合规体系一起管理。对已经在用 Azure 的企业来说，几乎不需要引入新的供应商。 同区域延迟低：微软在同区域测得的 p50 约为 85 ms、p95 约为 125 ms，加上决策只需要一次前向计算，适合放进 Agent 的多步循环里。 零样本能力进入第一梯队：不需要任何训练数据就能使用；在微软的 36 项基准上平均准确率最高，在 JevBench 的 27 个 API 产品中综合排名第 6。 鲁棒性好：对同一请求做 8 种等价扰动，平均翻转率只有 1.3%，选项改写或重排时不翻转。在提示词和选项不断演进的生产环境中，这一点很重要。 单次成本更低：标价与 Jev 相同，按 JevBench 的实测，每千次决策的成本还要低约 40%。 接口兼容，迁移成本低：请求格式与 Jev 的 System One 风格接口几乎一致，已有的问题设计和分流逻辑可以直接复用（阈值需要重新标定）。 有具体的落地参考：Xbox、Copilot、事故响应、Microsoft Discovery 等内部试点给出了可以借鉴的用法，虽然这些同样是自测数据。 劣势 以自测数据为主，第三方结果不如官方亮眼：在 JevBench 上，它的智能分和校准分都低于 Jev，在 API 榜单上综合排名第 6。 校准不是最强：无论官方数据还是第三方数据，它的校准都落后于 Jev，上线前一定要用自己的数据标定阈值。 上下文只有 32K：比 Jev 的 64K 短，长邮件串、长合同或 Agent 的长执行轨迹需要先截断、摘要或分块。 权重不开放，也不能微调：虽然底座是开源的 Qwen3.5-9B，但模型本身只能以托管 API 的形式使用，无法本地或离线部署；目前文档只介绍了通过 state、instructions 和选项来调优，没有提供基于自有数据的微调。 版本和底座都可能变化：目前只有版本 1，而微软已经表示会把它迁移到 MAI 或 OpenAI 的底座上。底座一换，概率分布很可能随之变化，调好的阈值需要重新标定。 没有解释，且对措辞敏感：模型只返回数字，不给理由。官方文档也提醒：问题和选项的措辞、顺序会影响分数；校准在熟悉的任务类型上最可靠；模型的知识可能已经过时；作为安全过滤器时，它可能漏掉隐蔽的有害内容，也可能误拦正常内容。 跨境与数据驻留：中国大陆的团队目前需要通过 Azure 全球版的 Foundry 调用它，要同时考虑跨境网络延迟和数据出境的合规要求；GlobalStandard 部署可能在任意区域处理推理请求，对数据驻留敏感的场景应选择 DataZoneStandard。 与 Jev、Laya 相比，怎么选 1. 场景对照表 场景 推荐 理由 已经在用 Azure，需要统一计费、Entra ID 认证和企业合规 Microsoft-Decision-1 不引入新的供应商，和现有的 Azure 资源统一管理 Agent 的多步控制（继续、重试、交接）、AI 评审、电脑操作中的下一步选择 Microsoft-Decision-1 同区域延迟低，官方明确支持评分细则类的问题 云端的海量分类和打标，追求单次成本 Microsoft-Decision-1 单价相同，实测单次成本更低 对置信度校准要求最高的阈值分流 Jev 官方和第三方数据中，校准都领先 长文本判断（32K～64K token） Jev 64K 上下文 细粒度、选项很多的分类（几十到上百个选项） Jev 最多支持 255 个选项，在 Banking77 这类细粒度意图分类上表现好 数据不能离开自己的环境，或者需要离线运行 Laya 三者中唯一可以本地部署的选项 每秒几十次以上的实时决策、端侧应用 Laya 本地推理，没有网络往返 有大量标注数据的垂直任务 Laya（微调后） 特化之后更快、更准、更便宜 中文等非英语场景 三者都需要自测 Jev 以英语为主；Laya 要使用多语言检查点；Microsoft-Decision-1 没有公布支持语言的细节 2. 选型时要回答的几个问题 可以按下面的顺序依次回答：\n1. 数据能否离开自己的环境（以及境内）？ └─ 不能 → Laya（硬约束，其他因素都要让路） 2. 是否已经在用 Azure，需要统一账单、Entra ID、RBAC 和数据区域部署？ └─ 是 → 优先评估 Microsoft-Decision-1 3. 是否高度依赖置信度阈值，或者单次输入经常超过 32K token？ └─ 是 → 优先评估 Jev 4. 是否有标注数据，并且需要每秒几十次以上的决策？ └─ 是 → 微调 Laya，本地部署 5. 以上都不是硬约束？ └─ 用同一组题目同时测试 Microsoft-Decision-1 和 Jev， 按准确率、校准、延迟和单次成本综合选择 3. 组合使用：不必三选一 由于三者的接口几乎一致，组合使用的成本很低：\n多供应商容灾：把 Microsoft-Decision-1 和 Jev 配置成互为备份的两个云端后端，一方限流或故障时自动切换。两者的概率分布不同，阈值需要分别标定。 影子测试：生产流量走一个后端，同时把请求异步发给另一个后端，积累两者结论不一致的样本，再交给人工或大模型裁决，为后续的选型和阈值调整提供依据。 分层级联：本地的 Laya 先处理高频、短文本、高置信度的请求；低置信度或长文本请求升级给 Microsoft-Decision-1 或 Jev；仍然拿不准的，再交给推理型大模型或人工处理。 按数据敏感度路由：涉及敏感数据的请求只走本地的 Laya，其余请求走云端。 代码示例：同一组问题，三种后端 1. 部署 Microsoft-Decision-1 可以在 Foundry 门户的模型目录中搜索 Microsoft-Decision-1 并一键部署，也可以使用 Azure CLI：\naz cognitiveservices account deployment create \\ --name \u0026lt;ACCOUNT_NAME\u0026gt; \\ --resource-group \u0026lt;RESOURCE_GROUP\u0026gt; \\ --deployment-name decision-1 \\ --model-name \u0026#34;Microsoft-Decision-1\u0026#34; \\ --model-format Microsoft \\ --model-version \u0026#34;1\u0026#34; \\ --sku-name GlobalStandard \\ --sku-capacity 1 如果需要把推理限制在某个数据区域内，可以把 --sku-name 换成 DataZoneStandard（需要所在区域支持）。\n2. 启动本地的 Laya 服务 pip install \u0026#34;laya[serve]\u0026#34; # 只监听本机；如需对外提供服务，请设置 LAYA_API_KEY 并放在网关后面 LAYA_HOST=127.0.0.1 LAYA_DEVICE=cuda LAYA_PRELOAD=1 laya-serve 3. 用同一组问题调用三个后端 下面的示例使用 requests 和 azure-identity（pip install requests azure-identity）。注意 team 问题里加了一个 cannot_tell 选项，这是微软文档推荐的做法：当模型不应该被迫做出选择时，给它一个“无法判断”的出口。\nimport os import requests from azure.identity import DefaultAzureCredential payload = { \u0026#34;state\u0026#34;: { \u0026#34;message\u0026#34;: \u0026#34;Checkout is down again. This is my third request.\u0026#34;, \u0026#34;customer_contact_count\u0026#34;: 3, }, \u0026#34;questions\u0026#34;: { \u0026#34;team\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;choice\u0026#34;, \u0026#34;instructions\u0026#34;: \u0026#34;Which team should handle this request?\u0026#34;, \u0026#34;criteria\u0026#34;: { \u0026#34;billing\u0026#34;: \u0026#34;Charges, invoices, and refunds\u0026#34;, \u0026#34;engineering\u0026#34;: \u0026#34;Bugs, errors, and outages\u0026#34;, \u0026#34;support\u0026#34;: \u0026#34;How-to and account questions\u0026#34;, \u0026#34;cannot_tell\u0026#34;: \u0026#34;The message does not say enough to decide\u0026#34;, }, }, \u0026#34;severity\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;score\u0026#34;, \u0026#34;instructions\u0026#34;: \u0026#34;How severe is this issue?\u0026#34;, \u0026#34;criteria\u0026#34;: [\u0026#34;Cosmetic\u0026#34;, \u0026#34;Minor\u0026#34;, \u0026#34;Major\u0026#34;, \u0026#34;Critical\u0026#34;], }, \u0026#34;repeat_contact\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;noul\u0026#34;, \u0026#34;instructions\u0026#34;: \u0026#34;Has the customer contacted support before?\u0026#34;, }, }, } def decide(url: str, headers: dict, model: str | None = None) -\u0026gt; dict: body = {**payload, \u0026#34;model\u0026#34;: model} if model else payload resp = requests.post(url, headers=headers, json=body, timeout=10) resp.raise_for_status() return resp.json()[\u0026#34;answers\u0026#34;] # 1) Microsoft-Decision-1（Foundry）：model 填部署名称，推荐使用 Entra ID 认证 token = DefaultAzureCredential().get_token(\u0026#34;https://cognitiveservices.azure.com/.default\u0026#34;).token answers = decide( f\u0026#34;{os.environ[\u0026#39;AZURE_ENDPOINT\u0026#39;].rstrip(\u0026#39;/\u0026#39;)}/providers/microsoft/v1/systemone\u0026#34;, {\u0026#34;Authorization\u0026#34;: f\u0026#34;Bearer {token}\u0026#34;}, os.environ[\u0026#34;DEPLOYMENT_NAME\u0026#34;], ) print(\u0026#34;decision-1:\u0026#34;, answers[\u0026#34;team\u0026#34;][\u0026#34;choice\u0026#34;]) # 2) Jev（TypeSafe）：固定版本号，避免别名升级后调好的阈值失效 answers = decide( \u0026#34;https://api.typesafe.ai/v1/systemone\u0026#34;, {\u0026#34;Authorization\u0026#34;: f\u0026#34;Bearer {os.environ[\u0026#39;TYPESAFE_API_KEY\u0026#39;]}\u0026#34;}, \u0026#34;jev-1.13.0\u0026#34;, ) print(\u0026#34;jev:\u0026#34;, answers[\u0026#34;team\u0026#34;][\u0026#34;choice\u0026#34;]) # 3) Laya（本地 laya-serve）：省略 model，由 Router 按语言自动选择检查点 answers = decide(\u0026#34;http://127.0.0.1:8000/v1/systemone\u0026#34;, {}) print(\u0026#34;laya:\u0026#34;, answers[\u0026#34;team\u0026#34;][\u0026#34;choice\u0026#34;]) 如果使用 API Key 调用 Microsoft-Decision-1，把请求头换成 {\u0026quot;api-key\u0026quot;: os.environ[\u0026quot;AZURE_API_KEY\u0026quot;]} 即可。\n三个后端返回的 answers 都以问题名为键，但有几个细节需要留意：\nJev 和 Laya 的 choice 结果都包含 choice、probabilities 和 confidence，不过 Laya 的 confidence 计算方式和 Jev 不同，所以它额外提供了一个按 Jev 公式计算的 x_jev_confidence，方便沿用 Jev 上调好的阈值； Microsoft-Decision-1 的文档说明会返回选中项和每个选项的概率，接入时建议先打印一次完整的响应，确认字段名之后再编写阈值逻辑； Microsoft-Decision-1 的 score 返回的是各等级下标的概率加权平均值（四级量表的取值范围是 0～3，可能落在两个等级之间），文档建议把它用于相对排序和阈值判断，而不是当作绝对的校准评分。 落地注意事项 Microsoft-Decision-1 的官方文档给出了不少实用的建议，它们同样适用于 Jev 和 Laya：\n先用代表性数据验证：用自己的标注样本测试准确率和延迟，再决定是否上线。 按误判代价设置阈值：根据误报和漏报的代价分别设置置信度阈值；不应该强行选择时，加一个“无法判断”的选项。 使用中性措辞，随机化选项顺序：测试改变选项顺序是否会影响结果。 用 Coding Agent 迭代问题设计：让 Coding Agent 生成多组候选的 state、instructions 和选项，在标注集上评估、对比，人工审核之后再上线。 涉及个人的重大决策必须有人参与：在信贷、招聘、住房、医疗、法律等场景中，只能把模型作为决策辅助，并告知受影响的用户有 AI 参与了决策；不要使用不必要的敏感属性，并持续监控不同群体之间的结果差异。 阈值不能跨后端照搬：三个模型的概率分布不同，置信度的计算方式也可能不同，切换或混用后端时要分别标定。 固定版本，持续监控：固定部署的模型版本，升级（包括底座迁移）之后重新标定阈值，并持续监控概率分布和校准漂移。 总结 用一句话概括三者：\nMicrosoft-Decision-1 是“Azure 里的企业级决策组件”，Jev 是“开箱即用的云端通才”，Laya 是“可以特化的本地专才”。\n如果你已经在用 Azure，需要统一的计费、身份认证和合规体系，或者想给 Agent 加一道低延迟的“决策闸门”，Microsoft-Decision-1 是最省事的选择； 如果你最看重置信度校准和长上下文，Jev 目前仍然更稳； 如果数据不能离开本地，或者需要毫秒级、海量的决策，并且有能力准备标注数据，Laya 依然不可替代。 从更大的视角看，Microsoft-Decision-1 的意义不只是“又多了一个决策模型”。微软带着开源底座、与 Jev 相同的价格和几乎一致的接口入局，说明了两件事：一是 state + questions 风格的决策接口正在成为事实标准，用户的迁移成本越来越低；二是底座正在变成可替换的零件，竞争的焦点转向了后训练数据、评测体系、校准质量和分发渠道。对开发者来说这是好消息：把决策从大模型里拆出来，已经从创业公司和开源社区的尝试，变成了大厂也认可的工程实践。\n参考资料 Microsoft Command Line：Introducing Microsoft-Decision-1, our model for fast decision-making Microsoft Learn：Deploy and use Microsoft-Decision-1 in Microsoft Foundry Microsoft Learn：Foundry Models sold by Azure Microsoft Foundry 模型目录：Microsoft-Decision-1 JevBench：Hosted decision API benchmark JevBench：Open-weights decision model leaderboard TypeSafe 文档：Models Laya GitHub 仓库 Jev vs Laya：闭源与开源 System One 决策模型的对比与选型 ","permalink":"https://wilsonwu.me/zh/blog/2026/microsoft-decision-1/","summary":"\u003cp\u003e\u003cimg alt=\"Microsoft-Decision-1、Jev 与 Laya\" loading=\"lazy\" src=\"/zh/blog/2026/microsoft-decision-1/1-microsoft-decision-1.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"引言\"\u003e引言\u003c/h2\u003e\n\u003cp\u003e上个月我写了一篇 \u003ca href=\"/zh/blog/2026/jev-vs-laya/\"\u003eJev vs Laya\u003c/a\u003e，对比了 TypeSafe AI 的闭源决策模型 Jev 和社区开源的 Laya。那时“决策模型”还是一个刚刚诞生的新品类：9 月 15 日 Jev 发布，三天后 Laya 开源。\u003c/p\u003e","title":"Microsoft-Decision-1 详解：微软入局决策模型，与 Jev、Laya 的三方对比"},{"content":"\n在上一篇文章中，我把 OpenWebUI 的数据库迁移到了 Azure 托管 PostgreSQL。在日常使用 OpenWebUI 的过程中，我经常遇到两个问题：\n问不到最新的信息：模型有知识截止时间，问到最近的版本发布、新闻或价格，要么答不上来，要么答错。 贴进去的链接不会被打开：把文章、文档或 GitHub 页面的 URL 贴进对话框，想让模型总结、翻译或对比，模型却不会去读这个链接，有时甚至会根据 URL 猜出一段“看起来很像”的内容。 这两个问题都可以靠 OpenWebUI 的联网能力解决。本文使用微软新推出的 Microsoft Web IQ 作为联网搜索引擎，并让模型自动识别消息中的 URL、读取网页后再回答。整个过程只需要在 Azure 门户中创建一个 Web IQ 资源，再到 OpenWebUI 的管理界面中完成配置，不需要写代码，也不需要部署额外的服务。\n本文基于 Open WebUI v0.11.4（2026 年 9 月发布），界面名称以英文界面为准。文中的 API Key 均为占位符。\n最终效果 配置完成后：\n提问涉及最新信息时，模型会自己调用 search_web，由 Web IQ 返回搜索结果，回答附带参考链接； 消息中出现 http:// 或 https:// 链接时，模型会自己调用 fetch_url，通过 Web IQ 读取页面正文后再回答，读取过的页面会出现在引用来源中； 两者可以在同一次回答中组合使用，例如先读取你给出的文档，再搜索最新的变更说明做对比。 为什么选择 Microsoft Web IQ 打开 OpenWebUI 的 Settings → Admin → Web Search，在 Web Search Engine 中可以找到 bing。选中后需要填写 Bing Search V7 Endpoint 和 Bing Search V7 Subscription Key，看起来只要填上 Azure 上 Bing 资源的密钥就能用，但这条路已经走不通了：\nBing Search v7 API（包括 Bing Custom Search）已于 2025 年 8 月 11 日退役，已有实例全部下线，也无法再创建新资源； 现在 Azure 上能创建的 Bing 资源是 Grounding with Bing Search，它只能作为 Microsoft Foundry 智能体的工具使用，不能拿密钥直接调用搜索接口。想接入 OpenWebUI，就得自己开发一个桥接服务。 Microsoft Web IQ 是微软在 2026 年 6 月发布的一组面向 AI 应用的 API，用来为模型提供实时的网络信息（grounding），可以理解为“给 AI 智能体用的搜索引擎”：\n基于 Bing 的全球索引构建，并针对智能体的多轮检索重新设计； 返回的不只是链接列表，而是带来源的相关段落，可以直接放进模型上下文，用更少的 token 得到更好的回答； 覆盖网页、新闻、图片、视频等内容，支持 100 多种语言和市场。 更重要的是，OpenWebUI 已经内置了 microsoft_web_iq 引擎。它既可以作为搜索引擎，也可以作为读取网页的网页加载引擎，只需要一个 API Key。\n方案 在 OpenWebUI 中接入 现状 Bing Search v7 内置 bing 引擎 已退役，不可用 Grounding with Bing Search 自己开发桥接服务，通过 Foundry 调用 主要面向 Foundry 智能体 Microsoft Web IQ 内置 microsoft_web_iq 引擎，填写 API Key 即可 2026 年 6 月发布，目前为有限访问 工作原理 用户消息 │ ▼ OpenWebUI（模型以原生工具调用方式工作） │ ├── search_web：联网搜索 │ └── Web IQ /search/web：返回标题、链接和相关段落 │ └── fetch_url：读取消息中的链接 └── Web IQ /browse：读取网页，返回 Markdown 格式的正文 几点说明：\nsearch_web 和 fetch_url 是 OpenWebUI 的两个内置工具，同属 Web Search 类别，由模型自己决定何时调用； 这两个工具依赖原生工具调用（Native Function Calling），这是 v0.10.0 起的默认模式； 网页加载引擎也设为 Web IQ 之后，网页由 Web IQ 读取，需要 JavaScript 渲染的页面也能读到内容。PDF 等文件仍然由 OpenWebUI 下载后自行解析。 第一步：创建 Web IQ 资源并获取 API Key Web IQ 以 Azure 资源的形式提供。在 Azure 门户中搜索 Microsoft Web IQ，进入 Microsoft Web IQ resources，点击 Create，在 Basics 页填写：\nSubscription 和 Resource group：选择用于管理和计费的订阅与资源组； Name：资源名称； Region：固定为 Global，这是一个可以跨 Azure 区域使用的全局资源。 点击 Review + create 完成创建，然后在资源页面中复制 API Key，下一步会用到。\nWeb IQ 目前仍处于有限访问（limited access）阶段。如果在 Azure 门户中找不到或者无法创建这个资源，可以先到 Microsoft Web IQ 官网 点击 Join the waitlist 申请。\n第二步：配置 Web IQ 搜索和网页读取 以管理员身份登录 OpenWebUI，点击左下角的头像，进入 Settings → Admin → Web Search。\n1. Search 部分 设置项 值 说明 Web Search 开启 全局开关 Web Search Engine microsoft_web_iq 选中后会出现下面三项 Microsoft Web IQ API Base URL https://api.microsoft.ai/v3 保持默认值 Microsoft Web IQ API Key \u0026lt;WEB_IQ_API_KEY\u0026gt; 第一步获取的 API Key Language en 搜索结果的语言代码，默认为 en Search Result Count 5 每次搜索返回的结果数量 Fetch URL Content Length Limit 50000 fetch_url 最多返回的字符数，留空表示不限制 几点说明：\nMicrosoft Web IQ API Base URL：OpenWebUI 已经预先填好，一般不需要修改；如果 Web IQ 资源页面显示了其他终结点，以资源页面为准； Language：技术类内容用 en 通常效果更好；如果主要查询中文资讯，可以改为 zh（以 Web IQ 支持的语言代码为准）； Fetch URL Content Length Limit：默认不截断，遇到很长的页面会占用大量上下文，建议设置上限； Web Search Confirmation：可选。开启后，用户在对话中使用联网搜索前会先看到确认提示，例如默认的 “Your query will be sent to the configured web search provider.”，提醒用户查询内容会发送到外部服务。 2. Loader 部分 设置项 值 说明 Web Loader Engine microsoft_web_iq 读取网页也交给 Web IQ，与搜索共用上面的 API Key 点击 Save 保存，配置立即生效，不需要重启容器。\nWeb Loader Engine 也可以保持 Default，由 OpenWebUI 服务器自己抓取网页。但默认加载器读不到需要 JavaScript 渲染的内容，它的 User-Agent 也容易被部分网站拦截，交给 Web IQ 读取会省心很多。\n第三步：让模型自动识别并读取消息中的 URL 1. 模型设置 进入 Settings → Admin → Models，编辑对话使用的模型：\n位置 设置 Capabilities 勾选 Web Search、Builtin Tools、Citations、Status Updates Builtin Tools 确认 Web Search（Search the web and fetch URLs）已勾选 Default Features 勾选 Web Search，新对话默认打开联网开关 Advanced Params Function Calling 设为 Native（默认值） 其中 Default Features 很关键：原生模式下，只有模型启用了 Web Search 能力，并且当前对话打开了联网开关，search_web 和 fetch_url 才会提供给模型。不勾选的话，用户每次都要在输入框的 + 菜单中手动打开联网搜索，否则贴进来的链接不会被读取。\n原生模式要求模型具备可靠的工具调用能力，建议使用较新的模型，太小或太旧的模型可能不会正确调用工具。\n2. 系统提示词 是否调用 fetch_url 由模型自己决定。为了让行为更稳定，在同一个页面的 System Prompt 中加入以下规则：\n你可以使用 search_web 和 fetch_url 两个工具： 1. 用户消息中出现以 http:// 或 https:// 开头的链接时，先调用 fetch_url 读取链接内容，再根据读取结果回答；有多个链接时逐个读取。 2. 问题涉及最新动态、版本、价格等时效性信息，或者你不确定答案时，调用 search_web 搜索；搜索结果中的摘要不够时，再用 fetch_url 打开最相关的结果。 3. 链接无法访问或内容为空时，如实告诉用户，不要根据链接地址推测页面内容。 4. 在回答末尾列出参考链接。 第 3 条用来防止模型在读取失败后“编”一段内容；第 4 条则是因为原生模式下，search_web 的结果不会出现在引用来源中，只有 fetch_url 读取过的页面才会，所以需要模型在回答中写出来。\n3. 手动附加网页 如果希望某个链接一定被读取，也可以手动附加：\n在输入框中输入 # 后粘贴链接，然后选择该网页； 或者通过输入框的 + → Attach Webpage 添加链接。 这两种方式由 OpenWebUI 直接读取页面（同样使用上面配置的网页加载引擎），不依赖模型的判断。\n效果验证 1. 读取链接 新建一个对话，输入：\n帮我总结这篇文档的要点：https://docs.openwebui.com/features/chat-conversations/web-search/providers/microsoft-web-iq 预期：\n回答过程中显示模型调用了 fetch_url； 回答下方的引用来源中出现这篇文档； 回答内容与文档一致，而不是泛泛而谈。 2. 联网搜索 Open WebUI 最新发布的版本是多少？有哪些主要更新？ 预期：\n模型调用 search_web，结果来自 Web IQ； 如果摘要不够，模型会继续调用 fetch_url 打开发布说明页面； 回答末尾列出参考链接。 3. 组合使用 先读取 https://github.com/open-webui/open-webui/releases 的内容，再搜索一下社区对最新版本的反馈，最后给我一个是否升级的建议。 预期模型会先读取给出的链接，再根据需要多次搜索，最后综合两部分信息给出建议。\n4. 排查问题 如果没有达到预期效果，可以先查看 OpenWebUI 的日志：\ndocker logs --since 10m open-webui 2\u0026gt;\u0026amp;1 | grep -Ei \u0026#39;web iq|microsoft browse|fetch_url|blocked\u0026#39; 常见的日志：\nError searching with Microsoft Web IQ API：搜索请求失败，通常是 API Key 不正确或者尚未开通； Error browsing ... with Microsoft Web IQ：Web IQ 读取网页失败； Blocked ...：链接指向内网地址或者在过滤列表中，被 OpenWebUI 拦截。 也可以直接用 curl 验证 API Key，下面的请求格式与 OpenWebUI 实际发送的一致：\ncurl -sS https://api.microsoft.ai/v3/search/web \\ -H \u0026#34;x-apikey: $WEB_IQ_API_KEY\u0026#34; \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;query\u0026#34;: \u0026#34;Open WebUI\u0026#34;, \u0026#34;maxResults\u0026#34;: 3, \u0026#34;language\u0026#34;: \u0026#34;en\u0026#34;, \u0026#34;contentFormat\u0026#34;: \u0026#34;passage\u0026#34;}\u0026#39; 正常情况下会返回包含 webResults 的 JSON，每条结果都有 url、title 和 content。\n注意事项 数据会发送到 Web IQ：搜索词和需要读取的链接会发送到 Web IQ API（api.microsoft.ai），数据如何处理以 Web IQ 的服务条款为准。敏感对话可以关闭联网开关，或者开启 Web Search Confirmation 提醒用户。 请求中会附带用户信息：从 v0.11.4 的源码看，调用 Web IQ 搜索时，OpenWebUI 会在请求头中附带当前用户的名称、ID、邮箱和角色（X-OpenWebUI-User-*），目前无法通过配置关闭。如果组织对此有要求，上线前需要评估。 内网链接无法读取：读取链接之前，OpenWebUI 会先校验 URL，默认禁止访问内网地址和云元数据地址。这些防护默认开启，无需额外配置，Web IQ 本身也访问不到内网页面。 调用次数与成本：每次 search_web 和 fetch_url 都是一次 Web IQ 调用，原生模式下模型还可能为一个问题连续调用多次。建议 Search Result Count 保持在 3～5，只在需要联网的模型上把 Web Search 设为默认功能。Web IQ 的费用计入创建资源时选择的 Azure 订阅，可以为资源组设置预算告警。 常见问题 现象 可能原因 处理方法 贴了链接，模型说无法访问网页 对话没有打开联网开关，或者模型没有启用 Web Search 能力和内置工具 检查模型的 Capabilities、Default Features 和对话中的联网开关 模型根据 URL 编造内容 没有调用 fetch_url 确认 Function Calling 为 Native，并加入系统提示词 普通用户看不到联网开关 用户组没有 Web Search 功能权限 在用户组的功能权限中允许 Web Search 搜索始终没有结果 API Key 错误或者尚未开通 查看日志，并用 curl 验证 API Key 搜索结果的语言不符合预期 Language 与查询的语言不一致 修改 Language 部分网页读取失败 页面需要登录、禁止抓取，或者链接指向内网 换一个可以公开访问的链接，或者直接把正文粘贴到对话中 总结 Bing Search API 退役之后，OpenWebUI 内置的 bing 引擎已经无法使用。借助 OpenWebUI 内置的 microsoft_web_iq 引擎，只需要在 Azure 上创建一个 Web IQ 资源并拿到 API Key，就可以在管理界面中完成联网搜索的接入：\n搜索和网页读取都交给 Web IQ，返回的是适合直接放进模型上下文的段落和正文； 在原生工具调用模式下，search_web 负责搜索，fetch_url 负责读取消息中的链接； 通过模型的 Default Features 和系统提示词，让模型自动识别 URL、先读后答，并在回答中给出参考链接。 整个过程不需要写一行代码。无论是询问最新的信息，还是直接丢一个链接让模型总结，现在都可以在 OpenWebUI 中完成。\n参考资料 Announcing Microsoft Web IQ Microsoft Web IQ Open WebUI: Microsoft Web IQ Open WebUI: Agentic Web Search \u0026amp; URL Fetching Open WebUI: Bing Open WebUI: Hardening ","permalink":"https://wilsonwu.me/zh/blog/2026/microsoft-web-iq-in-openwebui/","summary":"\u003cp\u003e\u003cimg alt=\"OpenWebUI 接入 Microsoft Web IQ\" loading=\"lazy\" src=\"/zh/blog/2026/microsoft-web-iq-in-openwebui/1-openwebui-web-iq.png\"\u003e\u003c/p\u003e\n\u003cp\u003e在\u003ca href=\"/zh/blog/2026/migrate-openwebui-to-azure-pg/\"\u003e上一篇文章\u003c/a\u003e中，我把 OpenWebUI 的数据库迁移到了 Azure 托管 PostgreSQL。在日常使用 OpenWebUI 的过程中，我经常遇到两个问题：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e问不到最新的信息\u003c/strong\u003e：模型有知识截止时间，问到最近的版本发布、新闻或价格，要么答不上来，要么答错。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e贴进去的链接不会被打开\u003c/strong\u003e：把文章、文档或 GitHub 页面的 URL 贴进对话框，想让模型总结、翻译或对比，模型却不会去读这个链接，有时甚至会根据 URL 猜出一段“看起来很像”的内容。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e这两个问题都可以靠 OpenWebUI 的联网能力解决。本文使用微软新推出的 \u003cstrong\u003eMicrosoft Web IQ\u003c/strong\u003e 作为联网搜索引擎，并让模型自动识别消息中的 URL、读取网页后再回答。整个过程只需要在 Azure 门户中创建一个 Web IQ 资源，再到 OpenWebUI 的管理界面中完成配置，\u003cstrong\u003e不需要写代码，也不需要部署额外的服务\u003c/strong\u003e。\u003c/p\u003e","title":"用 Microsoft Web IQ 增强 OpenWebUI：零代码实现联网搜索与链接自动读取"},{"content":"\n引言 2026 年 9 月，AI 圈突然流行起一类“不会说话”的模型。\n9 月 15 日，由前 OpenAI 研究员 Diogo Almeida 创办的 TypeSafe AI 发布了 Jev：它不聊天、不写文章、也不生成代码，只接收一段状态（state）和一组预先定义好的问题，然后直接返回带有校准概率的结构化决策。\n仅仅三天后，9 月 18 日，独立研究者 Nandakishor M（Convai Innovations）开源了 Laya。它同样是一个非自回归的 System 1 决策模型，采用 Apache 2.0 协议，权重可以直接从 Hugging Face 下载，三天内 GitHub 星标就超过了 6000，被社区称为“开源版 Jev”。\n一边是背靠 4000 万美元种子轮、开箱即用的闭源 API，一边是 4.2 亿参数、一张 T4 甚至一台 MacBook 就能跑起来的开源模型。社交媒体上“Laya 反超 Jev”“Jev 的护城河只维持了两天”之类的说法很多，但如果把双方公开的资料仔细读一遍，就会发现结论远没有这么简单。\n本文将从以下几个方面展开：\n什么是决策模型，它要解决什么问题 Jev 与 Laya 各自的技术路线 核心差异与基准数据解读 两者各自的优势与劣势 适用场景与选型建议 什么是决策模型（System One Model） 1. 名字的由来 “System One”来自丹尼尔·卡尼曼的《思考，快与慢》：System 1 是快速、直觉式的判断，System 2 是缓慢、审慎的推理。在这个比喻下，生成式大模型（尤其是推理模型）更像 System 2，而决策模型负责的是毫秒级的“直觉反应”。\n顺带一提，Jev 这个名字来自经济学家威廉·斯坦利·杰文斯（William Stanley Jevons）。TypeSafe 认为机器智能会重演“杰文斯悖论”：就像蒸汽机效率提升反而带动了煤炭需求一样，智能的单价每下降一个数量级，都会解锁更多数量级的新用例。\n2. 用大模型做“选择题”的痛点 在真实的 AI 工程里，大量决策其实都是“小事”：\n这张工单应该分给哪个团队？ 这封邮件是不是钓鱼邮件？ 这段提示词有没有注入攻击？ Agent 下一步该调用哪个工具？上一步执行成功了吗？ 今天常见的做法，是把这些问题交给一个擅长写作的大模型，再从它生成的文本里把答案“抠”出来。这会带来一系列问题：\n问题 表现 延迟高 逐 token 生成，动辄几百毫秒到几秒，推理模型更慢 成本高 输入输出都要计费，输出 token 通常更贵 需要解析 要写 JSON 解析、Schema 校验和失败重试逻辑 类型错误 可能多输出一段解释，甚至返回一个根本不存在的选项 置信度不可信 模型写出的 \u0026quot;confidence\u0026quot;: 0.95 只是一段“听起来很自信”的文本，缺乏统计意义上的校准 3. 决策模型的思路 决策模型干脆把“生成”这一步删掉了。TypeSafe 用一句话概括它：\nUnstructured state in, typed probabilistic decisions out.\n非结构化的状态输入，带概率的类型化决策输出。\n调用方只需要提交两部分内容：\nstate：需要判断的当前状态，可以是字符串、JSON 对象或文本数组 questions：一组类型化问题，每个问题的候选答案都由调用方提前定义 模型并行地回答所有问题，只返回数字和概率。Jev 与 Laya 采用了完全相同的三种问题原语：\n原语 回答的问题 返回内容 典型用途 choice 从给定选项中选一个 选中项、各选项概率、confidence 工单路由、意图识别、工具选择 score 在有序等级上打分 概率加权的期望分数、等级分布、confidence 紧急程度、情绪强度、风险等级 noul 是 / 否判断 “是”的概率（0～1） 钓鱼检测、越狱检测、是否申请退款 由于输出空间被提前限定，模型不可能返回类型错误或凭空编造的选项。但需要注意：类型安全不等于判断正确，它仍然可能在合法的选项里选错，这一点后文会反复提到。\n4. 它在系统中的位置 决策模型并不是用来取代大模型的，而是把大模型从一些“本不该由它来做”的位置上挪开。一个比较合理的分工是：\n代码（确定性逻辑） └─ 流程编排、权限控制、数学与日期计算、执行动作 │ 遇到需要语义理解的“模糊 if 语句” ▼ 决策模型（System 1：Jev / Laya） └─ 分类、路由、打分、校验、护栏，返回类型化答案 + 概率 │ 置信度不足，或需要生成文本 / 复杂推理 ▼ 大模型（System 2：GPT / Claude / DeepSeek 等） └─ 长上下文理解、规划、写作、代码生成 有了校准过的置信度，程序的控制流就不再只有“是 / 否”两种状态，而可以分成三档：高置信度自动执行，中置信度交给更强的模型复核或请求用户确认，低置信度转人工。这也是决策模型真正的价值所在：速度只是表象，可以被写进 if 语句的置信度才是关键。\n两位主角 Jev：TypeSafe AI 的闭源云端服务 基本信息\n发布时间：2026 年 9 月 15 日，以 early access 形式开放 团队背景：TypeSafe AI 是一家旧金山公司，创始人 Diogo Almeida 曾在 Google Brain 和 OpenAI 从事研究，是 InstructGPT 论文和 GPT-4 技术报告的作者之一。发布当天，公司同时宣布了由 DCVC 领投的 4000 万美元种子轮融资 产品形态：闭源，只提供 API（POST https://api.typesafe.ai/v1/systemone）以及 Python / JavaScript SDK 当前版本：Jev 1.13（jev-1.13.0），别名 jev-latest 技术路线\nTypeSafe 声称为自动化场景重建了一整套技术栈：全新的模型架构、追求极致效率的并行采样器，以及一种名为 RLCD（Reinforcement Learning for Calibrated Decisions，面向校准决策的强化学习） 的训练方法。与 RLHF 优化“人类偏好”、RLVR 优化“可验证奖励”不同，RLCD 的目标是让模型在给出答案的同时，给出认知上诚实的概率。\n不过，TypeSafe 目前并没有公开模型架构、参数规模和训练数据。社区有不少人认为它更接近 BERT 这类 encoder 模型，但这只是猜测。\n关键规格\n项目 规格 价格 输入 $0.042 / 百万 token，输出免费 延迟 官方称端到端 70～500 ms 上下文 每个请求 64k token，其中 state 加最长的单个问题不超过 32k token 输入 仅支持文本（字符串、JSON 对象、文本数组），不支持图像、音频和视频 选项数量 choice 最多支持 255 个选项 速率限制 250,000 token/秒、1,200 次请求/分钟（官方称会动态调整） 语言 英语是主要训练语言，中文等 CJK 文本可用，但准确率可能下降 定制 不支持微调或 LoRA，所有客户共用同一套权重 按这个定价计算，如果每次判断平均消耗 1000 个输入 token，100 万次判断的费用只有约 42 美元。\n生态\nJev 发布后迅速走红：LangChain 很快发布了集成指南，Vercel 把它接入了 AI Gateway，社区也涌现出不少实验项目，比如用 Jev 给 Claude Code 的工具调用输出做相关性打分、进而压缩上下文的 fast-jev-compaction。\nLaya：Convai Innovations 的开源本地模型 基本信息\n发布时间：2026 年 9 月 18 日 作者：独立研究者 Nandakishor M（Convai Innovations） 开源协议：Apache 2.0，代码、权重、训练方法和微调 Notebook 全部公开 获取方式：pip install laya（要求 Python 3.10+），权重托管在 Hugging Face 的 convaiinnovations/laya Laya 的诞生还有一段插曲。作者在 2025 年就先后发表过两篇相关论文（arXiv:2503.23303、arXiv:2510.01237），研究用强化学习训练非自回归的决策模型，最早用于预测销售对话的转化率。看到 Jev 以“突破”之名发布后，他把旧方案的架构缺陷全部修了一遍，做出了这个通用的开源版本。\n模型架构\nLaya 采用“双向编码器 + 决策头”的结构，总参数量 421M：\nState（文本或 JSON）+ 类型化问题与选项 │ 拼接为：[CLS] 问题 [SEP] [MASK] 选项1 [MASK] 选项2 ... [SEP] state [SEP] ▼ ModernBERT-large 编码器（395M，28 层双向注意力） ▼ 决策头 Transformer（约 25M，2 层） ├─ 选项打分器：取每个 [MASK] 位置的隐状态 → logit → 温度缩放 → softmax └─ Act / Escalate 头：基于 [CLS] 与分布统计量，判断“自动执行”还是“升级处理” 每个选项前面放一个 [MASK] 标记，经过双向编码器之后，从这些标记的位置取出隐状态进行打分。同一个 state 上的多个问题会组成一个 batch，在一次前向传播中完成计算。\n三个检查点 + 路由器\n检查点 编码器 参数量 默认上下文 用途 laya ModernBERT-large 421M 512 英文 laya-multilingual mmBERT-base 322M 1024 100+ 种语言，速度约快 2 倍 laya-typed-decisions ModernBERT-large 421M 1024 针对 typed-decisions 工作流微调 Laya 内置了一个纯 Python 实现的 Router，会在前向计算之前用不到 1 毫秒的时间检测文本的书写系统和语言，然后自动选择合适的检查点。之所以需要它，是因为英文检查点在非拉丁文字上会“自信地犯错”：在高棉语测试中，它的准确率为 0，置信度却高达 0.952。这种情况下，靠置信度阈值是拦不住的。\n开放的 RLCD 实现\nLaya 沿用了 RLCD 这个名字，并公开了具体做法：用严格适当评分规则（strictly proper scoring rules，由对数评分、球面评分和排序概率评分组合而成）作为奖励，采用 GRPO 风格的组内基线策略梯度进行训练，完全不使用监督交叉熵损失。严格适当评分规则的特点是：只有当模型报告真实的概率时，才能拿到最高的期望奖励，从而“逼”模型说实话。\nAct / Escalate 头则用一个代价矩阵来训练：判对奖励 +1，判错惩罚 -3，升级人工惩罚 -0.5。由此可以推导出，只有当正确率超过 62.5% 时，自动执行才比升级人工更划算，模型也就学会了“没把握就上交”。\n此外，Laya 还提供了开箱即用的工作流预设（模型路由、提示词护栏、内容审核、工单分诊），以及一个可以在 Kaggle 免费 2×T4 GPU 上运行的微调 Notebook：在约 3 万个问题上训练 4 个 epoch，耗时 4～5 小时。\n核心差异一览 维度 Jev Laya 发布方 TypeSafe AI（商业公司） Convai Innovations（独立研究者） 开源 闭源，仅提供 API Apache 2.0，权重与代码全部开放 部署 云端托管（服务目前位于美国西海岸） 本地、私有云或离线环境，GPU / Mac MPS / CPU 均可运行 架构 未公开 ModernBERT / mmBERT 编码器 + 决策头 参数规模 未公开 322M～421M 训练方法 RLCD（细节未公开） RLCD（严格适当评分规则 + 组基线策略梯度，已公开） 问题原语 choice / score / noul choice / score / noul，请求格式几乎一致 上下文长度 64k token 默认 512 / 1024 token（编码器最高支持 8192） 选项数量 最多 255 个 超过 20 个时效果明显下降 零样本能力 强，开箱即用 弱，基础检查点需要微调 定制方式 只能通过 state、instructions、criteria 调整 可以在自有数据上微调，并拟合校准温度 多语言 以英语为主 多语言检查点 + 自动路由，51 种语言中有 45 种达到可用水平 单问题延迟 官方 70～500 ms，第三方实测 p50 为 236～276 ms T4 GPU 上约 33～40 ms 成本 输入 $0.042 / 百万 token 模型免费，只需自备算力 数据合规 数据需发送到 TypeSafe，企业版支持零数据保留 数据不出本地 运维 免运维 需要自行部署、扩缩容、监控和管理版本 基准数据怎么看 1. Laya 公布的对比数据 Laya 的 README 中给出了一张与 Jev 1.13.0 的对比表（Laya 一侧为经过 Router 路由后的实测结果）：\n指标 Jev 1.13.0 Laya（路由） typed-decisions 准确率（2000 个决策） 0.727 0.766 AG News 准确率（4 类） 0.910 0.950 DAIR Emotion 准确率（6 类） 0.480 0.595 Banking77 准确率（72 / 77 类） 0.870 0.425 ECE 校准误差（越低越好） 0.246 0.081 单问题 p50 延迟 236～276 ms 32.8 ms 成本 $0.042 / 百万 token 自托管 $0 乍一看，Laya 几乎全面领先。但如果只看这张表就下结论，很容易踩坑。\n2. 解读数据时必须注意的五点 第一，Jev 的数据并非同场实测。 Laya 作者明确说明，自己没有 TypeSafe 的 API 权限，表中 Jev 的数字全部引用自第三方公开的结果，样本量和提示词都不相同。\n第二，Laya 的高分来自针对性微调。 typed-decisions 上的 0.766 是 laya-typed-decisions 在该基准自己的训练集上微调后取得的成绩。未经微调的两个基础检查点只有 0.34～0.36，接近随机猜测（0.318），甚至低于“每题都猜最常见类别”的基线（0.461），而 Jev 不做任何训练就能拿到 0.727。作者自己也说得很直白：Laya 是一个适合特化的快速底座，而不是零样本决策引擎。 换句话说，这张表比的其实是“特化后的专才”和“零样本的通才”。\n第三，校准优势需要额外的温度拟合。 表中 0.081 的 ECE 是用领域留出数据重新拟合温度之后的结果，出厂时为 0.466。而在同一个 typed-decisions 基准上，Laya 微调检查点的 ECE 为 0.213，反而高于 Jev 的 0.144；在衡量概率分布与参考分布吻合程度的“软准确率”上，Jev 也以 0.580 领先 Laya 的 0.471。当然 Jev 也并非完美：Laya 作者引用的数据显示，在 DAIR Emotion 上，Jev 对 16% 的样本给真实标签分配了 0 概率，这对依赖置信度做分流的系统来说是个硬伤。\n第四，延迟对比的本质是部署方式的差异。 Jev 的延迟包含从客户端到 TypeSafe 云端的网络往返（TypeSafe 的评测大多从美国西海岸发起），而 Laya 的 32.8 ms 是在本地 T4 GPU 上的推理耗时。对于国内或其他地区的用户，Jev 的实际延迟还要叠加跨境网络的开销；反过来，如果把 Laya 部署成远程服务，同样要算上网络开销。\n第五，Jev 自己的宣传数据同样需要打折。 TypeSafe 官网上“快 193.6 倍、便宜 444.6 倍”的数字来自其团队设计的 4 套工作流评测，参考答案取自 GPT-6 Astra 和 Fable 5.1 两个前沿模型的平均结果，官方也坦言这些数字处于真实收益的上限。自动化服务公司 AY Automate 发布的第三方测试则显示：Jev 比几款对照模型快约 2～3.6 倍；成本方面，相比便宜的小模型约节省 4.7～7.5 倍，相比昂贵的前沿模型才能达到 40～49 倍。\n3. 社区实测 发布后短短几天，社区就做了不少对比测试，大多集中在高频交互场景：\n贪吃蛇：在同一套决策逻辑下，本地运行的 421M Laya 在 30 秒内平均每秒决策 86.5 次，P50 延迟约 9 ms；云端 Jev 每秒只能决策 3.2 次，API 往返约 317 ms M5 Pro 测试：两者的中位耗时分别为 15.3 ms 和 298.1 ms，差距接近 20 倍 Apple Silicon 移植：社区项目 laya-mlx 把模型移植到 MLX，运行时最高约占 1GB 内存，在 M3 Max 上运行贪吃蛇可以达到每秒 60 次决策 这些测试体现的主要是“本地推理”与“云端往返”的差异。对于每秒需要几十次决策的实时场景，这个差距是决定性的；但对于一次工单分流来说，30 ms 和 300 ms 的区别，用户几乎感知不到。\n4. 小结 比较公允的结论是：在开箱即用、长上下文、选项复杂的场景下，Jev 更强；在有标注数据的垂直任务上，经过微调的 Laya 可以做到更快、更准、更便宜。\nJev 的优势与劣势 优势 开箱即用，零样本能力强：不需要任何训练数据，写好 state 和问题就能直接使用。TypeSafe 称 Jev 在 System One 类任务上达到了与现有前沿 LLM 相近的智能水平；Karpathy 也评价说，它处在 LLM 帕累托曲线上一个长期被低估的位置：不需要复杂思考，只需要在低延迟下给出足够可靠的判断。 长上下文与高基数选项：64k 的上下文可以直接放入长邮件串、合同条款或 Agent 执行轨迹；choice 最多支持 255 个选项，在 Banking77 这类细粒度意图分类上仍有 0.870 的准确率。 出厂即校准：不需要自己拟合温度，返回的概率可以直接用于阈值分流；官方文档还给出了按风险等级设置不同阈值的最佳实践。 免运维，生态成熟：托管服务加官方 SDK，已经接入 LangChain、Vercel AI Gateway 等生态，团队不需要操心 GPU、扩缩容和模型升级。 单价极低：按每次 1000 个输入 token 估算，100 万次判断约 42 美元，对大多数业务来说，模型费用已经不是瓶颈。 劣势 闭源黑盒：架构、参数量和训练数据都没有公开。决策模型本身也不输出解释，正如 Simon Willison 所提醒的，Jev 最终留给开发者的可能只有一个数字，判断出错时很难追问原因。 数据必须出域：所有 state 都要发送到 TypeSafe 的云端，零数据保留（ZDR）仅面向企业客户，这对金融、医疗、政企等场景是硬伤。另据媒体报道，Jev 目前尚未面向中国大陆开放。 网络延迟无法消除：几百毫秒的往返对于业务工作流已经足够快，但对于游戏 AI、实时风控这类每秒几十次决策的场景仍然太慢。 无法微调：所有客户共用同一套权重，只能通过 state、instructions 和 criteria 来“调教”，在高度垂直的领域可能达不到专用模型的效果。 英语优先：官方明确英语是主要训练语言，中文场景一定要先用自己的数据验证。 早期产品的不确定性：速率限制仍在动态调整；官方预计价格只降不升，但也坦言暂时无法证明当前价格没有补贴；jev-latest 别名会随新版本移动，调好的阈值可能需要重新标定；此外还有厂商锁定的风险。 已知的能力“锯齿”：官方文档专门列出了 Jev 1.13 的多种失效模式，包括过于字面地理解问题、不擅长计数和数学运算、日期比较不可靠、多跳的间接推理效果差、state 中无关内容过多会降低准确率、容易被 state 中的对抗内容（如提示词注入）带偏等。甚至一些“常识性”的一致性也无法保证：对同一个问题和它的否定分别提问，两个 noul 的概率之和可能是 1.19，而不是 1。 Laya 的优势与劣势 优势 完全开源：Apache 2.0 协议，架构、训练方法、评测脚本和微调 Notebook 全部公开，可审计、可复现、可二次开发，没有厂商锁定。 数据不出域：可以部署在本地、私有云甚至离线环境中，天然满足数据驻留和隐私合规的要求。 极低延迟、高吞吐：T4 上单个问题约 33～40 ms，多语言检查点批量推理时每题约 7 ms，单卡吞吐约 100～330 题/秒，在实时交互类应用中优势明显。 边际成本接近零：按单张 T4 每秒 100～300 题估算，100 万次判断只需要 1～3 个 GPU 小时。 可微调、可特化：在自有数据上微调后可以超越 Jev（typed-decisions 上 0.766 对 0.727），而且整个流程在 Kaggle 的免费 GPU 上就能完成。 多语言与自动路由：laya-multilingual 覆盖 100 多种语言，Router 会在推理前自动选择检查点，对中文等非英语场景更友好（但仍需自测）。 面向生产的设计：Act / Escalate 头可以直接给出“自动执行还是升级人工”的建议；内置了护栏、审核、分诊等预设；文档还坦诚地列出了自身的各项短板。 劣势 零样本能力弱：基础检查点在 typed-decisions 上接近随机水平，想要好的效果，就必须准备标注数据并进行微调。没有数据的团队拿到手的，基本上只是一个“待训练的底座”。 上下文短：英文检查点默认只有 512 token（留给 state 的约 320 token），多语言和 typed-decisions 检查点为 1024 token（留给 state 的约 768 token）。长文档、长对话需要先做截断、摘要或分块。编码器最高支持 8192 token，但调大之后延迟会上升，效果也需要自行验证。 高基数选项吃力：所有选项共享一个固定的 token 预算，77 个选项时每个选项只能分到 3～4 个 token，Banking77 的准确率随之跌到 0.425。需要调大 head_max_len、先用 embedding 做候选筛选（predict_shortlist），或者拆成“粗分类 + 细分类”两级问题。 校准需要额外工作：出厂的检查点偏向“过度自信”，需要用领域内的留出数据拟合温度（ECE 可以从 0.466 降到 0.081）；多语言检查点出厂时则完全没有拟合温度。 score 是最弱的原语：有序打分任务的表现最差，例如 SST-5 五级情感分类的准确率只有 0.372。 语言陷阱：英文检查点在非拉丁文字上会高置信度地犯错，必须使用 Router 或手动选对检查点。另外，默认的懒加载模式下切换语言会触发 7～10 秒的模型重载，生产环境需要开启预加载。 运维与项目成熟度：需要自己负责 GPU 资源、扩缩容、监控和模型版本管理；项目刚刚发布，主要由独立研究者维护，长期演进存在不确定性。 适用场景与选型建议 1. 场景对照表 场景 推荐 理由 快速验证 PoC，手上没有标注数据 Jev 零样本即可使用，几分钟就能接入 细粒度意图分类（几十上百个选项） Jev 最多支持 255 个选项；Laya 需要额外做候选筛选或分级设计 长文本判断（合同、长邮件串、Agent 执行轨迹） Jev 64k 上下文，而 Laya 默认只有 512 / 1024 云端 Agent 的工具选择、完成判定、重试决策 Jev 通用性强，与 LangChain、Vercel 等生态集成度高 数据不能出域（金融、医疗、政企、境内业务） Laya 本地部署，数据不离开自己的环境 高频实时决策（游戏 AI、实时风控、端侧应用） Laya 毫秒级本地推理，没有网络往返 超大规模批处理（海量分类、打标、特征提取） Laya 边际成本接近零 有大量标注数据的垂直任务（垃圾邮件、钓鱼检测、工单分诊、内容审核） Laya（微调后） 特化之后更快、更准、更便宜 多语言，尤其是中文等非拉丁文字 Laya（多语言检查点 + Router） Jev 以英语为主；但两者都需要用自己的数据验证 网关层的提示词护栏 Laya 本地低延迟，适合作为第一道过滤；但需要配合其他防护手段 关于最后一点再多说一句：Laya 在未参与训练的提示词注入测试集上的准确率约为 0.698，Jev 的官方文档也承认 state 中的对抗内容可能带偏判断。决策模型可以作为一道低成本的前置过滤，但不能作为唯一的安全防线。\n2. 选型时要回答的几个问题 可以按下面的顺序依次回答：\n1. 数据能否发送到第三方云（以及境外）？ └─ 不能 → Laya（硬约束，其他因素都要让路） 2. 是否有标注数据，并且愿意投入微调？ └─ 没有 → Jev 3. 是否需要每秒几十次以上的实时决策，或者要在端侧运行？ └─ 是 → Laya 4. state 是否很长（超过 1k token），或者选项是否很多（超过 20 个）？ └─ 是 → Jev（或 Laya + 分块 / 候选筛选） 5. 月调用量是否达到数千万级？ └─ 是 → 倾向 Laya；否则 Jev 的总拥有成本通常更低 关于成本，可以简单算一笔账：假设每次判断消耗 1000 个输入 token，Jev 每 100 万次判断约 42 美元；一台 T4 实例全天候运行，按每小时约 0.5 美元估算，每月约 360 美元，相当于 Jev 约 850 万次判断的费用，这还没算高可用冗余和运维人力。所以单纯从成本角度看，只有当调用量足够大时，自托管 Laya 才有明显优势。更多时候，选择 Laya 的理由应该是延迟、合规和可定制性，而不仅仅是省钱。\n3. 组合使用：不必二选一 在实际项目中，两者完全可以组合使用：\n阶段式迁移：先用 Jev 快速上线，验证问题拆解、选项设计和置信度阈值是否合理，同时沉淀真实数据和人工复核的标签；当调用量上来或合规要求收紧时，再用这些人工标签微调 Laya，迁移到自托管。由于两者的请求格式（state + questions，以及 choice / score / noul 的 instructions、criteria 结构）几乎一致，迁移成本很低。需要注意的是，如果打算直接把 Jev 的输出当作训练标签，请先确认 TypeSafe 的服务条款；Laya 作者也提醒过，用模型生成的标签来训练，只会把新模型校准到“老师”的错误上。 分层级联：本地 Laya 处理高频、简单、短文本的判断；低置信度或长上下文的请求升级给 Jev；Jev 仍然拿不准的，再交给推理型大模型或人工处理。 按数据敏感度路由：涉及个人隐私或敏感业务数据的请求走本地 Laya，其余请求走 Jev。 4. 其他选择 除了这两者，社区还出现了一条“零训练复刻”的路线：直接读取现成开源 LLM 在候选项上的 token 概率来模拟 Jev 的输出。例如 SemIf 用一个现成的 4B 开源模型直接读取概率，据称与 Jev 的结论一致率在 85% 左右；Kev 则基于 Qwen 3.5 制作了 0.8B、4B 和 9B 三个 Jev-like 模型。这类方案在门槛和效果上介于两者之间，适合想先低成本尝鲜的团队。\n代码示例：同一组问题，两种调用方式 由于 Jev 和 Laya 的问题格式几乎一致，同一组 state 和 questions 可以同时用于两者：\nstate = { \u0026#34;message\u0026#34;: \u0026#34;I was charged twice for order A-104. Please refund the duplicate.\u0026#34;, \u0026#34;order\u0026#34;: {\u0026#34;charges\u0026#34;: [49, 49]}, } questions = { \u0026#34;department\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;choice\u0026#34;, \u0026#34;instructions\u0026#34;: \u0026#34;Which team should handle this request?\u0026#34;, \u0026#34;criteria\u0026#34;: { \u0026#34;billing\u0026#34;: \u0026#34;Payments, invoices, and refunds\u0026#34;, \u0026#34;technical\u0026#34;: \u0026#34;Bugs and integrations\u0026#34;, \u0026#34;account\u0026#34;: \u0026#34;Account access and profile issues\u0026#34;, }, }, \u0026#34;urgency\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;score\u0026#34;, \u0026#34;instructions\u0026#34;: \u0026#34;How urgent is this request?\u0026#34;, \u0026#34;criteria\u0026#34;: [\u0026#34;Not urgent\u0026#34;, \u0026#34;Soon\u0026#34;, \u0026#34;Critical or blocking\u0026#34;], }, \u0026#34;refund_requested\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;noul\u0026#34;, \u0026#34;instructions\u0026#34;: \u0026#34;Does the message request a refund?\u0026#34;, }, } 调用云端的 Jev（固定版本号，避免别名升级后调好的阈值失效）：\nimport os import requests resp = requests.post( \u0026#34;https://api.typesafe.ai/v1/systemone\u0026#34;, headers={\u0026#34;Authorization\u0026#34;: f\u0026#34;Bearer {os.environ[\u0026#39;TYPESAFE_API_KEY\u0026#39;]}\u0026#34;}, json={\u0026#34;model\u0026#34;: \u0026#34;jev-1.13.0\u0026#34;, \u0026#34;state\u0026#34;: state, \u0026#34;questions\u0026#34;: questions}, timeout=5, ) resp.raise_for_status() answers = resp.json()[\u0026#34;answers\u0026#34;] 在本地运行 Laya：\nfrom laya import Router # 生产环境建议预加载，避免切换语言时重新加载模型 router = Router(preload=True) answers = router.predict(state, questions)[\u0026#34;answers\u0026#34;] 两者返回的结构基本相同：choice 包含 choice、probabilities 和 confidence，score 包含期望分数、等级概率分布和 confidence，noul 则直接返回“是”的概率。后续的置信度分流逻辑可以完全复用：\ndept = answers[\u0026#34;department\u0026#34;] if dept[\u0026#34;confidence\u0026#34;] \u0026gt;= 0.9: route_to_team(dept[\u0026#34;choice\u0026#34;]) # 高置信度：自动执行 elif dept[\u0026#34;confidence\u0026#34;] \u0026gt;= 0.6: review_with_llm(state, dept) # 中置信度：交给更强的模型复核 else: send_to_human(state) # 低置信度：转人工 这里的 0.9 和 0.6 只是示意，真实的阈值一定要用自己的数据来标定。\n落地时的共同注意事项 无论选择哪一个，以下几点都同样适用：\n类型安全 ≠ 判断正确：校准描述的是一组预测的统计性质，并不保证单个答案正确，阈值必须用自己的数据标定。 把问题拆小：不要问“这张工单该怎么处理”，而是拆成“是否申请退款”“紧急程度如何”“属于哪条业务线”等原子问题，再在代码里组合。 计算交给代码：计数、数学运算、日期比较等确定性逻辑不要交给模型。 精简 state：只放与问题相关的字段，无关内容会降低准确率；Laya 的上下文更短，这一点尤为关键。 不可逆操作要兜底：付款、删除数据、医疗或合规审批等操作，不能仅凭一个概率就自动执行。 防范注入：state 虽然是数据，但其中的恶意内容仍可能影响判断，不要把决策模型当作唯一的安全防线。 固定版本，持续监控：固定模型版本（如 jev-1.13.0 或指定的 Laya 检查点），升级后重新标定阈值；上线后持续记录概率分布、抽样复核，并监控校准漂移。 总结 用一句话概括：\nJev 是“开箱即用的云端通才”，Laya 是“可以特化的本地专才”。\n如果你追求快速上线、手上没有标注数据，或者面对长上下文和复杂的选项空间，Jev 是更省心的选择； 如果你有数据、有合规要求、需要毫秒级的实时决策或海量调用，并且有能力运维一个小模型，Laya 更值得投入。 两者并不是替代关系，而是同一条“决策光谱”上的不同档位。更值得关注的是这场对决背后的趋势：AI 系统正在从“一个大模型包打天下”走向分工协作，代码负责骨架，决策模型负责快速判断，大模型负责深度思考。Jev 定义了这个品类，Laya 则在三天之内证明了开源社区可以迅速跟上。对开发者来说，这意味着工具箱里又多了一种更快、更便宜、也更可控的构建模块。\n参考资料 TypeSafe AI：Introducing System One Models \u0026amp; Jev TypeSafe 文档：Models TypeSafe 文档：Jev 1.13 jaggedness TypeSafe 文档：Confidence TypeSafe Workflow Evals Laya GitHub 仓库 Laya Hugging Face 模型页 Nandakishor M：I Built Non-Autoregressive Decision Models a Year Ago. Then a Frontier Lab Called It a \u0026ldquo;Breakthrough\u0026rdquo;. 虎嗅：号称快200倍的Jev模型，拿不下LLM的大结果 ","permalink":"https://wilsonwu.me/zh/blog/2026/jev-vs-laya/","summary":"\u003cp\u003e\u003cimg alt=\"Jev vs Laya：闭源云端与开源本地的决策模型\" loading=\"lazy\" src=\"/zh/blog/2026/jev-vs-laya/1-jev-vs-laya.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"引言\"\u003e引言\u003c/h2\u003e\n\u003cp\u003e2026 年 9 月，AI 圈突然流行起一类“不会说话”的模型。\u003c/p\u003e\n\u003cp\u003e9 月 15 日，由前 OpenAI 研究员 Diogo Almeida 创办的 TypeSafe AI 发布了 \u003cstrong\u003eJev\u003c/strong\u003e：它不聊天、不写文章、也不生成代码，只接收一段状态（state）和一组预先定义好的问题，然后直接返回带有校准概率的结构化决策。\u003c/p\u003e","title":"Jev vs Laya：闭源与开源 System One 决策模型的对比与选型"},{"content":"\n在普通 HTTP API 中，我们通常只需要限制“每分钟请求数”。但对于大模型接口，仅限制请求数量远远不够：一次请求可能只消耗几十个 Token，也可能消耗数万个 Token。\n因此，一个实用的 AI 网关通常需要同时支持：\nRPM（Requests Per Minute）：每分钟请求数。 TPM（Tokens Per Minute）：每分钟 Token 数。 按租户、API Key、模型或套餐分别计费。 多个 Envoy 实例共享同一份额度。 在高并发条件下避免各实例独立计数造成额度放大。 返回统一的 429 Too Many Requests 和剩余额度信息。 本文介绍如何使用 envoyproxy/ratelimit、Redis 和 Envoy 构建一套分布式 AI 限流系统。\n为什么不能只使用 Envoy Local Rate Limit Envoy 提供了 Local Rate Limit Filter，但它的计数器默认位于当前 Envoy 进程中。\n假设我们有 4 个 Envoy 实例，并为某个租户配置：\nRPM = 100 如果每个 Envoy 都独立维护 100 RPM，那么整个集群实际上可能放行：\n4 × 100 = 400 RPM 这显然不是我们想要的结果。\nEnvoy 的 Global Rate Limit Filter 可以把限流请求发送给外部 Rate Limit Service。envoyproxy/ratelimit 正是 Envoy 官方生态中常用的全局限流服务实现，它将计数器存放在 Redis 中。\n整体架构如下：\n这里增加了一个很薄的 AI Quota Adapter，主要用于解决两个问题：\n从请求体中解析模型、租户和 Token 数量。 分别使用不同的 hits_addend 检查 RPM 和 TPM。 为什么 AI TPM 需要额外的适配层 Envoy 的普通限流规则通常把一次 HTTP 请求视为一次命中：\nhits_addend = 1 这适合 RPM，但不适合 TPM。\n例如下面两个请求：\n请求 A：消耗 200 Token 请求 B：消耗 20,000 Token 如果两者都只增加一次计数，TPM 就没有意义。TPM 必须让一次请求按照 Token 数增加计数器：\nhits_addend = token_cost 例如：\nRPM 请求：hits_addend = 1 TPM 请求：hits_addend = 3200 Rate Limit Service 的 hits_addend 通常作用于整个 RateLimitRequest，因此 RPM 和 TPM 需要使用两次限流调用，或者由自定义适配服务组合处理。\n推荐的处理顺序是：\n检查并增加 RPM，hits_addend=1。 RPM 允许后，再检查并增加 TPM，hits_addend=token_cost。 任意一个返回 OVER_LIMIT，适配层向 Envoy 返回 429。 这种模式下，每个维度都由 Redis 原子计数，多个 Envoy 实例不会各自放大额度。\n需要注意：两次调用并不是跨计数器事务。RPM 已增加后，TPM 可能拒绝请求。因此要提前明确计数语义：\nRPM 统计所有进入限流流程的请求尝试；或者 自行扩展服务，使用一段 Redis Lua 脚本同时检查 RPM 和 TPM。 多数系统接受第一种语义，因为它更简单，也能避免恶意用户通过大量超大请求免费消耗网关资源。\n设计限流 Descriptor 限流维度通常包括：\ntenant：租户、组织或 API Key。 model：模型名称。 metric：rpm 或 tpm。 最终生成的 Descriptor 可以表示为：\ntenant = tenant-a model = gpt-4.1 metric = rpm 以及：\ntenant = tenant-a model = gpt-4.1 metric = tpm 这样可以实现：\ntenant-a + gpt-4.1 -\u0026gt; 60 RPM tenant-a + gpt-4.1 -\u0026gt; 100,000 TPM 如果不希望按模型拆分额度，也可以只保留：\ntenant + metric 例如所有模型共享同一份租户额度。\n配置 envoyproxy/ratelimit 创建 config/config.yaml：\ndomain: ai-gateway descriptors: - key: tenant descriptors: - key: model descriptors: - key: metric value: rpm rate_limit: unit: minute requests_per_unit: 60 - key: metric value: tpm rate_limit: unit: minute requests_per_unit: 100000 这里 tenant 和 model 没有指定固定值，可用于根据请求中的 Descriptor 值进行分桶。实际部署前，应使用所选 ratelimit 版本提供的配置校验工具检查配置。\n适配层发送 RPM Descriptor：\n[ {\u0026#34;key\u0026#34;: \u0026#34;tenant\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;tenant-a\u0026#34;}, {\u0026#34;key\u0026#34;: \u0026#34;model\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;gpt-4.1\u0026#34;}, {\u0026#34;key\u0026#34;: \u0026#34;metric\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;rpm\u0026#34;} ] 发送 TPM Descriptor：\n[ {\u0026#34;key\u0026#34;: \u0026#34;tenant\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;tenant-a\u0026#34;}, {\u0026#34;key\u0026#34;: \u0026#34;model\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;gpt-4.1\u0026#34;}, {\u0026#34;key\u0026#34;: \u0026#34;metric\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;tpm\u0026#34;} ] Descriptor 的顺序必须与配置中的嵌套路径一致。\n不同套餐额度 如果不同租户拥有不同额度，可以增加套餐维度：\ntenant -\u0026gt; plan -\u0026gt; model -\u0026gt; metric 例如：\ndomain: ai-gateway descriptors: - key: tenant descriptors: - key: plan value: standard descriptors: - key: model descriptors: - key: metric value: rpm rate_limit: unit: minute requests_per_unit: 60 - key: metric value: tpm rate_limit: unit: minute requests_per_unit: 100000 - key: plan value: enterprise descriptors: - key: model descriptors: - key: metric value: rpm rate_limit: unit: minute requests_per_unit: 1000 - key: metric value: tpm rate_limit: unit: minute requests_per_unit: 2000000 请求中的 tenant 用于隔离计数器，plan 用于选择额度。\n不要只使用 plan 作为计数维度，否则所有 Standard 用户可能共享同一个计数器。\n五、使用 Docker Compose 部署 下面是一个简化示例：\nservices: redis: image: redis:7-alpine command: - redis-server - --appendonly - \u0026#34;yes\u0026#34; volumes: - redis-data:/data ports: - \u0026#34;6379:6379\u0026#34; ratelimit: image: envoyproxy/ratelimit:\u0026lt;固定版本号\u0026gt; command: /bin/ratelimit environment: LOG_LEVEL: info REDIS_SOCKET_TYPE: tcp REDIS_URL: redis:6379 RUNTIME_ROOT: /data RUNTIME_SUBDIRECTORY: ratelimit RUNTIME_WATCH_ROOT: \u0026#34;false\u0026#34; USE_STATSD: \u0026#34;false\u0026#34; volumes: - ./config:/data/ratelimit ports: - \u0026#34;8081:8081\u0026#34; - \u0026#34;6070:6070\u0026#34; depends_on: - redis volumes: redis-data: 生产环境不要直接使用 master 或浮动标签，应固定到经过验证的 release 版本。\n不同版本的镜像端口和环境变量可能存在差异，因此部署时应以对应版本仓库中的示例配置为准。\n启动：\ndocker compose up -d 查看日志：\ndocker compose logs -f ratelimit 生产环境可以运行多个 ratelimit 副本：\nratelimit-1 ─┐ ratelimit-2 ─┼── Redis ratelimit-3 ─┘ ratelimit 服务本身可以保持无状态，只要所有副本：\n使用相同的配置。 指向同一个 Redis 逻辑集群。 使用一致的 Redis Key 前缀或命名空间。 AI Quota Adapter 的核心逻辑 适配层可以实现为 Envoy ext_authz 服务。它接收 Envoy 转发过来的请求信息，完成身份识别、Token 计算和额度检查。\n下面是简化后的 Go 伪代码：\nfunc CheckAIQuota( ctx context.Context, tenant string, model string, tokenCost uint32, ) error { // 第一次调用：RPM rpmResp, err := rateLimitClient.ShouldRateLimit( ctx, \u0026amp;ratelimitv3.RateLimitRequest{ Domain: \u0026#34;ai-gateway\u0026#34;, Descriptors: []*ratelimitv3.RateLimitDescriptor{ { Entries: []*ratelimitv3.RateLimitDescriptor_Entry{ {Key: \u0026#34;tenant\u0026#34;, Value: tenant}, {Key: \u0026#34;model\u0026#34;, Value: model}, {Key: \u0026#34;metric\u0026#34;, Value: \u0026#34;rpm\u0026#34;}, }, }, }, HitsAddend: 1, }, ) if err != nil { return err } if rpmResp.OverallCode == ratelimitv3.RateLimitResponse_OVER_LIMIT { return ErrRPMExceeded } // 第二次调用：TPM tpmResp, err := rateLimitClient.ShouldRateLimit( ctx, \u0026amp;ratelimitv3.RateLimitRequest{ Domain: \u0026#34;ai-gateway\u0026#34;, Descriptors: []*ratelimitv3.RateLimitDescriptor{ { Entries: []*ratelimitv3.RateLimitDescriptor_Entry{ {Key: \u0026#34;tenant\u0026#34;, Value: tenant}, {Key: \u0026#34;model\u0026#34;, Value: model}, {Key: \u0026#34;metric\u0026#34;, Value: \u0026#34;tpm\u0026#34;}, }, }, }, HitsAddend: tokenCost, }, ) if err != nil { return err } if tpmResp.OverallCode == ratelimitv3.RateLimitResponse_OVER_LIMIT { return ErrTPMExceeded } return nil } 实际代码中的 protobuf 包名和字段类型应以项目锁定的 Envoy API 版本为准。\n适配层的完整处理流程如下：\n1. 从 Authorization/API Key 中识别 tenant 2. 从 JSON 请求体中读取 model 3. 使用模型对应的 tokenizer 计算 input_tokens 4. 根据策略计算 token_cost 5. 调用 RLS 检查 RPM 6. 调用 RLS 检查 TPM 7. 都允许：返回 OK 8. 任意超限：返回 429 在 Envoy 中接入适配层 如果适配层实现 Envoy 的 ext_authz gRPC 协议，可以在 Envoy 中配置：\nhttp_filters: - name: envoy.filters.http.ext_authz typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz transport_api_version: V3 grpc_service: envoy_grpc: cluster_name: ai_quota_adapter timeout: 0.5s with_request_body: max_request_bytes: 1048576 allow_partial_message: false pack_as_bytes: true failure_mode_allow: false - name: envoy.filters.http.router typed_config: \u0026#34;@type\u0026#34;: type.googleapis.com/envoy.extensions.filters.http.router.v3.Router 对应的 Cluster：\nclusters: - name: ai_quota_adapter type: STRICT_DNS connect_timeout: 0.25s http2_protocol_options: {} load_assignment: cluster_name: ai_quota_adapter endpoints: - lb_endpoints: - endpoint: address: socket_address: address: ai-quota-adapter port_value: 9000 因为 Token 计算需要读取 JSON 请求体，所以需要配置 with_request_body。\n注意限制最大请求体，避免攻击者提交超大 Prompt 导致 Envoy 或适配层内存压力。\nTPM 应该如何计算 RPM 的定义比较简单，一次请求增加 1。\nTPM 更复杂，因为请求发出时通常只知道输入 Token，尚不知道模型最终会产生多少输出 Token。\n方案一：只计算输入 Token token_cost = input_tokens 优点：\n请求开始前就能精确计算。 实现最简单。 非流式和流式接口都适用。 缺点：\n无法限制输出 Token。 用户可能通过很短的 Prompt 请求大量输出。 适合将 TPM 定义为“输入 Token 配额”的系统。\n方案二：预留最大输出 Token token_cost = input_tokens + requested_max_output_tokens 例如：\ninput_tokens = 1200 max_tokens = 4096 token_cost = 5296 优点：\n在调用模型前就能做严格准入控制。 不会因为输出 Token 未知而突破额度。 适合流式响应。 缺点：\n实际只输出 300 Token 时仍预留了 4096 Token。 如果不实现退款，额度利用率会偏低。 这是最容易实现“严格不超额”的方案。\n网关必须对 max_tokens 设置服务端上限，不能完全信任客户端：\neffective_max_tokens = min(client_max_tokens, server_model_limit) 否则客户端可以提交异常大的 max_tokens，让自己或整个租户迅速耗尽配额。\n方案三：先预留，再按实际用量结算 完整思路是：\n请求开始时预留： input_tokens + max_output_tokens 响应结束后得到实际输出 Token。 退还未使用部分： refund = max_output_tokens - actual_output_tokens 但 envoyproxy/ratelimit 的标准限流调用主要面向正向增加计数，并不天然提供完整的“预留—提交—退款”事务模型。\n如果业务必须支持精确退款，通常需要：\n在适配层实现额外的 Redis Lua 脚本。 使用独立的预留记录和过期时间。 请求结束后进行 commit/refund。 使用请求 ID 保证幂等。 此时 envoyproxy/ratelimit 仍可负责 RPM 或基础 TPM，但结算逻辑已经属于定制配额服务。\n方案四：流式增量扣费 对于流式输出，可以：\n请求开始时扣除输入 Token。 每生成一批输出 Token，再增加一次 TPM 计数。 一旦超过额度，终止上游流。 例如每 32 或 128 个 Token 扣一次。\n这种方式需要能观察响应流，因此仅靠请求阶段的 ext_authz 不够，需要：\nEnvoy ext_proc； Wasm Filter； 自定义 HTTP Filter； 或由 AI Gateway 自身代理并读取流式响应。 增量批次越小，控制越精确，但 gRPC 和 Redis 调用次数也越多。\n多 Envoy 实例如何保证共享额度 假设一个租户拥有：\nRPM = 100 TPM = 200,000 请求可能随机落到不同 Envoy：\nRequest 1 -\u0026gt; Envoy A Request 2 -\u0026gt; Envoy B Request 3 -\u0026gt; Envoy C 但所有限流请求最终都会访问同一套 Redis 计数器：\nai-gateway / tenant-a / gpt-4.1 / rpm ai-gateway / tenant-a / gpt-4.1 / tpm 因此额度是整个集群共享的，而不是每个 Envoy 各有一份。\n可以横向扩展的部分包括：\nEnvoy。 AI Quota Adapter。 envoyproxy/ratelimit。 不能随意拆分的是同一个配额 Key 的计数状态。如果使用多个完全独立的 Redis，则必须确保相同租户、模型和指标始终路由到同一个分片，否则额度又会被放大。\n返回标准限流响应 发生超限时，适配层应向 Envoy 返回 429：\nHTTP/1.1 429 Too Many Requests Content-Type: application/json Retry-After: 12 响应体可以采用 OpenAI 风格：\n{ \u0026#34;error\u0026#34;: { \u0026#34;message\u0026#34;: \u0026#34;Rate limit exceeded for tenant tenant-a\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;rate_limit_error\u0026#34;, \u0026#34;code\u0026#34;: \u0026#34;tpm_limit_exceeded\u0026#34; } } 建议分别暴露 RPM 和 TPM 信息：\nX-RateLimit-Limit-Requests: 60 X-RateLimit-Remaining-Requests: 12 X-RateLimit-Reset-Requests: 18 X-RateLimit-Limit-Tokens: 100000 X-RateLimit-Remaining-Tokens: 24580 X-RateLimit-Reset-Tokens: 18 为了避免客户端形成同步重试风暴，SDK 应使用带随机抖动的指数退避：\nsleep = random(0, min(cap, base × 2^retry_count)) “精准限流”需要明确的边界 使用 Redis 共享计数器可以解决多实例额度放大的问题，但“精准”仍然需要明确具体含义。\n1. 它通常是固定时间窗 配置：\nunit: minute 通常表示分钟级固定时间窗，不等同于严格的滑动窗口。\n用户可能在一个窗口结束前发送 100 次请求，并在新窗口开始后立刻再发送 100 次。从任意连续 60 秒观察，流量可能接近配置值的两倍。\n因此，该方案能够保证：\n所有 Envoy 实例共享同一个固定时间窗计数器。\n但它不自动等于：\n任意连续 60 秒都绝不超过限制。\n如果业务要求严格滑动窗口，需要使用 Redis Sorted Set、滑动窗口计数器或专门的配额算法。\n2. 输出 Token 在请求开始时未知 如果 TPM 包含输出 Token，则无法在生成之前知道最终消耗量。\n所谓严格控制通常意味着：\n使用 max_tokens 预留；或者 在流式生成过程中增量扣费。 单纯在响应结束后上报实际 Token，只能用于计费和统计，不能阻止当前请求突破 TPM。\n3. RPM 和 TPM 不是一个原子事务 标准 RLS 请求中的 hits_addend 是统一的，而 RPM 和 TPM 需要不同增量，因此通常需要两次调用。\n如果必须保证“RPM 和 TPM 同时检查、同时增加、任意失败都不修改”，需要定制服务并使用单个 Redis Lua 脚本完成事务。\n生产环境中的关键问题 1. 请求重试和重复扣费 Rate Limit Service 的计数调用通常不是业务级幂等操作。\n如果适配层因为超时重试同一个请求：\n第一次调用已经写入 Redis，但响应丢失 第二次调用再次增加计数 就会产生重复扣费。\n建议：\n限流调用不要盲目自动重试。 使用 Envoy 的 x-request-id 或业务请求 ID。 对严格计费场景，在适配层中实现请求幂等记录。 幂等记录过期时间至少覆盖限流窗口和最大请求时长。 2. 故障时 Fail Open 还是 Fail Closed 当 Redis 或 ratelimit 服务不可用时，可以选择：\nFail Open：\n限流系统不可用时继续放行 优点是可用性高，缺点是可能导致成本失控。\nFail Closed：\n限流系统不可用时拒绝请求 优点是保护成本，缺点是限流组件故障会影响业务。\nAI 推理通常成本较高，面向外部用户的付费 API 更适合 Fail Closed。内部低风险环境可以考虑短时间 Fail Open，并配合熔断和告警。\n3. Tokenizer 必须与模型匹配 不同模型的 Tokenizer 可能不同。不能使用简单的字符数除以固定比例来实现严格 TPM。\n应该维护：\nmodel -\u0026gt; tokenizer 例如：\ngpt-family -\u0026gt; cl100k/o200k 类 tokenizer llama-family -\u0026gt; 对应 SentencePiece/BPE tokenizer 自定义模型 -\u0026gt; 模型实际 tokenizer.json 如果 tokenizer 不可用，可以做保守估算，但必须预留误差：\nestimated_tokens = ceil(raw_estimate × safety_factor) 4. 规范化模型名称 客户端可能使用：\ngpt-4.1 gpt-4.1-2025-04-14 my-gpt-alias 限流前应将它们映射到统一的配额名称：\nmy-gpt-alias -\u0026gt; gpt-4.1 否则用户可能通过切换别名绕过同一模型的额度。\n5. 不要信任客户端提供的 Token 数 不能让客户端直接提交：\nX-Token-Cost: 1 然后按该值扣费。\nToken 数必须由可信组件计算，或者由上游模型返回后进行校验。客户端声明只能作为参考，不能作为计费依据。\n6. 配置发布必须一致 如果多个 ratelimit 副本加载了不同配置，可能出现：\n副本 A：RPM 100 副本 B：RPM 200 即使 Redis 相同，结果也会变得不可预测。\n生产环境应：\n使用固定版本配置。 进行启动前校验。 滚动发布时保持向后兼容。 为配置添加版本号和监控指标。 监控指标 至少应监控：\nEnvoy ext_authz 调用次数。 ext_authz 超时和失败次数。 HTTP 429 数量。 上游请求数和并发数。 请求体缓冲失败次数。 AI Quota Adapter RPM 允许和拒绝次数。 TPM 允许和拒绝次数。 Token 计算耗时。 RLS gRPC 延迟。 Tokenizer 加载失败次数。 按模型和租户聚合的 Token 消耗。 重复请求或幂等命中次数。 Ratelimit OK、OVER_LIMIT、错误数量。 Redis 请求延迟。 Redis 连接池使用率。 配置加载状态。 各限流规则命中次数。 Redis CPU 和内存。 命令延迟。 连接数。 Key 数量。 Key 过期速率。 主从复制延迟。 Failover 次数。 高基数租户 ID 不适合直接作为 Prometheus Label。否则租户数量较多时会产生严重的指标基数问题。按租户的详细用量更适合写入日志、ClickHouse 或专门的计量系统。\n推荐的最终实现 对于大多数 AI API 网关，可以采用下面的组合：\nEnvoy └── ext_authz └── AI Quota Adapter ├── 解析租户和模型 ├── 使用可信 tokenizer 计算 input_tokens ├── 规范化并限制 max_tokens ├── RPM：RLS hits_addend = 1 └── TPM：RLS hits_addend = input_tokens + max_output_tokens └── envoyproxy/ratelimit └── Redis 这一方案具备以下特征：\n多 Envoy 实例共享统一额度。 RPM 和 TPM 分别计数。 支持按租户、模型、套餐隔离。 在请求进入模型服务之前完成准入控制。 通过预留最大输出 Token，保证不会因为流式输出突破 TPM。 Envoy、适配层和 ratelimit 服务都可以横向扩展。 如果还需要按照实际输出 Token 精确结算，则在此基础上增加：\n预留记录 + 响应用量采集 + commit/refund + 请求幂等 此时系统已经从“限流服务”演进为一套完整的“分布式 AI 配额与计量服务”。\n总结 envoyproxy/ratelimit 非常适合解决多 Envoy 实例之间的全局共享限流问题。它通过 Redis 集中维护计数器，避免每个网关实例独立计算额度。\n对于 AI 场景，需要在标准 HTTP 限流之上增加 Token 感知能力：\nRPM 使用 hits_addend=1。 TPM 使用 hits_addend=token_cost。 使用租户、模型和指标构建 Descriptor。 请求阶段通过输入 Token或最大输出 Token预留完成准入。 多 Envoy、多个适配层和多个 RLS 副本共享同一个 Redis 计数空间。 需要特别注意的是，分布式共享计数、固定时间窗、RPM/TPM 组合事务以及实际输出 Token 结算是四个不同问题。理解这些边界，才能构建一套既能保护模型成本、又能在高并发环境中稳定运行的 AI 限流系统。\n","permalink":"https://wilsonwu.me/zh/blog/2026/envoy-ai-ratelimit/","summary":"\u003cp\u003e\u003cimg alt=\"概览\" loading=\"lazy\" src=\"/zh/blog/2026/envoy-ai-ratelimit/1-overview.png\"\u003e\u003c/p\u003e\n\u003cp\u003e在普通 HTTP API 中，我们通常只需要限制“每分钟请求数”。但对于大模型接口，仅限制请求数量远远不够：一次请求可能只消耗几十个 Token，也可能消耗数万个 Token。\u003c/p\u003e","title":"使用 Envoy Ratelimit 构建支持 RPM/TPM 的分布式 AI 限流服务器"},{"content":"问题的背景 最近，一台部署在 Azure 上的 Linux 服务器突然出现 CPU 持续 100% 的情况。排查后发现，服务器上存在一个以 postgres 用户身份运行的恶意程序，并通过 cron 定时任务维持运行。\n这台服务器上的 PostgreSQL：\n没有通过 Azure NSG 向公网开放 5432； 没有被当前业务使用； 业务实际使用的是本机 MySQL； 但 PostgreSQL 服务仍然安装并运行； 系统中保留了 postgres 用户、主目录、cron 和历史脚本。 这次事件说明：\nPostgreSQL 没有向公网开放，并不代表服务器上与 PostgreSQL 相关的组件完全没有风险。\n需要特别说明的是，目前的证据只能证明恶意程序利用了 postgres 操作系统用户及其目录进行持久化，不能证明攻击者一定是通过 PostgreSQL 的 5432 端口进入服务器的。初始入口也可能是历史配置、SSH、Web 应用、容器、旧镜像或其他漏洞。\n如果生产环境确实需要 PostgreSQL，但没有必须自建数据库服务器的特殊要求，建议优先使用：\nAzure Database for PostgreSQL，并通过私有网络访问，关闭公共网络入口。\n这样可以减少自建数据库所带来的操作系统用户、SSH、cron、systemd、补丁、备份和主机入侵等风险。\n发现问题：服务器 CPU 持续占满 最初是在服务器上执行 htop 时发现异常。\n从截图中可以看到：\n服务器共有 4 个 CPU 核心； 4 个核心全部达到 100%； Load Average 长期维持在 4 左右； 多个进程以 postgres 用户运行； 进程名称为随机字符串 dzZ7yVrxv7； 单个进程的 CPU 使用率超过 200%； 进程占用了大量内存。 进一步执行：\nsudo ps -eo user,pid,ppid,lstart,etime,%cpu,%mem,stat,comm,args \\ --sort=-%cpu | head -20 其中一个主要进程如下：\nUSER PID PPID %CPU %MEM STAT COMMAND postgres 9918 1 377 29.8 Ssl dzZ7yVrxv7 这不是正常的 PostgreSQL 进程。\n正常情况下，PostgreSQL 进程名称通常类似：\npostgres postgres: checkpointer postgres: background writer postgres: walwriter postgres: autovacuum launcher 而本次进程具有以下异常特征：\n名称为随机字符串； 以 postgres 用户运行； CPU 使用率接近 4 个核心； 父进程已经变成 PID 1； 内存占用异常高。 因此，初步判断这不是正常数据库负载，而是疑似挖矿程序或其他恶意程序。\n分析问题：确认恶意程序来源 1. 检查进程真实文件 Linux 进程名称可以被修改或伪装，所以不能只看 COMMAND，需要检查 /proc/\u0026lt;PID\u0026gt;/exe。\n执行：\nsudo sh -c \u0026#39; echo \u0026#34;EXE:\u0026#34; ls -l /proc/9918/exe echo \u0026#34;CWD:\u0026#34; ls -l /proc/9918/cwd echo \u0026#34;CMDLINE:\u0026#34; tr \u0026#34;\\0\u0026#34; \u0026#34; \u0026#34; \u0026lt; /proc/9918/cmdline echo echo \u0026#34;CGROUP:\u0026#34; cat /proc/9918/cgroup \u0026#39; 输出如下：\nEXE: /proc/9918/exe -\u0026gt; /dev/shm/gJppJl6TbG (deleted) CWD: /proc/9918/cwd -\u0026gt; /var/lib/postgresql CMDLINE: dzZ7yVrxv7 CGROUP: 0::/system.slice/cron.service 这几项信息非常关键。\n可执行文件位于 /dev/shm 真实程序路径为：\n/dev/shm/gJppJl6TbG /dev/shm 是基于内存的临时文件系统。恶意程序经常将文件下载到这里，再从内存文件系统中执行，以减少在磁盘上的痕迹。\n原文件已经被删除 路径末尾显示：\n(deleted) 说明程序启动后，磁盘上的原文件已被删除。但由于进程仍然持有该文件，程序依然可以继续运行。\n这种“启动后删除自身”的行为是常见的恶意程序隐藏手段。\n工作目录属于 PostgreSQL 用户 进程工作目录是：\n/var/lib/postgresql 这说明恶意程序与 postgres 用户的主目录有关。\n进程由 cron 启动 cgroup 显示：\n/system.slice/cron.service 这说明程序是通过定时任务启动的。\n2. 从运行中的进程保存样本 因为原始文件已经被删除，如果立即结束进程，可能无法再提取样本。因此先从 /proc 保存程序：\nsudo mkdir -p /root/incident sudo chmod 700 /root/incident sudo cp -L /proc/9918/exe \\ /root/incident/suspect-9918.bin sudo chmod 600 \\ /root/incident/suspect-9918.bin 检查文件类型：\nsudo file /root/incident/suspect-9918.bin 结果如下：\nELF 64-bit LSB shared object, x86-64, statically linked, no section header 计算 SHA-256：\nsudo sha256sum /root/incident/suspect-9918.bin 得到：\nd32f9eef63c496a146a6a811677dd27e337db993d6f1a4b1af145c3135d391b3 该文件具有以下特征：\n随机文件名； 位于 /dev/shm； 静态链接； 没有节区表； 启动后删除原文件； 持续占用全部 CPU。 这些行为进一步证明它不是正常的 PostgreSQL 组件。\n3. 检查 PostgreSQL 用户的定时任务 由于进程是从 cron.service 启动的，继续检查 postgres 用户的 crontab：\nsudo crontab -u postgres -l 发现如下任务：\n24 * * * * /var/lib/postgresql/.dbus-XGodM5jOyL 该任务会在每小时第 24 分钟执行一次隐藏脚本：\n/var/lib/postgresql/.dbus-XGodM5jOyL 文件名使用了以点开头的隐藏形式，并模仿了 D-Bus 的命名方式，很容易被误认为系统文件。\n检查 cron 日志：\nsudo journalctl -u cron.service \\ --since \u0026#34;2026-08-15 07:15:00\u0026#34; \\ --until \u0026#34;2026-08-15 09:15:00\u0026#34; \\ --no-pager 日志中可以看到：\nAug 15 07:24:01 CRON: (postgres) CMD (/var/lib/postgresql/.dbus-XGodM5jOyL) Aug 15 08:24:01 CRON: (postgres) CMD (/var/lib/postgresql/.dbus-XGodM5jOyL) 这证明恶意脚本至少在 07:24 和 08:24 被执行过。\n4. 分析恶意启动脚本 检查文件：\nsudo file /var/lib/postgresql/.dbus-XGodM5jOyL sudo stat /var/lib/postgresql/.dbus-XGodM5jOyL sudo sha256sum /var/lib/postgresql/.dbus-XGodM5jOyL 结果显示它是一个 Bash 脚本：\nBourne-Again shell script, ASCII text executable, with very long lines 对应 SHA-256：\n36b1dcf43876f55aae2b8be7dada848b41f60ec61cd4ca7ab2a1278193f286f0 这份脚本负责下载、启动或恢复恶意程序，也是恶意进程能够周期性出现的主要持久化机制。\n如果只结束高 CPU 进程，而不清理这条 cron，恶意程序会在下一个小时的第 24 分钟再次出现。\n5. 在用户目录中发现第二个恶意下载器 停止并卸载 PostgreSQL 后，我继续检查 /var/lib/postgresql 的残留文件：\nsudo find /var/lib/postgresql \\ -xdev -maxdepth 10 -ls 发现了另一个可疑文件：\n/var/lib/postgresql/.config/systemd/user/ └── systemd-tmpfiles-cleanup/ └── systemd-tmpfiles-cleanup-TNWow7.sh 这个文件使用了：\nsystemd-tmpfiles-cleanup 作为目录名，试图伪装成正常的 systemd 临时文件清理组件。\n检查文件类型和哈希：\nsudo file \\ /var/lib/postgresql/.config/systemd/user/systemd-tmpfiles-cleanup/systemd-tmpfiles-cleanup-TNWow7.sh sudo sha256sum \\ /var/lib/postgresql/.config/systemd/user/systemd-tmpfiles-cleanup/systemd-tmpfiles-cleanup-TNWow7.sh 结果为：\nBourne-Again shell script, ASCII text executable, with very long lines SHA-256：\n9962de5e0c68ea6e85879c5f62f2d0806360dbe186dc15e1d59abf8e4209de11 脚本内容经过 Base64 编码，末尾直接执行：\nbase64 -d | bash 解码后的逻辑显示，它具备以下行为：\n通过 Tor、SOCKS5 或 Tor2web 连接外部地址； 获取服务器公网 IP； 收集当前用户名、主机名和内核版本； 读取机器 ID； 从外部下载其他程序； 在 /dev/shm、/tmp、/var/tmp 等目录测试和执行文件； 启动程序后删除临时文件。 这已经不只是一个单纯的高 CPU 进程，而是一套包含下载、执行、隐藏和持久化能力的恶意程序。\n为什么 PostgreSQL 没有开放公网端口，仍然存在风险？ Azure 网络安全组当时只允许公网访问：\nTCP 22； TCP 80； TCP 443。 并没有允许公网直接访问 PostgreSQL 的 5432 端口。\n但检查服务器本机监听状态时发现：\nsudo ss -lntp | grep \u0026#39;:5432\u0026#39; 输出为：\nLISTEN 0 244 0.0.0.0:5432 0.0.0.0:* LISTEN 0 244 [::]:5432 [::]:* 这说明 PostgreSQL 在操作系统层面监听了所有 IPv4 和 IPv6 网卡。\n虽然 Azure NSG 没有向公网放行 5432，但以下风险依然存在。\n1. 同一虚拟网络中的主机仍可能访问 Azure NSG 默认存在 AllowVnetInBound 规则，同一虚拟网络中的其他服务器可能访问该端口。\n如果同一 VNet 内的其他服务器被入侵，攻击者就可能通过内网继续攻击数据库服务器。\n2. 本机应用或容器仍可能访问 即使公网无法连接，服务器本机运行的以下组件仍然可能访问 PostgreSQL：\nWeb 应用； Docker 容器； CI/CD Agent； 其他被攻陷的服务； 本机恶意程序。 网络安全组无法阻止服务器本机进程访问本机数据库。\n3. 风险不只来自数据库端口 本次恶意程序的实际持久化位置是：\npostgres 用户的 crontab； /var/lib/postgresql 用户目录； 用户级 systemd 配置目录； /dev/shm 临时执行目录。 即使 PostgreSQL 服务停止，只要这些用户、目录和定时任务仍然存在，恶意程序就可能继续运行。\n所以，数据库的安全边界不只是 5432 端口，还包括：\n操作系统账户； 数据目录； 用户主目录； cron； systemd； SSH； 软件包； 配置文件； 历史镜像和备份。 4. 未使用的软件往往更容易被忽略 这台服务器的业务实际使用 MySQL，PostgreSQL 并没有业务用途。\n由于“不使用”，它反而容易长期处于以下状态：\n没有人检查配置； 没有人检查系统用户； 没有人检查数据库日志； 没有人检查用户的 cron； 没有人检查相关目录； 没有人确认是否仍然需要； 没有人关注其补丁和版本。 因此：\n未使用但仍然安装和运行的软件，并不是备用能力，而是未被管理的攻击面。\n解决问题：清除恶意程序并卸载 PostgreSQL 1. 暂停恶意进程 取证完成后，先暂停进程：\nsudo kill -STOP 9918 检查进程状态：\nps -p 9918 \\ -o user,pid,ppid,%cpu,%mem,stat,comm,args 状态中出现 T，说明进程已暂停。\n暂停的好处是：\n立即停止继续占用 CPU； 暂时停止潜在网络行为； 保留进程，便于继续调查； 避免在完成取证前丢失内存中的样本。 2. 备份并删除恶意 cron 先保存 crontab：\nsudo crontab -u postgres -l \\ \u0026gt; /root/incident/postgres-crontab.txt 然后删除：\nsudo crontab -u postgres -r 确认已删除：\nsudo crontab -u postgres -l 预期输出：\nno crontab for postgres 3. 隔离恶意启动脚本 先备份脚本：\nsudo cp -a \\ /var/lib/postgresql/.dbus-XGodM5jOyL \\ /root/incident/cron-payload 再从原位置移走：\nsudo mv \\ /var/lib/postgresql/.dbus-XGodM5jOyL \\ /root/incident/original-dbus-XGodM5jOyL 限制证据文件权限：\nsudo chown root:root \\ /root/incident/original-dbus-XGodM5jOyL \\ /root/incident/cron-payload sudo chmod 600 \\ /root/incident/original-dbus-XGodM5jOyL \\ /root/incident/cron-payload 4. 结束恶意进程 确认恶意 cron 和启动脚本已隔离后，结束进程：\nsudo kill -KILL 9918 检查进程是否消失：\nps -p 9918 \\ -o user,pid,ppid,%cpu,%mem,stat,comm,args 随后查看系统负载：\nuptime 清理后的负载变为：\nload average: 0.36, 2.71, 3.68 1 分钟负载已经从 4 以上下降到 0.36。\n5 分钟和 15 分钟负载仍然较高是正常的，因为它们反映的是一段时间内的平均值，会逐渐下降。\n5. 停止并禁用 PostgreSQL 因为业务并不使用 PostgreSQL，所以先停止并禁止开机启动：\nsudo systemctl disable --now postgresql 确认服务状态：\nsystemctl status postgresql --no-pager 确认端口不再监听：\nsudo ss -lntp | grep \u0026#39;:5432\u0026#39; \\ || echo \u0026#34;5432 端口未监听\u0026#34; 最终结果为：\n5432 端口未监听 6. 卸载 PostgreSQL 确认业务没有受到影响后，卸载 PostgreSQL 相关软件包：\nsudo apt-get purge -y \\ postgresql \\ postgresql-14 \\ postgresql-client-14 \\ postgresql-client-common \\ postgresql-common \\ postgresql-contrib 需要注意：\n卸载软件包不等于完成恶意程序清理。\n还应继续检查以下位置：\n/var/lib/postgresql /etc/postgresql /etc/postgresql-common /var/spool/cron /tmp /var/tmp /dev/shm /home/*/.config/systemd 本次发现的第二个恶意下载器，就是在卸载 PostgreSQL 后检查残留目录时找到的。\n7. 隔离第二个恶意下载器 将伪装成 systemd 清理任务的脚本移入证据目录：\nsudo mv \\ /var/lib/postgresql/.config/systemd/user/systemd-tmpfiles-cleanup/systemd-tmpfiles-cleanup-TNWow7.sh \\ /root/incident/systemd-tmpfiles-cleanup-TNWow7.sh 限制权限：\nsudo chown root:root \\ /root/incident/systemd-tmpfiles-cleanup-TNWow7.sh sudo chmod 600 \\ /root/incident/systemd-tmpfiles-cleanup-TNWow7.sh 然后搜索是否存在同类文件：\nsudo grep -RIlE \\ \u0026#39;TNWow7|XGodM5jOyL|tor2socks|tor2web|relay\\.tor2socks\u0026#39; \\ /etc \\ /var/spool/cron \\ /var/lib/postgresql \\ /tmp \\ /var/tmp \\ /dev/shm \\ /home \\ 2\u0026gt;/dev/null Azure 网络安全配置整改 1. 不需要删除不存在的 5432 入站规则 Azure NSG 中原本没有允许公网访问 5432 的规则，所以不需要专门删除 PostgreSQL 入站规则。\n停止并卸载 PostgreSQL 后，本机也已经不再监听 5432。\n但是，不能因为 NSG 没有开放 5432 就认为服务器已经安全。\n2. 收紧 SSH 入站范围 排查过程中，SSH 日志中出现了多个公网 IP 对 postgres 用户进行密码尝试：\nFailed password for postgres from 172.83.83.194 Failed password for postgres from 101.36.110.41 这些记录显示的是失败登录，不能说明对方已经通过 SSH 登录成功。但它们说明服务器的 SSH 正在被公网持续扫描。\n原 Azure SSH 规则为：\nSource: Any Destination port: 22 Action: Allow 建议改为：\nSource: 管理员固定公网 IP/32 Destination port: 22 Protocol: TCP Action: Allow 更安全的方式包括：\nAzure Bastion； VPN； Just-In-Time VM Access； 禁止 SSH 密码登录； 只使用 SSH Key； 禁止 root 远程登录； 配置失败登录告警。 3. 限制服务器出站访问 恶意脚本需要连接外部地址下载程序，而当时 Azure NSG 允许任意出站访问。\n如果业务条件允许，应限制服务器的出站范围：\n只允许必要的 DNS； 只允许必要的软件源； 只允许业务需要访问的 API； 使用 Azure Firewall 或集中式代理； 记录 DNS 和出站网络日志； 对 Tor、代理、矿池和异常域名建立告警。 仅限制入站流量是不够的。恶意程序一旦进入服务器，通常还需要通过出站网络下载载荷、连接控制服务器或矿池。\n为什么建议使用 Azure 托管 PostgreSQL？ 如果业务确实需要 PostgreSQL，建议优先使用：\nAzure Database for PostgreSQL 而不是直接在 Azure VM 上安装 PostgreSQL。\n1. 减少操作系统层面的攻击面 在 VM 上自建 PostgreSQL，需要自行维护：\nLinux 操作系统； PostgreSQL 软件包； postgres 系统用户； SSH； cron； systemd； 数据目录权限； 安全补丁； 版本升级； 网络防火墙； 日志； 备份； 高可用； 恶意进程检测。 使用 Azure 托管 PostgreSQL 后，用户不会直接管理数据库所在主机的 root 权限，也不会在数据库主机上创建 cron、systemd 服务或运行 /dev/shm 中的程序。\n这可以显著减少本次事件中出现的主机级风险。\n2. 使用私有网络，关闭公网访问 建议采用以下网络架构：\nAzure VM / App Service / AKS | Private Network | Azure Database for PostgreSQL 数据库应配置为：\n禁止公共网络访问； 使用 Private Endpoint 或私有网络集成； 只允许应用所在子网访问； 不向公网开放数据库端口； 数据库管理也通过 VPN、Bastion 或受控网络完成。 3. 自动备份和维护 Azure 托管 PostgreSQL 可以提供：\n自动备份； 时间点恢复； 高可用选项； 数据加密； 监控指标； 日志与告警； 平台维护； 安全更新； 版本支持。 这些能力可以降低因长期无人维护而产生的风险。\n4. 责任边界更清晰 自建 PostgreSQL 经常出现这种情况：\n为了测试临时安装，后来业务不用了，但服务、用户和目录一直留在服务器上。\n托管数据库的资源边界更加清晰：\n需要时创建； 通过私有网络连接； 不再使用时导出数据； 删除数据库实例； 删除访问权限和私有连接； 不会在业务 VM 上留下数据库系统用户和历史脚本。 5. 托管服务并不等于绝对安全 Azure 托管 PostgreSQL 可以减少操作系统和基础设施维护风险，但仍需要正确管理：\n数据库账号； 强密码或身份认证； 最小权限； 网络访问范围； SQL 注入； 连接字符串； Azure Key Vault； 日志审计； 数据加密； 应用依赖； 凭据轮换。 正确的理解是：\nAzure 托管 PostgreSQL 缩小了需要自己负责的安全范围，但不会消除应用和账号层面的安全责任。\n本次事件的 IOC 以下指标可用于内部服务器排查。\n可疑进程及文件名 dzZ7yVrxv7 gJppJl6TbG .dbus-XGodM5jOyL systemd-tmpfiles-cleanup-TNWow7.sh SHA-256 恶意 ELF 程序：\nd32f9eef63c496a146a6a811677dd27e337db993d6f1a4b1af145c3135d391b3 恶意 cron 启动脚本：\n36b1dcf43876f55aae2b8be7dada848b41f60ec61cd4ca7ab2a1278193f286f0 恶意下载器：\n9962de5e0c68ea6e85879c5f62f2d0806360dbe186dc15e1d59abf8e4209de11 行为特征 以 postgres 用户运行； 从 /dev/shm 执行随机名称文件； 启动后删除原文件； 利用用户 crontab 定时启动； Base64 解码后直接交给 Bash 执行； 使用 Tor、SOCKS5 或 Tor2web； 收集公网 IP、用户名、主机名和机器 ID； 从外部下载其他程序； 在 /tmp、/var/tmp 和 /dev/shm 中执行程序； 持续占用全部 CPU； 伪装成 D-Bus 或 systemd 组件。 最终总结 本次事件从表面上看，是一次服务器 CPU 占用过高的问题。\n但深入排查后发现，真正的问题包括：\n以 postgres 用户运行的恶意程序； 位于 /dev/shm 的随机 ELF 文件； postgres 用户的恶意 cron； /var/lib/postgresql 下的隐藏启动脚本； 伪装成 systemd 组件的恶意下载器； 可连接 Tor 和外部下载地址的恶意逻辑。 最值得关注的是：\n这台服务器没有通过 Azure NSG 向公网开放 PostgreSQL 的 5432 端口，而且业务也没有使用 PostgreSQL，但与 PostgreSQL 有关的系统用户、主目录、cron 和残留文件仍然构成了安全风险。\n当然，现有证据并不能证明初始入侵入口就是 PostgreSQL。恶意程序使用 postgres 用户运行，不等于攻击者一定通过 PostgreSQL 端口进入服务器。\n但这次事件依然说明了几个重要原则。\n第一，不使用的服务应立即停止和卸载 不要因为“没有向公网开放”就长期保留闲置服务。\n不使用的软件应当：\n停止服务； 禁止开机启动； 卸载软件包； 检查系统用户； 检查 cron； 检查 systemd； 检查残留目录和脚本。 第二，云安全组不是唯一安全边界 NSG 没有开放 5432，只能说明公网无法通过该规则直接连接数据库，不能防止：\n内网访问； 本机访问； 容器访问； 历史持久化； 用户级 cron； 本机恶意程序； SSH 和应用漏洞。 第三，生产 PostgreSQL 优先使用 Azure 托管服务 如果没有必须自建数据库服务器的明确需求，建议使用：\nAzure Database for PostgreSQL + 私有网络 + 禁止公共访问。\n这样可以减少数据库主机操作系统、SSH、系统用户、cron、systemd、补丁、备份和高可用等方面的维护风险。\n第四，杀掉恶意进程不等于完成安全恢复 结束高 CPU 进程只解决了当前资源占用问题，还应继续：\n查找持久化； 调查入侵入口； 检查 SSH 成功登录记录； 检查 Web 应用和容器； 轮换服务器上的全部凭据； 检查同一 VNet 中的其他主机； 保存日志和磁盘快照； 必要时从可信镜像重建服务器。 最终可以将这次事件概括为一句话：\n不使用的数据库不要留在服务器上；需要 PostgreSQL 时，优先使用 Azure 托管 PostgreSQL，并通过私有网络访问。\n","permalink":"https://wilsonwu.me/zh/blog/2026/azure-managed-postgresql-vs-self-hosted-security/","summary":"\u003ch2 id=\"问题的背景\"\u003e问题的背景\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"PostgreSQL 安全问题\" loading=\"lazy\" src=\"/zh/blog/2026/azure-managed-postgresql-vs-self-hosted-security/1-overview.png\"\u003e\u003c/p\u003e\n\u003cp\u003e最近，一台部署在 Azure 上的 Linux 服务器突然出现 CPU 持续 100% 的情况。排查后发现，服务器上存在一个以 \u003ccode\u003epostgres\u003c/code\u003e 用户身份运行的恶意程序，并通过 \u003ccode\u003ecron\u003c/code\u003e 定时任务维持运行。\u003c/p\u003e","title":"使用 Azure 托管 PostgreSQL 降低自建数据库的安全风险"},{"content":"\n随着 OpenWebUI 中的用户、对话、文件和知识库数据不断增长，原先部署在 Azure VM 上的自建 PostgreSQL 带来了越来越多的运维工作，包括数据库补丁、备份、故障恢复、存储监控和安全维护。\n为了降低维护成本并提高数据库可靠性，我将 OpenWebUI 的业务数据库从 VM 本地 PostgreSQL 迁移到了 Azure Database for PostgreSQL Flexible Server。\n这次迁移采用了：\n在线全量迁移 + 数据一致性验证 + 活跃对话增量同步 + 快速切换\n全量数据库导出和恢复期间，OpenWebUI 始终保持在线。最终切换仅涉及一次很短的 Docker 容器重建，用户侧基本无感。\n本文中的服务器地址、数据库名称、用户名、密码和资源标识均已隐藏或替换为占位符。\n迁移背景 OpenWebUI 以 Docker 容器形式运行在 Azure VM 中，最初使用安装在同一台 VM 上的 PostgreSQL。\n迁移前的架构如下：\n用户 │ ▼ Azure VM ├── OpenWebUI Docker 容器 │ └── 连接 VM 宿主机 PostgreSQL │ └── PostgreSQL └── 数据保存在 VM 本地磁盘 这种部署方式比较简单，但随着应用逐渐稳定运行，数据库维护也成为了新的负担。\n需要自行处理的工作包括：\nPostgreSQL 安全更新和补丁 数据库备份及备份轮换 数据恢复和恢复演练 磁盘容量监控 数据库服务监控 PostgreSQL 版本升级 故障处理 网络和访问权限管理 对于 OpenWebUI 这样的应用来说，数据库是重要的状态组件，但并不是应用的核心运维目标。因此，将 PostgreSQL 迁移到 Azure 托管服务是一个更合理的选择。\n迁移后的架构 迁移后的架构如下：\n用户 │ ▼ Azure VM └── OpenWebUI Docker 容器 │ ├── Private Endpoint │ └── Azure Database for PostgreSQL │ └── 宿主机持久化目录 ├── uploads ├── vector_db └── cache 最终实现了：\nOpenWebUI 使用 Azure 托管 PostgreSQL 数据库通过 Private Endpoint 私网访问 数据库连接启用 TLS PostgreSQL 不再占用 VM 本地端口和磁盘 OpenWebUI 上传文件和向量数据实现持久化挂载 Azure 自动执行数据库备份 VM 上只保留 PostgreSQL 客户端，用于日常管理 Azure 数据库 为什么选择 Azure Database for PostgreSQL 1. 降低数据库运维成本 Azure Database for PostgreSQL Flexible Server 是一项托管数据库服务。\n与 VM 自建 PostgreSQL 相比，Azure 可以帮助处理大量底层工作，例如：\n数据库基础设施维护 自动备份 事务日志备份 时间点恢复 监控指标采集 存储管理 计划性维护 可选高可用 可选只读副本 迁移完成后，VM 只需要负责运行 OpenWebUI，数据库生命周期则交给 Azure 管理。\n2. 自动备份和时间点恢复 Azure PostgreSQL 提供内置自动备份，并持续备份事务日志，支持时间点恢复（Point-in-Time Restore，PITR）。\n当前环境配置了自动备份保留策略。发生误操作时，可以在保留期内创建一台恢复到指定时间点的新服务器。\n与自行编写 pg_dump 和 Cron 脚本相比，Azure 托管备份具有以下优势：\n不需要维护备份脚本 不需要手动轮换备份文件 不会占用 VM 本地磁盘 可以恢复到保留期内的特定时间 备份操作由平台统一管理 需要注意的是，Azure 内置备份主要用于平台恢复，不等同于可随意下载的逻辑备份文件。\n如果有以下需求，仍建议额外定期执行 pg_dump：\n长期离线归档 跨平台恢复 数据审计 跨订阅或跨云迁移 保留期超过平台备份策略 3. 使用 Private Endpoint 私网访问 本次迁移没有将 PostgreSQL 直接暴露到公网，而是为 Azure PostgreSQL 配置了 Private Endpoint。\nOpenWebUI 访问数据库时，数据库域名通过 Private DNS 解析到 VNet 中的私有 IP：\nAzure PostgreSQL FQDN ↓ Private DNS ↓ Private Endpoint 私有 IP 从 OpenWebUI 容器内部进行了两层验证：\nDNS 是否解析到私有地址 TCP 5432 是否可以通过私网访问 示例：\ndocker exec -i open-webui python3 - \u0026lt;\u0026lt;\u0026#39;PY\u0026#39; import socket host = \u0026#34;\u0026lt;AZURE_POSTGRESQL_PRIVATE_FQDN\u0026gt;\u0026#34; port = 5432 addresses = sorted({ item[4][0] for item in socket.getaddrinfo( host, port, type=socket.SOCK_STREAM ) }) print(\u0026#34;Resolved addresses:\u0026#34;, addresses) with socket.create_connection((host, port), timeout=5) as sock: print(\u0026#34;TCP connection: OK\u0026#34;) print(\u0026#34;Connected peer:\u0026#34;, sock.getpeername()) PY 数据库安全性并不只是来自 Private Endpoint，而是来自以下措施的组合：\nPrivate Endpoint Private DNS VNet 隔离 NSG 和路由控制 禁用或限制公网访问 PostgreSQL 身份认证 TLS 链路加密 最小权限数据库账号 4. 数据库连接启用 TLS OpenWebUI 的数据库连接串启用了：\nsslmode=require 脱敏后的连接串格式如下：\npostgresql://\u0026lt;APP_USER\u0026gt;:\u0026lt;PASSWORD\u0026gt;@\u0026lt;AZURE_POSTGRESQL_FQDN\u0026gt;:5432/\u0026lt;APP_DATABASE\u0026gt;?sslmode=require 迁移前还通过 OpenWebUI 使用的 Python 和 SQLAlchemy 环境实际验证了 TLS：\nimport os from sqlalchemy import create_engine, text engine = create_engine(os.environ[\u0026#34;DATABASE_URL\u0026#34;]) with engine.connect() as conn: ssl_enabled = conn.execute(text(\u0026#34;\u0026#34;\u0026#34; SELECT ssl FROM pg_stat_ssl WHERE pid = pg_backend_pid() \u0026#34;\u0026#34;\u0026#34;)).scalar_one() print(\u0026#34;TLS enabled:\u0026#34;, ssl_enabled) 如果安全要求更高，可以进一步使用：\nsslmode=verify-full 这种模式不仅加密连接，还会验证证书和服务器主机名，但需要正确配置 CA 证书，并使用与证书匹配的服务器 FQDN。\n5. 更容易扩展可靠性能力 迁移到 Azure PostgreSQL 后，可以根据 OpenWebUI 的使用规模继续配置：\n计算规格扩缩容 存储扩容 可用区高可用 只读副本 慢查询分析 数据库连接监控 CPU、内存和存储告警 更长的备份保留期 异地恢复策略 这使 OpenWebUI 的数据库层从“单 VM 上的本地服务”升级为独立、可管理的托管数据服务。\n迁移目标：让 OpenWebUI 尽量保持在线 传统迁移通常采用以下流程：\n停止应用 ↓ 导出数据库 ↓ 恢复目标数据库 ↓ 修改连接配置 ↓ 重新启动应用 这种方式虽然简单，但数据库越大，停机时间越长。\n本次迁移采用了不同策略：\n在线全量备份 ↓ 在线恢复到 Azure PostgreSQL ↓ 验证结构、权限和数据 ↓ 同步迁移期间新增的活跃对话 ↓ 快速重建 OpenWebUI 容器 在以下阶段，OpenWebUI 都保持在线：\n全量 pg_dump Azure PostgreSQL 数据恢复 表结构验证 数据量对比 Private Endpoint 验证 TLS 验证 增量数据同步 最终只有更换 DATABASE_URL 并重建容器时发生了短暂连接中断。\n因此，更准确地说，这是一种：\n近零停机、用户侧近乎无感知的 OpenWebUI 数据库迁移方案。\n迁移前检查 OpenWebUI 1. 确认 OpenWebUI 使用的数据库类型 首先检查 OpenWebUI 容器环境变量：\ndocker inspect open-webui \\ --format \u0026#39;{{range .Config.Env}}{{println .}}{{end}}\u0026#39; | grep -E \u0026#39;DATABASE_URL|DATA_DIR|WEBUI_SECRET_KEY\u0026#39; 确认 OpenWebUI 已经使用 PostgreSQL，而不是默认 SQLite：\nDATABASE_URL=postgresql://\u0026lt;LOCAL_USER\u0026gt;:***@\u0026lt;LOCAL_HOST\u0026gt;:5432/\u0026lt;LOCAL_DATABASE\u0026gt; 由于源端和目标端都是 PostgreSQL，因此可以直接使用：\npg_dump → pg_restore 不需要使用 SQLite 到 PostgreSQL 的转换工具。\n2. 检查源数据库 检查 PostgreSQL 版本和数据库大小：\nsudo -u postgres psql \\ -d \u0026lt;LOCAL_DATABASE\u0026gt; \\ -c \u0026#34; SELECT current_database(), current_setting(\u0026#39;server_version\u0026#39;), pg_size_pretty( pg_database_size(current_database()) ); \u0026#34; 同时检查源数据库使用了哪些扩展：\nsudo -u postgres psql \\ -d \u0026lt;LOCAL_DATABASE\u0026gt; \\ -c \u0026#34; SELECT extname, extversion FROM pg_extension ORDER BY extname; \u0026#34; 本次源数据库没有使用额外的第三方扩展，因此降低了跨环境迁移的兼容性风险。\n3. 检查 OpenWebUI 的本地文件 数据库并不是 OpenWebUI 的全部数据。\n检查容器挂载后发现，原容器没有配置持久化 volume，而 /app/backend/data 中已经存在大量数据：\n/app/backend/data ├── cache ├── uploads └── vector_db 这些内容包括：\n用户上传文件 向量数据库 缓存 其他应用本地状态 它们不会进入 PostgreSQL 的 pg_dump，因此必须单独处理。\n这也是本次迁移中一个非常重要的发现：\n迁移 OpenWebUI 的 PostgreSQL，并不等于迁移了 OpenWebUI 的全部数据。\n准备 Azure PostgreSQL 1. 创建独立应用账号 不建议让 OpenWebUI 长期使用 Azure PostgreSQL 管理员账号。\n在目标服务器中创建专用角色：\nCREATE ROLE \u0026lt;APP_USER\u0026gt; LOGIN; 通过 psql 交互方式设置密码，可以避免密码出现在 Shell 历史记录中：\n\\password \u0026lt;APP_USER\u0026gt; 然后创建目标数据库：\nCREATE DATABASE \u0026lt;APP_DATABASE\u0026gt; OWNER \u0026lt;APP_USER\u0026gt;; 2. 验证应用账号 psql \\ \u0026#34;host=\u0026lt;AZURE_POSTGRESQL_FQDN\u0026gt; \\ port=5432 \\ dbname=\u0026lt;APP_DATABASE\u0026gt; \\ user=\u0026lt;APP_USER\u0026gt; \\ sslmode=require\u0026#34; \\ -W \\ -c \u0026#34;SELECT current_user, current_database();\u0026#34; 同时确认目标数据库为空：\nSELECT count(*) FROM information_schema.tables WHERE table_schema = \u0026#39;public\u0026#39; AND table_type = \u0026#39;BASE TABLE\u0026#39;; 3. 处理 public schema 权限 第一次测试恢复时遇到了：\nERROR: permission denied for schema public 原因是应用账号虽然可以连接数据库，但没有在 public schema 中创建对象的权限。\n使用管理员账号连接目标数据库后执行：\nALTER SCHEMA public OWNER TO \u0026lt;APP_USER\u0026gt;; GRANT USAGE, CREATE ON SCHEMA public TO \u0026lt;APP_USER\u0026gt;; 这个问题说明，仅验证账号“能否登录”还不够，还需要确认该账号能否：\n创建表 创建索引 创建序列 创建约束 执行 OpenWebUI 的 Alembic 数据库迁移 在线执行全量数据库迁移 1. 在线导出 OpenWebUI 数据库 PostgreSQL 的 pg_dump 会基于一致性事务快照导出数据，因此可以在 OpenWebUI 继续运行时执行。\nsudo -u postgres pg_dump \\ --dbname=\u0026lt;LOCAL_DATABASE\u0026gt; \\ --format=custom \\ --no-owner \\ --no-acl \\ --file=/tmp/openwebui-pre-migration.dump 参数含义：\n--format=custom：使用 PostgreSQL 自定义归档格式 --no-owner：不恢复源端对象 owner --no-acl：不恢复源端权限 --file：指定备份文件 检查归档是否有效：\npg_restore \\ --list \\ /tmp/openwebui-pre-migration.dump | head -30 自定义格式会保留：\n表结构 表数据 主键 唯一约束 索引 外键 序列 Alembic 迁移版本 2. 恢复到 Azure PostgreSQL pg_restore \\ --host=\u0026lt;AZURE_POSTGRESQL_FQDN\u0026gt; \\ --port=5432 \\ --username=\u0026lt;APP_USER\u0026gt; \\ --dbname=\u0026lt;APP_DATABASE\u0026gt; \\ --no-owner \\ --no-acl \\ --exit-on-error \\ --verbose \\ /tmp/openwebui-pre-migration.dump 使用：\n--exit-on-error 可以确保遇到恢复错误时立即停止，避免忽略关键问题。\n恢复成功后更新统计信息：\npsql \\ \u0026#34;host=\u0026lt;AZURE_POSTGRESQL_FQDN\u0026gt; \\ port=5432 \\ dbname=\u0026lt;APP_DATABASE\u0026gt; \\ user=\u0026lt;APP_USER\u0026gt; \\ sslmode=require\u0026#34; \\ -W \\ -c \u0026#34;ANALYZE;\u0026#34; 对比源端与目标端数据 恢复完成后，需要对 OpenWebUI 的核心表进行对比。\n示例查询：\nSELECT (SELECT count(*) FROM \u0026#34;user\u0026#34;) AS users, (SELECT count(*) FROM chat) AS chats, (SELECT count(*) FROM chat_message) AS chat_messages, (SELECT count(*) FROM file) AS files, (SELECT count(*) FROM knowledge) AS knowledge, ( SELECT count(*) FROM information_schema.tables WHERE table_schema = \u0026#39;public\u0026#39; AND table_type = \u0026#39;BASE TABLE\u0026#39; ) AS tables; 重点检查：\n用户数量 对话数量 消息数量 文件记录数量 知识库数量 业务表数量 Alembic 版本 表 owner 序列状态 对比时发现，目标端的对话消息比源端少了少量记录。\n这并不是恢复失败，而是因为：\n在全量备份和恢复过程中，OpenWebUI 仍然在线，当前迁移操作的对话还在不断产生新消息。\n因此，下一步需要同步全量快照之后新增的数据。\n同步迁移期间新增的活跃对话 本次迁移期间，主要只有当前操作对话仍在更新，因此没有再次恢复整个数据库，而是只同步当前活跃对话。\n涉及两张表：\nchat chat_message 1. 找到最近更新的对话 SELECT c.id, c.title, to_timestamp(c.updated_at) AS updated_at, count(cm.id) AS message_count FROM chat c LEFT JOIN chat_message cm ON cm.chat_id = c.id GROUP BY c.id, c.title, c.updated_at ORDER BY c.updated_at DESC LIMIT 5; 确定活跃对话后，后续脚本通过环境变量或参数接收对话 ID，而不是将其直接写入公开文档：\nCHAT_ID=\u0026#34;\u0026lt;ACTIVE_CHAT_ID\u0026gt;\u0026#34; 2. 使用一致性快照导出对话数据 为了保证 chat 和 chat_message 来自同一个时间点，使用只读、可重复读事务：\nBEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY; 然后在同一事务中导出：\n\\copy ( SELECT * FROM chat WHERE id = \u0026#39;\u0026lt;ACTIVE_CHAT_ID\u0026gt;\u0026#39; ) TO \u0026#39;/tmp/chat.csv\u0026#39; WITH (FORMAT CSV); \\copy ( SELECT * FROM chat_message WHERE chat_id = \u0026#39;\u0026lt;ACTIVE_CHAT_ID\u0026gt;\u0026#39; ORDER BY created_at, id ) TO \u0026#39;/tmp/chat-messages.csv\u0026#39; WITH (FORMAT CSV); 最后提交：\nCOMMIT; 需要注意：psql 的 \\copy 是客户端元命令，复杂字段列表最好写在同一行，否则可能出现：\n\\copy: parse error at end of line 3. 在 Azure 中使用临时表和 UPSERT 目标端创建临时 staging 表：\nCREATE TEMP TABLE stage_chat (LIKE public.chat INCLUDING DEFAULTS); CREATE TEMP TABLE stage_chat_message (LIKE public.chat_message INCLUDING DEFAULTS); 将 CSV 导入临时表后，对 chat 主记录执行 UPSERT：\nINSERT INTO public.chat (...) SELECT ... FROM stage_chat ON CONFLICT (id) DO UPDATE SET title = EXCLUDED.title, updated_at = EXCLUDED.updated_at, chat = EXCLUDED.chat, meta = EXCLUDED.meta, current_message_id = EXCLUDED.current_message_id; 对消息表执行类似操作：\nINSERT INTO public.chat_message (...) SELECT ... FROM stage_chat_message ON CONFLICT (id) DO UPDATE SET content = EXCLUDED.content, output = EXCLUDED.output, status_history = EXCLUDED.status_history, error = EXCLUDED.error, usage = EXCLUDED.usage, done = EXCLUDED.done, updated_at = EXCLUDED.updated_at, context_summary = EXCLUDED.context_summary; 为了让 Azure 中该对话的消息集合与源快照完全一致，还会删除目标端多余记录：\nDELETE FROM public.chat_message target WHERE target.chat_id = \u0026#39;\u0026lt;ACTIVE_CHAT_ID\u0026gt;\u0026#39; AND NOT EXISTS ( SELECT 1 FROM stage_chat_message staged WHERE staged.id = target.id ); 整个导入和 UPSERT 过程放在一个事务中执行：\nBEGIN; -- 创建临时表 -- 导入数据 -- UPSERT -- 清理多余记录 COMMIT; 如果任何步骤失败，事务会自动回滚，不会留下部分同步数据。\n最终快速切换 OpenWebUI 完成在线全量迁移后，最终操作为：\n最后同步一次当前活跃对话 ↓ 重建 OpenWebUI 容器 ↓ 新容器连接 Azure PostgreSQL 示例：\n./sync-active-chat-to-azure.sh \u0026amp;\u0026amp; \\ docker compose \\ -f run-with-azure-pg.yml \\ up -d --force-recreate 完整数据库已经提前迁移并验证，因此最终阶段不需要再次等待几百 MB 数据恢复，只需要同步少量增量记录并快速重建容器。\n这将切换窗口缩短到了容器启动所需的时间。\n安全保存 OpenWebUI 数据库连接串 数据库连接串没有直接写入 Compose 文件，而是放在独立环境文件中：\nopen-webui-azure-db.env 文件内容形式：\nDATABASE_URL=postgresql://\u0026lt;APP_USER\u0026gt;:\u0026lt;URL_ENCODED_PASSWORD\u0026gt;@\u0026lt;AZURE_POSTGRESQL_FQDN\u0026gt;:5432/\u0026lt;APP_DATABASE\u0026gt;?sslmode=require 文件权限设置为：\nchmod 600 open-webui-azure-db.env 如果密码包含特殊字符，需要先进行 URL 编码：\npython3 - \u0026lt;\u0026lt;\u0026#39;PY\u0026#39; import getpass import urllib.parse password = getpass.getpass(\u0026#34;Database password: \u0026#34;) print(urllib.parse.quote(password, safe=\u0026#34;\u0026#34;)) PY 新的 Compose 文件示例：\nservices: open-webui: image: ghcr.io/open-webui/open-webui:\u0026lt;VERSION\u0026gt; container_name: open-webui restart: always ports: - \u0026#34;8080:8080\u0026#34; env_file: - ./open-webui.env - ./open-webui-azure-db.env volumes: - /opt/open-webui/data:/app/backend/data 持久化 OpenWebUI 本地文件 迁移前，OpenWebUI 的 /app/backend/data 没有挂载到宿主机。\n先将数据复制出来：\nsudo install \\ -d \\ -m 700 \\ -o \u0026#34;$(id -un)\u0026#34; \\ -g \u0026#34;$(id -gn)\u0026#34; \\ /opt/open-webui/data docker cp \\ open-webui:/app/backend/data/. \\ /opt/open-webui/data/ 在 Compose 中加入：\nvolumes: - /opt/open-webui/data:/app/backend/data 这样，即使未来重建或升级 OpenWebUI 容器，以下内容仍会保留：\n上传文件 向量数据库 缓存 其他本地应用数据 迁移后验证：\ndocker inspect open-webui \\ --format \u0026#39;{{range .Mounts}}{{println .Source \u0026#34;-\u0026gt;\u0026#34; .Destination \u0026#34;rw=\u0026#34; .RW}}{{end}}\u0026#39; 预期：\n/opt/open-webui/data -\u0026gt; /app/backend/data rw=true 迁移后的验证 1. OpenWebUI 健康检查 curl \\ -sS \\ -o /dev/null \\ -w \u0026#39;HTTP %{http_code}\\n\u0026#39; \\ --max-time 10 \\ http://127.0.0.1:8080/health 预期：\nHTTP 200 2. 验证 OpenWebUI 当前数据库 为了避免输出密码，只解析连接串中的非敏感部分：\ndocker exec -i open-webui python3 - \u0026lt;\u0026lt;\u0026#39;PY\u0026#39; import os from urllib.parse import urlsplit u = urlsplit(os.environ[\u0026#34;DATABASE_URL\u0026#34;]) print(\u0026#34;Database host configured:\u0026#34;, bool(u.hostname)) print(\u0026#34;Database port:\u0026#34;, u.port) print(\u0026#34;TLS required:\u0026#34;, \u0026#34;sslmode=require\u0026#34; in u.query) PY 验证重点：\nHost 已不再指向 VM 本地 PostgreSQL 端口为 5432 已启用 sslmode=require 3. 检查启动日志 docker logs \\ --since 15m \\ open-webui 2\u0026gt;\u0026amp;1 | grep -Ei \\ \u0026#39;error|fatal|exception|traceback|permission denied|database|postgres|alembic|ssl|timeout\u0026#39; 正常情况下只会看到类似：\nINFO [alembic.runtime.migration] Context impl PostgresqlImpl. INFO [alembic.runtime.migration] Will assume transactional DDL. 不应出现：\n数据库连接失败 密码认证失败 TLS 错误 Schema 权限错误 Alembic 迁移失败 Python Traceback 卸载 VM 本地 PostgreSQL 确认 OpenWebUI 已稳定连接 Azure PostgreSQL 后，可以删除 VM 上的本地 PostgreSQL。\n首先确认：\nOpenWebUI 健康检查正常 数据库连接已切换 Azure PostgreSQL 中存在最新数据 自动备份策略已经启用 本地 PostgreSQL 不再有应用连接 停止并删除本地集群：\nsudo pg_dropcluster \\ --stop \\ \u0026lt;POSTGRESQL_VERSION\u0026gt; \\ main 卸载 PostgreSQL 服务端：\nsudo apt-get purge -y \\ postgresql \\ postgresql-\u0026lt;POSTGRESQL_VERSION\u0026gt; \\ postgresql-contrib 删除遗留数据和配置：\nsudo rm -rf \\ /var/lib/postgresql \\ /etc/postgresql \\ /var/log/postgresql 可以继续保留 PostgreSQL 客户端：\npostgresql-client postgresql-client-common 这样仍然可以从 VM 使用 psql 管理 Azure PostgreSQL。\n最终检查本地 5432：\nif sudo ss -lntp | grep -q \u0026#39;:5432\u0026#39;; then sudo ss -lntp | grep \u0026#39;:5432\u0026#39; else echo \u0026#34;No local PostgreSQL listener\u0026#34; fi 迁移过程中的经验总结 1. 数据库迁移不等于 OpenWebUI 全部数据迁移 OpenWebUI 除了 PostgreSQL，还可能在 /app/backend/data 中保存：\n上传文件 向量数据库 缓存 本地状态文件 只执行 pg_dump 并不能迁移这些内容。\n2. 不要只测试端口连通 nc 成功只能证明 TCP 5432 可达，并不能证明：\nPostgreSQL 用户可以登录 密码正确 TLS 正常 数据库权限正确 OpenWebUI 使用的驱动兼容 应至少完成以下验证：\nDNS → TCP → PostgreSQL 登录 → TLS → SQL 查询 → OpenWebUI 实际连接 3. 数据库 owner 不代表一定拥有 schema 创建权限 即使应用用户是数据库 owner，也可能遇到：\npermission denied for schema public 迁移前应验证应用账号是否具备：\nUSAGE CREATE 表和序列所有权 Alembic DDL 权限 4. 在线全量备份后必须处理增量写入 pg_dump 提供的是开始备份时的一致性快照。\n备份期间 OpenWebUI 新产生的对话和消息不会自动进入已经完成的 dump。因此在线迁移必须增加以下机制之一：\n最终停写后重新完整备份 逻辑复制 CDC 应用双写 按业务对象执行增量同步 本次采用的是“全量快照 + 活跃对话 UPSERT”。\n5. 不要在迁移时顺便升级 OpenWebUI 本次迁移始终保持 OpenWebUI 镜像版本不变。\n如果同时升级应用和迁移数据库，一旦发生问题，很难快速判断原因来自：\nPostgreSQL 版本 数据迁移 Azure 网络 数据库权限 OpenWebUI 版本 Alembic schema 变化 更稳妥的做法是：\n先迁移数据库并稳定运行 ↓ 再单独升级 OpenWebUI 这算不算真正的零停机？ 从用户体验看，这次迁移接近零停机：\n在线完成全量备份 在线恢复目标数据库 在线完成数据验证 在线同步活跃对话 仅在最后重建了一次 OpenWebUI 容器 但如果采用严格工程定义，真正的零停机还需要满足：\n任意时刻都能接受新连接 最后时刻写入绝不丢失 没有单实例容器重启窗口 客户端连接完全不中断 具备自动流量切换能力 更大规模的 OpenWebUI 部署可以考虑：\nPostgreSQL 逻辑复制 Azure Database Migration Service CDC 增量迁移 应用双写 蓝绿部署 多个 OpenWebUI 实例 反向代理健康检查 连接排空和流量切换 对于单 VM、单 OpenWebUI 容器和中小规模数据库，本次采用的方案在复杂度、风险和停机时间之间取得了较好的平衡。\n迁移后的日常运维 启动 OpenWebUI：\ndocker compose \\ -f run-with-azure-pg.yml \\ up -d 查看状态：\ndocker compose \\ -f run-with-azure-pg.yml \\ ps 查看日志：\ndocker compose \\ -f run-with-azure-pg.yml \\ logs -f --tail=200 健康检查：\ncurl \\ -sS \\ -o /dev/null \\ -w \u0026#39;HTTP %{http_code}\\n\u0026#39; \\ http://127.0.0.1:8080/health 日常还应重点监控 Azure PostgreSQL 的：\nCPU 使用率 内存使用情况 存储空间 活跃连接数 失败连接数 查询延迟 自动备份状态 维护事件 Private Endpoint 状态 总结 这次迁移将 OpenWebUI 的数据库从 Azure VM 自建 PostgreSQL 平滑迁移到了 Azure Database for PostgreSQL Flexible Server。\n最终实现了：\nOpenWebUI 数据库由 Azure 托管 通过 Private Endpoint 私网访问 数据库连接启用 TLS 自动备份和时间点恢复 OpenWebUI 本地文件持久化 VM 本地 PostgreSQL 完全下线 本地 5432 端口关闭 全量迁移期间应用持续在线 最终切换对用户影响极低 本次最关键的经验是：\n对 OpenWebUI 进行低停机数据库迁移，不应只关注 pg_dump 和 pg_restore，还必须同时考虑增量写入、Private Endpoint、TLS、数据库权限、Alembic 迁移以及 /app/backend/data 中的本地文件。\n通过“在线全量迁移、目标端预验证、活跃数据增量同步、快速容器切换”，可以在不引入复杂 CDC 平台的情况下，将中小规模 OpenWebUI 平滑迁移到 Azure 托管 PostgreSQL。\n","permalink":"https://wilsonwu.me/zh/blog/2026/migrate-openwebui-to-azure-pg/","summary":"\u003cp\u003e\u003cimg alt=\"迁移\" loading=\"lazy\" src=\"/zh/blog/2026/migrate-openwebui-to-azure-pg/1-overview.png\"\u003e\u003c/p\u003e\n\u003cp\u003e随着 OpenWebUI 中的用户、对话、文件和知识库数据不断增长，原先部署在 Azure VM 上的自建 PostgreSQL 带来了越来越多的运维工作，包括数据库补丁、备份、故障恢复、存储监控和安全维护。\u003c/p\u003e","title":"OpenWebUI 数据库迁移实践：从 VM 自建 PostgreSQL 平滑迁移到 Azure 托管 PostgreSQL"},{"content":"1. 引言 在 vLLM Semantic Router 的设计中，Embedding 推理是驱动语义路由的核心信号之一。\n官方教程已经演示了如何使用远程 Embedding 推理提供商（Remote Embedding Providers）替代本地 Embedding 推理服务：\n👉 https://vllm-sr.ai/docs/tutorials/global/remote-embeddings/\n然而，在实际生产环境中，一个更为关键的问题随之而来：\n如何使用企业级、可扩展的 Embedding 推理服务来支持语义路由？\n本文基于官方教程中的场景，并结合 Microsoft Foundry Embedding 模型，演示如何构建一个具备以下特性的智能路由系统：\n无需 GPU 无需在本地部署 Embedding 模型 生产就绪 2. 回顾：远程 Embedding 推理提供商 在 vLLM Semantic Router 中，路由器运行在 CPU 上，不依赖 GPU 推理能力。\n通过远程 Embedding 推理提供商：\n路由器 → 远程 Embedding 推理 API → 向量 我们可以：\n将嵌入计算完全卸载到云服务 统一多模型的语义能力 解耦路由与推理 3. 教程场景（沿用官方测试场景） 在官方教程中，核心流程如下：\n第 1 步：定义语义路由 routing: decisions: - name: math when: semantic_similarity: text: \u0026#34;math question\u0026#34; threshold: 0.8 - name: general 第 2 步：发送请求 curl http://localhost:8888/v1/chat/completions \\ -d \u0026#39;{ \u0026#34;model\u0026#34;: \u0026#34;MoM\u0026#34;, \u0026#34;messages\u0026#34;: [{\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;What is 1+1?\u0026#34;}] }\u0026#39; 路由器将执行以下操作：\n生成嵌入向量 执行语义匹配 选择目标模型 4. 第 0 步：在 Microsoft Foundry 中创建 Embedding 模型 在与 vLLM Semantic Router 集成之前，我们首先需要在 Microsoft Foundry 中部署一个 Embedding 模型。\n4.1 创建 Foundry 项目 前往 Microsoft Foundry 门户 创建新项目（或选择现有项目） 确保该项目已关联 Azure 订阅 4.2 部署嵌入模型 在模型目录中：\n搜索 Embedding 模型 选择受支持的模型（例如 text-embedding-3-small） 单击 Deploy（部署） 部署完成后，你将获得：\nEndpoint URL API Key Model Name 示例：\nEndpoint: https://\u0026lt;your-foundry-resource\u0026gt;.inference.ai.azure.com Model: text-embedding-3-small API Key: ****** 4.3 验证 Embedding 推理 API 你可以直接测试该终结点：\ncurl https://\u0026lt;your-foundry-endpoint\u0026gt;/v1/embeddings \\ -H \u0026#34;api-key: \u0026lt;your-api-key\u0026gt;\u0026#34; \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{ \u0026#34;input\u0026#34;: \u0026#34;Hello world\u0026#34; }\u0026#39; 如果调用成功，你将收到包含向量的响应。\n5. 升级：使用 Microsoft Foundry 替代本地 Embedding 模型 现在，我们将教程中的 Embedding 推理提供商替换为：\n✅ Microsoft Foundry Embedding 模型\n6. 为什么选择 Microsoft Foundry Embedding 模型 Microsoft Foundry 提供：\n高质量 Embedding 模型（更强的语义理解能力） 托管 API（无需自行部署） 全球可用性与企业级 SLA 与 Azure AI 生态系统无缝集成 更重要的是：\n它可以直接充当 Semantic Router 的“语义大脑”\n7. 集成：将 Microsoft Foundry 用作远程 Embedding 推理提供商 7.1 配置 Embedding 推理提供商 providers: embedding: type: remote provider: openai-compatible base_url: https://\u0026lt;your-foundry-endpoint\u0026gt; api_key: \u0026lt;your-api-key\u0026gt; model: text-embedding-3-small 说明：\nFoundry 提供兼容 OpenAI 的 API 可以直接复用 Semantic Router 的远程 Embedding 推理提供商 无需额外适配 7.2 路由器工作流 用户请求 ↓ Semantic Router ↓ Microsoft Foundry Embedding 推理 API ↓ 向量相似度计算 ↓ 路由决策 ↓ 目标模型（vLLM / API） 8. 相比官方教程的主要改进 能力 原始方案 Microsoft Foundry 方案 嵌入来源 本地 / 模拟 云端高质量模型 部署成本 需要模型或容器 零运维 可扩展性 单一环境 多区域 语义质量 中等 企业级 9. 实际价值 🚀 1. 真正的“无 GPU 架构” 路由器本身运行在 CPU 上。\nEmbedding 模型与推理能力完全外部化：\nFoundry → Embedding 模型 vLLM / API → 推理 🧠 2. 更准确的语义路由 在教程场景中：\n\u0026#34;What is 1+1?\u0026#34; → math 路由 \u0026#34;Tell me a joke\u0026#34; → general 路由 使用 Foundry 嵌入后，可以获得：\n更稳定的语义理解 更好的多语言支持 对边界场景更准确的判断 🌐 3. 统一的多模型入口 结合 Semantic Router：\nMicrosoft Foundry（Embedding 模型） vLLM（自托管推理） OpenAI / 其他 API 从而实现真正的：\n多提供商智能路由\n10. 架构洞察 此次升级的本质是：\n升级前： 路由器 = 基础设施 + 智能 升级后： 路由器 = 智能 Foundry = AI 能力 由此形成清晰的分层架构：\n[ AI 能力层 ] → Microsoft Foundry [ 路由层 ] → Semantic Router [ 推理层 ] → vLLM / APIs 11. 总结 通过将 Microsoft Foundry 与官方远程 Embedding 模型教程相结合，我们实现了一套生产就绪的架构：\n无需本地 Embedding 模型 无需 GPU 高质量的语义能力 企业级可靠性 这不仅是一次功能增强，更是 Semantic Router 的一次关键演进：\n从“本地工具”演进为“云原生 AI 路由层”\n","permalink":"https://wilsonwu.me/zh/blog/2026/azure-foundry-embeddings-with-vllm-sr/","summary":"\u003ch2 id=\"1-引言\"\u003e1. 引言\u003c/h2\u003e\n\u003cp\u003e在 vLLM Semantic Router 的设计中，Embedding 推理是驱动语义路由的核心信号之一。\u003c/p\u003e\n\u003cp\u003e官方教程已经演示了如何使用\u003cstrong\u003e远程 Embedding 推理提供商（Remote Embedding Providers）\u003c/strong\u003e替代本地 Embedding 推理服务：\u003c/p\u003e","title":"在 vLLM Semantic Router 中使用 Microsoft Foundry 远程 Embedding 推理"},{"content":"\n在大模型应用快速落地的过程中，推理服务的工程化能力往往比模型本身更复杂：模型调度、路由策略、限流、灰度发布、网关治理等问题，都需要真实的推理接口进行验证。然而，大模型推理依赖 GPU 和高成本算力资源，使得开发和测试门槛居高不下。\n如果你希望在没有 GPU 或资源受限的环境中验证推理架构设计，那么这个工具值得关注：\nllm-d-inference-sim GitHub 项目\n本文将系统介绍这个工具的能力、设计思路，以及它在实际工程中的应用价值。\n工具定位：为推理工程而生的“模拟器” llm-d-inference-sim 是一个专门用于模拟大模型推理服务的工具，其核心能力包括：\n提供与真实 LLM 推理服务一致的接口（如 OpenAI-style API） 接收推理请求（prompt / messages） 按配置生成“模拟响应” 支持多实例、多模型模拟部署 简单来说，它并不真正执行模型推理，而是：\n模拟“推理服务行为”，而非“推理计算本身”\n为什么需要推理模拟？ 在真实项目中，我们经常遇到以下问题：\n1. 推理资源昂贵 GPU 成本高（A100 / H100） 推理服务部署复杂（vLLM / TensorRT-LLM / SGLang） 测试环境难以复刻生产规模 2. 工程验证依赖真实服务 很多关键能力必须依赖推理接口验证，例如：\nAPI 网关路由策略（Envoy / Istio） 多模型调度与 fallback 机制 embedding + intent routing 限流 / 熔断 / 超时控制 灰度发布与 A/B testing 但这些能力并不依赖模型“正确性”，只依赖：\n“是否有一个行为类似 LLM 的服务存在”\nllm-d-inference-sim 的核心价值 1. 零 GPU 依赖 开发者可以在以下环境直接使用：\n本地 MacBook CI/CD 环境 Kubernetes 测试集群 云主机（无 GPU） 无需部署任何真实模型。\n2. 高度可控的响应模拟 可以模拟：\n固定响应（用于稳定测试） 延迟（模拟推理耗时） Token 生成节奏（Streaming） 错误返回（测试容错） 这使它非常适合：\n压测、容错测试、链路验证\n3. 支持多模型实例 你可以快速部署多个“模拟模型”：\ngpt-4 模拟服务 embedding 模型模拟 不同延迟 / 不同性能 profile 用于验证：\n多模型路由策略 智能网关（AI Gateway） fallback 逻辑 4. 与真实 API 兼容 接口风格通常兼容：\nOpenAI API（/v1/chat/completions） streaming 输出 这意味着：\n可以无缝替换真实模型服务\n典型使用场景 场景 1：AI 网关 / 服务网格验证 例如使用 Envoy / Istio 构建 AI Gateway：\n基于路径或 header 路由不同模型 embedding-based routing Canary 发布 问题：没有真实模型怎么办？\n使用 llm-d-inference-sim 模拟多个模型后端，即可验证完整链路。\n场景 2：多模型调度系统开发 在做类似以下系统时：\nvLLM-aware scheduler KV Cache aware routing 模型负载均衡 你需要：\n多个模型 endpoint 不同响应延迟 可控行为 模拟器可以快速构造这些环境。\n场景 3：CI/CD 自动化测试 真实模型服务难以接入 CI：\n成本高 不稳定 启动慢 使用模拟器：\n启动快速 行为可预测 支持 mock response 非常适合集成测试（integration test）\n场景 4：产品演示 / Demo 环境 在售前或内部 Demo 中：\n不需要真实模型 只需要“看起来能跑” 使用模拟器可以：\n快速部署多个模型服务 构造稳定演示环境 避免 GPU 成本 工作原理简析 llm-d-inference-sim 的设计可以理解为：\n请求流程 接收 HTTP 请求（如 /v1/chat/completions） 解析输入（prompt/messages） 根据配置规则生成响应 返回 JSON 或 streaming 输出 模拟能力通常包括 Response 模板\n固定文本 参数化生成 Latency 注入\n模拟推理耗时（如 200ms / 2s） Streaming\n分块返回 token 模拟真实 LLM 输出节奏 错误注入\n500 / timeout 测试重试机制 与真实推理服务的关系 维度 模拟器 真实模型（vLLM 等） GPU ❌ 不需要 ✅ 必需 成本 极低 高 响应真实性 ❌ 无语义能力 ✅ 接口兼容性 ✅ ✅ 工程验证能力 ✅ ✅ 模拟器 ≠ 替代模型，而是补齐工程验证环节\n最佳实践建议 1. 与网关结合使用 推荐组合：\nEnvoy / Istio AI Gateway llm-d-inference-sim 构建完整推理流量链路\n2. 构造多模型拓扑 例如：\nmodel-a（低延迟） model-b（高质量，高延迟） model-fallback 验证智能路由策略\n3. 注入异常场景 主动模拟：\ntimeout 高延迟 错误返回 提升系统鲁棒性\n4. 用于开发早期阶段 在以下阶段优先使用模拟器：\n架构设计 API 设计 路由策略验证 等到后期再接入真实模型。\n总结 llm-d-inference-sim 解决的是一个被低估但关键的问题：\n在没有模型的情况下，如何推进大模型工程化？\n它的核心价值不在于“模拟模型”，而在于：\n降低开发门槛 提升验证效率 解耦算力与工程 对于正在构建 AI 基础设施（AI Gateway / 推理平台 / 多模型调度系统）的团队来说，这是一个非常实用的工具。\n","permalink":"https://wilsonwu.me/zh/blog/2026/llm-inference-simulation/","summary":"\u003cp\u003e\u003cimg alt=\"LLM 推理模拟\" loading=\"lazy\" src=\"/zh/blog/2026/llm-inference-simulation/1-llm-inference-simulation.png\"\u003e\u003c/p\u003e\n\u003cp\u003e在大模型应用快速落地的过程中，\u003cstrong\u003e推理服务的工程化能力\u003c/strong\u003e往往比模型本身更复杂：模型调度、路由策略、限流、灰度发布、网关治理等问题，都需要真实的推理接口进行验证。然而，大模型推理依赖 GPU 和高成本算力资源，使得开发和测试门槛居高不下。\u003c/p\u003e","title":"用 llm-d-inference-sim 模拟大模型推理：无 GPU 环境下的最佳验证工具"},{"content":"引言 在大模型后训练（Post-training）阶段，强化学习（RLHF / RLAIF）已经成为决定模型能力上限的关键因素之一。近期，GLM-5.2 在训练算法上从 GLM-5.1 使用的 GRPO（Generalized Reward Policy Optimization）切换到更经典的 PPO（Proximal Policy Optimization），并带来了明显的效果提升。\n这一变化并非简单的“算法替换”，而是一次在稳定性、泛化能力以及训练可控性上的系统性升级。\n本文将从三个层面展开分析：\nPPO 与 GRPO 的核心原理 两种算法的关键差异 为什么 PPO 能带来“质的提升” PPO（Proximal Policy Optimization）原理 1. 背景 PPO 是 OpenAI 在 2017 年提出的一种策略梯度方法，是 TRPO（Trust Region Policy Optimization）的工程化简化版本，目前已经成为 RLHF 训练中的事实标准。\n2. 核心思想 PPO 的核心目标是：\n在优化策略的同时，限制新旧策略之间的偏移，防止训练不稳定。\n其优化目标函数为：\nLPPO(θ)=E[min⁡(rt(θ)At, clip(rt(θ),1−ϵ,1+ϵ)At)] L^{PPO}(\\theta) = \\mathbb{E}\\left[\\min\\left(r_t(\\theta) A_t,\\ \\text{clip}(r_t(\\theta), 1-\\epsilon, 1+\\epsilon) A_t\\right)\\right] LPPO(θ)=E[min(rt​(θ)At​, clip(rt​(θ),1−ϵ,1+ϵ)At​)]其中：\nrt(θ)=πθ(a∣s)πθold(a∣s) r_t(\\theta) = \\frac{\\pi_\\theta(a|s)}{\\pi_{\\theta_{old}}(a|s)} rt​(θ)=πθold​​(a∣s)πθ​(a∣s)​ At A_t At​：优势函数（Advantage） ϵ \\epsilon ϵ：裁剪系数（通常 0.1~0.2） 3. 关键机制 PPO 的稳定性来源于三点：\nClipping 机制 限制策略更新幅度，防止“过度优化”。\ndvantage Estimation（GAE） 降低方差，提高训练稳定性。\n多步更新（Epoch） 在同一批数据上进行多次优化，提高 sample efficiency。\n4. 在大模型中的作用 在 LLM 训练中，PPO 通常用于：\n对齐人类偏好（RLHF） 控制生成风格（安全、格式、推理能力） 平衡 exploration vs exploitation GRPO（Generalized Reward Policy Optimization）原理 1. 背景 GRPO 是在 RLHF 场景中提出的一种去 Value Function（无 critic）的优化方法，其目标是：\n简化 PPO 训练流程，降低工程复杂度，同时提升吞吐。\n2. 核心思想 GRPO 的关键在于：\n不训练 value model 不计算 advantage（或用简化替代） 直接基于 reward 进行相对优化 典型流程：\n对同一 prompt 采样多个输出（N samples） 使用 reward model 打分 做归一化或排序 使用相对奖励更新策略 可表示为：\nLGRPO=E[log⁡πθ(a∣s)⋅R^(a)] L^{GRPO} = \\mathbb{E}\\left[\\log \\pi_\\theta(a|s) \\cdot \\hat{R}(a)\\right] LGRPO=E[logπθ​(a∣s)⋅R^(a)]其中：\nR^(a) \\hat{R}(a) R^(a)：归一化后的 reward（如 rank-based 或 mean-centered） 3. 关键特点 无 Critic 架构 避免 value model 训练的不稳定性。\nBatch 内对比学习 依赖同一 prompt 下多个样本的“相对优劣”。\n高吞吐 更适合大规模并行训练（尤其是多 GPU 推理采样）。\nPPO vs GRPO：核心差异对比 维度 PPO GRPO 是否使用 Value Model ✅ 使用 ❌ 不使用 Advantage 计算 ✅ GAE ❌ 无（或简化） 稳定性 ⭐⭐⭐⭐⭐ ⭐⭐⭐ 训练复杂度 高 低 样本效率 高 中 并行友好性 中 高 对 reward 依赖 中 高 对数据质量敏感性 中 高 为什么 GLM-5.2 从 GRPO 切换到 PPO？ 这是本文的核心问题。\n1. GRPO 的瓶颈 尽管 GRPO 在工程上更简单，但存在几个关键问题：\nReward 噪声放大 GRPO 强依赖 reward 的“相对排序”，当：\nreward model 不够精确 多样本之间差异较小 会导致：\n梯度信号极不稳定\n缺乏长期信用分配（Credit Assignment） GRPO 没有 value function：\n无法建模长期回报 对长链推理（CoT）不友好 训练容易 collapse 在某些情况下：\n模型会过拟合 reward model 输出趋于模式化（mode collapse） 2. PPO 的优势在 GLM-5.2 中体现 更稳定的优化路径 PPO 的 clipping + advantage：\n避免策略震荡 保证逐步改进（monotonic improvement） 更好的推理能力（Reasoning） PPO 的 value function：\n能对“中间步骤”进行隐式建模 更适合 chain-of-thought / multi-step reasoning 对 reward model 依赖降低 相比 GRPO：\nPPO 不完全依赖 reward 排序 对 reward 噪声更鲁棒 更强的泛化能力 PPO 本质上优化的是：\n策略分布，而不是样本排序\n因此在：\n未见任务 长文本生成 工具调用 等场景中更稳定。\n一个直观理解：排序 vs 回归 可以用一个类比帮助理解：\nGRPO：像做“排序学习”\n哪个答案更好？ 强依赖 pairwise / listwise 对比 PPO：像做“回归优化”\n当前策略离最优策略有多远？ 有连续的优化方向 结论：\nGRPO 更“快”，PPO 更“稳且准”。\n总结 GLM-5.2 从 GRPO 切换到 PPO，本质上是一次：\n从工程效率优先 → 模型能力优先 的转变\n关键结论 GRPO 适合：\n快速训练 大规模并行 reward 明确的任务 PPO 更适合：\n高质量对齐 复杂推理任务 长上下文生成 在当前 LLM 发展阶段：\n稳定性、泛化能力、推理能力 \u0026gt; 训练吞吐\n因此，PPO 的回归成为一个“必然选择”。\n结语 强化学习算法的选择，正在成为大模型能力差异的关键分水岭。从 GRPO 到 PPO，不只是算法切换，更代表着：\n大模型训练从“能跑”走向“跑得好、跑得稳”。\n未来我们很可能看到：\nPPO + DPO 混合范式 或新的 off-policy RL 方法 甚至 reward-free alignment 但在当前阶段：\nPPO 仍然是最稳健的工业级选择。\n","permalink":"https://wilsonwu.me/zh/blog/2026/ppo-vs-grpo/","summary":"\u003ch2 id=\"引言\"\u003e引言\u003c/h2\u003e\n\u003cp\u003e在大模型后训练（Post-training）阶段，强化学习（RLHF / RLAIF）已经成为决定模型能力上限的关键因素之一。近期，GLM-5.2 在训练算法上从 GLM-5.1 使用的 GRPO（Generalized Reward Policy Optimization）切换到更经典的 PPO（Proximal Policy Optimization），并带来了明显的效果提升。\u003c/p\u003e","title":"PPO vs GRPO：两种主流 LLM 强化学习范式的对比与选择"},{"content":"在多模型并存的 AI 架构中（如 GPT-4 / GPT-4o / 轻量模型 / 垂直模型等），一个核心问题是：\n如何在不显式指定模型 ID 的情况下，让系统自动选择最合适的模型？\n本文介绍一种工程可落地的方案：\n通过 Embedding 模型计算用户意图 → 在网关层进行语义匹配 → 动态路由到最合适的上游模型服务\n我们将基于：\nEmbedding 模型：Azure OpenAI text-embedding-3-small 网关：Envoy 核心机制：向量相似度打分 + 策略路由 问题背景与动机 在真实生产环境中，大模型调用通常面临几个挑战：\n1. 成本与性能权衡 模型类型 优点 缺点 大模型（如 GPT-4） 能力强 成本高 / 延迟高 小模型（如 GPT-4o-mini） 快 / 便宜 能力有限 专用模型 精准 泛化弱 不同请求应该走不同模型，而不是“一刀切”。\n2. 传统做法的局限 常见策略：\n手动指定 model_id ❌（不智能） 基于规则（if/else）❌（不可扩展） 基于关键词匹配 ❌（语义不鲁棒） 3. 理想目标 我们希望：\n用户只输入 prompt，系统自动理解“意图”，并选择最优模型\n这正是 Embedding 可以发挥作用的地方。\n核心原理：Embedding 驱动的语义路由 1. Embedding 本质 Embedding 模型（如 text-embedding-3-small）可以将文本映射为高维向量：\n\u0026#34;写一段 Python 排序代码\u0026#34; → [0.012, -0.98, ..., 0.33] (1536维向量) 语义相似的文本，其向量距离更近。\n2. 路由思路 核心逻辑可以抽象为：\n用户请求 → Embedding → 与“意图模板库”计算相似度 → 选择最佳匹配 → 路由模型 3. 意图模板（Intent Prototypes） 我们预先定义一组“意图向量”：\n意图类型 示例文本 目标模型 代码生成 \u0026ldquo;write code\u0026rdquo;, \u0026ldquo;generate function\u0026rdquo; GPT-4 简单问答 \u0026ldquo;what is\u0026rdquo;, \u0026ldquo;define\u0026rdquo; GPT-4o-mini 翻译 \u0026ldquo;translate this\u0026rdquo; 轻量模型 长文生成 \u0026ldquo;write blog\u0026rdquo; GPT-4 这些文本会提前计算 embedding 并缓存。\n4. 相似度计算 常用方法：\nCosine Similarity（推荐） similarity = cosine(query_embedding, intent_embedding) 选择最高分：\nargmax(similarity_i) 系统架构设计 整体架构如下：\n┌──────────────┐ │ Client │ └──────┬───────┘ │ ┌───────▼────────┐ │ Envoy 网关 │ │ (智能路由层) │ └───────┬────────┘ │ ┌──────────────┼──────────────┐ │ │ │ ┌──────▼─────┐ ┌──────▼─────┐ ┌──────▼─────┐ │ GPT-4 │ │ GPT-4o-mini│ │ 专用模型 │ └────────────┘ └────────────┘ └────────────┘ 实现逻辑拆解 1. 请求流程 Step 1：获取用户请求 { \u0026#34;input\u0026#34;: \u0026#34;帮我写一段 Go 的 HTTP server 示例\u0026#34; } Step 2：调用 Embedding API 使用 Azure OpenAI：\nPOST https://{endpoint}/openai/deployments/text-embedding-3-small/embeddings?api-version=2024-02-15-preview 请求体：\n{ \u0026#34;input\u0026#34;: \u0026#34;帮我写一段 Go 的 HTTP server 示例\u0026#34; } Step 3：计算相似度 伪代码：\ndef route(query_embedding): best_score = -1 best_model = None for intent in intents: score = cosine_similarity(query_embedding, intent.embedding) if score \u0026gt; best_score: best_score = score best_model = intent.model return best_model Step 4：Envoy 动态路由 将结果写入 Header：\nx-llm-route: gpt-4 Envoy 根据 Header 路由：\nroutes: - match: prefix: \u0026#34;/v1/chat\u0026#34; headers: - name: x-llm-route exact_match: gpt-4 route: cluster: gpt4_service Envoy 网关实现方式 Envoy 本身不直接支持向量计算，因此需要扩展：\n方案 1：External Processing（推荐） Envoy 调用外部服务（ext_proc）：\nEnvoy → ext_proc 服务 → 返回路由决策 → Envoy 转发 ext_proc 负责：\n调用 embedding 计算相似度 返回 header 方案 2：WASM 插件 在 Envoy 内嵌逻辑：\n优点：低延迟 缺点：开发复杂（Rust/CPP） 方案 3：Sidecar Router（简单） Client → Router Service → Envoy → Model Router 服务负责所有智能逻辑。\n优化策略 1. 向量缓存 避免重复调用 embedding：\ncache[input_hash] = embedding 2. 意图分层 避免单层分类过粗：\n一级：代码 / 问答 / 写作 二级：语言 / 长度 / 难度 3. 阈值控制 if best_score \u0026lt; threshold: fallback_model = \u0026#34;gpt-4\u0026#34; 防止误判。\n4. 多模型融合（Ensemble） 可扩展为：\nTop-K 模型候选 A/B 测试 加权路由 5. 成本感知路由 结合：\ntoken 预算 用户等级 SLA 做策略增强：\nif user == \u0026#34;free\u0026#34;: prefer_small_model() 优势分析 相比规则路由 维度 Embedding 路由 语义理解 强 可扩展性 高 维护成本 低 相比手动指定模型 用户体验更好（无需关心模型） 自动最优成本 支持动态策略 落地建议 1. 初始阶段 5~10 个意图模板即可 手动标注 + 调优 2. 数据驱动优化 记录：\n用户请求 路由结果 用户反馈 持续优化 intent embedding。\n3. 与 LLM Router 结合 未来可以升级为：\n用小模型 + embedding 混合决策（Hybrid Router）\n总结 本文方案的本质是：\n用 Embedding 替代规则，用向量相似度驱动模型选择\n关键收益：\n降低成本（智能选择轻量模型） 提升效果（复杂任务走强模型） 架构解耦（路由逻辑与模型解耦） 在多模型时代，这种“语义驱动的网关”会成为标准架构组件。\n","permalink":"https://wilsonwu.me/zh/blog/2026/llm-smart-router/","summary":"\u003cp\u003e在多模型并存的 AI 架构中（如 GPT-4 / GPT-4o / 轻量模型 / 垂直模型等），一个核心问题是：\u003c/p\u003e\n\n\u003cblockquote\u003e\n  \u003cp\u003e\u003cstrong\u003e如何在不显式指定模型 ID 的情况下，让系统自动选择最合适的模型？\u003c/strong\u003e\u003c/p\u003e\n\n\u003c/blockquote\u003e","title":"让模型自己选模型：Embedding 驱动的 LLM 智能路由机制"},{"content":"\n在过去一年里，AI 编程的主流交互方式经历了一个明显演进：\nPrompt → Prompt Engineering → Context Engineering → Skill（能力封装）\n如果说 Prompt 是一次性调用模型能力，那么 Skill 本质上是在构建“可复用的智能能力模块”，它让 AI 从“会回答问题”，进化为“能执行任务”。\n这篇文章将系统讲清：\n什么是 AI 编程中的 Skill Skill 与 Prompt 的本质区别 如何在 VS Code + GitHub Copilot Chat 中使用 Skill 思维 实战案例（工程级） 什么是 Skill？ 在 AI 编程语境下，Skill = 结构化提示 + 上下文 + 执行约束 + 输出格式\n可以用一个更工程化的定义：\nSkill = f(Instruction, Context, Constraints, Output Schema) 它不是一句 prompt，而是一个“可复用的能力接口”。\n对比理解 方式 特点 问题 Prompt 临时输入 不可复用、不稳定 Prompt Engineering 优化表达 仍然是一次性 Skill 能力封装 可复用、可组合、可治理 为什么 Skill 很重要？ 在实际开发中，AI 使用很快会遇到几个瓶颈：\n1. Prompt 不稳定 同一个问题，换个说法结果不同\n2. 团队无法复用经验 每个人都有自己的“私有 prompt”\n3. AI 输出不可控 格式、风格、边界难以统一\nSkill 的价值 标准化能力 沉淀团队最佳实践 降低认知负担 提升输出一致性 本质上：Skill 是 AI 时代的“函数”或“API”\nVS Code 中的 Skill 实现方式 虽然 VS Code 本身没有官方“Skill”概念，但可以通过以下机制实现：\n1. .github/copilot-instructions.md 这是最接近“全局 Skill”的机制。\n作用：\n为 Copilot 提供长期上下文 约束 AI 的行为方式 示例：\n# Backend Coding Guidelines - Use clean architecture - Follow RESTful API design - Add unit tests for all services - Use dependency injection # Code Style - Prefer TypeScript over JavaScript - Use async/await instead of callbacks 这相当于一个“全局 Skill 注入器”\n2. 项目级 Skill（README / project.md） 你可以在项目中定义：\n# AI Skill: API Generator When generating APIs: - Use OpenAPI 3.0 spec - Include request/response schema - Add validation rules - Provide curl examples 3. 会话级 Skill（Copilot Chat） 在 Chat 中你可以定义一次性 Skill：\nYou are a senior backend architect. Task: - Refactor code to hexagonal architecture - Keep business logic pure - Extract adapters Output: - Folder structure - Example code GitHub Copilot Chat 中的 Skill 用法 Copilot Chat 是 Skill 最直接的使用场景。\n下面给你几个工程级实战例子。\n示例 1：代码重构 Skill 输入（Skill） Act as a senior software architect. Refactor this code: - Apply clean architecture - Separate domain, application, infrastructure - Add dependency inversion Output: 1. Folder structure 2. Refactored code 3. Explanation 效果 AI 不再只是“改代码”，而是：\n输出结构设计 给出分层方案 解释设计决策 从“补全工具”变成“架构助手”\n示例 2：测试生成 Skill You are a QA engineer. Generate unit tests: - Use Jest - Cover edge cases - Include mocks - Ensure \u0026gt;80% coverage Output: - Test file - Coverage explanation 优势：\n输出结构稳定 测试风格统一 可团队复用 示例 3：API 文档生成 Skill Act as an API documentation generator. Given code: - Extract endpoints - Generate OpenAPI spec - Add request/response examples Output in YAML 直接产出可用 API 文档\n示例 4：代码评审 Skill（强烈推荐） Act as a senior code reviewer. Review this code: - Check performance issues - Identify security risks - Suggest improvements Output: - Issues list - Severity level - Fix suggestions 这个 Skill 在团队中极其高价值\n进阶：Skill 组合（Skill Pipeline） 真正强大的不是单个 Skill，而是Skill 组合：\n需求 → API Skill → 实现 Skill → 测试 Skill → Review Skill 形成一个 AI 开发流水线：\n生成 API 生成实现 生成测试 自动 review 本质：AI 驱动的 Dev Pipeline\n最佳实践 1. Skill 必须结构化 不要写：\n帮我优化代码 要写：\nOptimize code: - Reduce time complexity - Improve readability - Add comments Output: - Before/After - Explanation 2. 强制输出格式 Output in JSON: { \u0026#34;issues\u0026#34;: [], \u0026#34;fixes\u0026#34;: [] } 这对自动化极其重要\n3. 限定角色（Role Prompting） Act as: - Staff Engineer - Security Expert - Performance Engineer 4. Skill 应该可复制 建议统一放在：\n.github/copilot-instructions.md docs/ai-skills.md 内部 wiki Skill vs Agent（很多人会混淆） 概念 本质 Skill 单一能力 Agent 多 Skill + 自动决策 Skill 是积木 Agent 是机器人\n总结 AI 编程正在从：\n“怎么问 AI” → “怎么设计 AI 能力”\nSkill 的出现意味着：\nPrompt 不再是核心 能力设计才是核心 AI 正在变成“开发基础设施” 未来优秀的工程师，不只是写代码的人，而是设计 AI 能力的人\n","permalink":"https://wilsonwu.me/zh/blog/2026/ai-skills/","summary":"\u003cp\u003e\u003cimg alt=\"AI 编程 Skill 时代\" loading=\"lazy\" src=\"/zh/blog/2026/ai-skills/1-ai-skills.png\"\u003e\u003c/p\u003e\n\u003cp\u003e在过去一年里，AI 编程的主流交互方式经历了一个明显演进：\u003c/p\u003e\n\n\u003cblockquote\u003e\n  \u003cp\u003ePrompt → Prompt Engineering → Context Engineering → \u003cstrong\u003eSkill（能力封装）\u003c/strong\u003e\u003c/p\u003e\n\n\u003c/blockquote\u003e\n\u003cp\u003e如果说 Prompt 是一次性调用模型能力，那么 \u003cstrong\u003eSkill 本质上是在构建“可复用的智能能力模块”\u003c/strong\u003e，它让 AI 从“会回答问题”，进化为“能执行任务”。\u003c/p\u003e","title":"AI 编程进入 Skill 时代：从提示词到“能力封装”的跃迁"},{"content":"\n最近，单机游戏圈被一颗“深水炸弹”炸开了锅。原本号称坚不可摧的 Denuvo（D加密）阵营，在短短几周内接连失守。 《如龙 8》、《无主之地 4》以及备受瞩目的《剑星》PC 版相继传出被攻破的消息，就连国货之光《黑神话：悟空》都没有幸免遇难。\n然而，这次的“攻臣”不再是传统的黑客小组，而是一个名为 Hypervisor 的新型技术手段。在白嫖党欢呼的同时，安全专家们却发出了前所未有的严厉警告：这可能是一场以出卖电脑底层控制权为代价的博弈。\n什么是 Hypervisor 破解？（硬核科普） 传统的破解方式（如著名的 Empress）通常是“外科手术式”的：黑客通过成千上万次的调试，寻找 D 密代码中的逻辑漏洞并将其绕过或剔除。 而 Hypervisor（虚拟机监控器）破解则更像是一种“维度打击”。它不再尝试修改游戏文件，而是在你的操作系统（Windows）之下，强行插入一个极薄的软件层。\n它的原理： 利用 CPU 的硬件虚拟化技术（VT-x 或 AMD-V），欺骗 D 加密系统，让其认为自己运行在完美的硬件环境中。 它的位置： 它的运行级别是 Ring -1，比 Windows 内核（Ring 0）还要深。它能拦截并伪造所有硬件指令，让 D 密在毫秒间被“降维打击”。 对玩家的影响：这不只是一个补丁 如果你打算尝试这种破解方式，你面临的不仅仅是安装游戏，还有以下“额外负担”：\n极高的技术门槛：你必须深入 BIOS 开启虚拟化设置。如果你的硬件较老，或者系统开启了 VBS（基于虚拟化的安全性），补丁将直接导致蓝屏死机。 绝对的安全黑洞：这是最致命的一点。由于 Hypervisor 运行在系统最底层，现有的任何杀毒软件都无法监控它。如果破解者在其中植入了挖矿程序、键盘记录器甚至固件级后门，你的电脑将彻底变成他人的“肉鸡”，且重启和重装系统都未必能根除。 系统稳定性雪崩：这种强行插入的“薄层”极易与显卡驱动或正版游戏的反作弊系统（如小蓝熊 EAC）冲突。轻则游戏掉帧、瞬间卡顿，重则系统频繁崩溃。 社区的争议：这是一把双刃剑 目前，包括 CS.RIN.RU 在内的全球知名破解论坛已开始对 Hypervisor 相关补丁采取限制措施。很多资深开发者认为，虽然这种技术在破解效率上惊人，但它破坏了用户对系统的基本控制权，将玩家推向了极大的安全隐患之中。\n结语：为什么我们依然呼吁支持正版？ 在这个“一键破解”看似爽快的时代，我们更需要冷静思考：免费的午餐，往往标着最昂贵的价码。\n为了你的数据安全：相比于几百块钱的游戏票价，你电脑里的个人隐私、账号密码和硬件寿命显然更加无价。 为了行业的未来：3A 大作的开发动辄耗资数亿、历时数年。如果开发者无法通过作品获得合理回报，未来我们可能再也玩不到像《黑神话：悟空》或《剑星》这样精良的单机作品。 为了尊严与体验：正版玩家拥有云存档、成就系统和即时补丁更新，更重要的是，我们拥有一份心安理得的纯净游戏环境。 支持正版，不仅是为开发者的心血买单，更是为我们自己未来的游戏梦想投下一张赞成票。面对 Hypervisor 这样的“危险诱惑”，请握紧你的钱包，保护好你的电脑，把热爱留在阳光下。\n","permalink":"https://wilsonwu.me/zh/blog/2026/hypervisor-vs-denuvo/","summary":"\u003cp\u003e\u003cimg alt=\"破解 D 加密\" loading=\"lazy\" src=\"/zh/blog/2026/hypervisor-vs-denuvo/1-crack-denuvo.png\"\u003e\u003c/p\u003e\n\u003cp\u003e最近，单机游戏圈被一颗“深水炸弹”炸开了锅。原本号称坚不可摧的 Denuvo（D加密）阵营，在短短几周内接连失守。\n《如龙 8》、《无主之地 4》以及备受瞩目的《剑星》PC 版相继传出被攻破的消息，就连国货之光《黑神话：悟空》都没有幸免遇难。\u003c/p\u003e","title":"3A 大作集体“沦陷”？深度解析 Hypervisor 破解风波"},{"content":"\n随着生成式 AI 与智能代理的快速发展，自托管的智能系统正变得越来越受开发者和技术团队关注。Clawdbot 就是这样一款开源、可自托管的个人 AI 助手，它不仅能与用户进行对话，还能执行任务、集成消息平台、提供自动化能力，并可部署在云平台上如 Azure。本文将逐步介绍 Clawdbot 的能力、架构以及如何在 Microsoft Azure 上部署并开始使用它。\n什么是 Clawdbot Clawdbot 是一个开源的个人 AI 助手项目，本质上是一个智能代理框架，它可以：\n与多种消息平台进行交互，包括 WhatsApp、Telegram、Discord、Slack、Microsoft Teams 等。 承担智能对话、任务执行、自动化工作流的角色，而非单纯聊天机器人。 支持在本地或自托管服务器上运行，数据默认保存在本地，增强隐私保护。 通过插件/技能扩展，实现日程提醒、文件管理、邮件处理、代码生成等功能。 Clawdbot 的核心由 Gateway、Providers（平台接入）、Agents/Skills（智能任务执行单元）构成。Gateway 负责消息总线、Providers 负责与各消息平台对接，Agents/Skills 则实际执行用户请求。\nClawdbot 的主要特性 多平台消息接入 Clawdbot 支持通过连接聊天平台 API，接收来自 WhatsApp、Telegram、Discord、Signal、Slack、Microsoft Teams 等消息，并将这些消息传递给 AI 模型进行处理。\n隐私和数据控制 与云端托管的聊天 AI 不同，Clawdbot 可以运行在你自己的服务器或 VPS 上，所有对话历史、配置、凭据都可以按需存储和访问。\n自动化执行能力 除了对话，Clawdbot 还能执行具体任务，包括浏览器控制、文件操作、数据处理、发送提醒等，这使其成为一个更为复杂的个人助手平台。\n可扩展的模型支持 Clawdbot 支持多种 AI 模型提供者（如 OpenAI GPT、Anthropic Claude，甚至本地 LLM 服务如 Ollama）。\n在 Azure VM 部署 Clawdbot 虽然 Clawdbot 支持本地运行或容器平台，但在以下场景中，Azure VM 是非常合适的部署形态：\n完全控制运行环境\n自由选择 OS（Ubuntu / Debian） 自由安装 Node.js、Docker、GPU 驱动等 数据与隐私可控\n所有对话记录、日志、配置文件仅存在于 VM 内 不依赖第三方 PaaS 适合长期运行的 Agent\n7×24 小时在线 可通过 systemd 或 Docker 保证自动重启 可扩展性良好\n可横向扩容（多 VM + 负载均衡） 可纵向扩容（更大规格 CPU / GPU） 整体部署架构概览 在 Azure VM 上运行 Clawdbot 的典型架构如下：\n创建 Azure VM VM 基础配置建议 OS：Ubuntu 22.04 LTS 规格： 轻量使用：Standard B2s / B4ms 多 Agent：D4s_v5 及以上 磁盘：至少 64 GB 网络： 入站：22（SSH） 入站：3000（或自定义 Clawdbot 端口） 登录 VM ssh azureuser@\u0026lt;VM_PUBLIC_IP\u0026gt; 安装运行时环境 系统依赖 sudo apt update \u0026amp;\u0026amp; sudo apt upgrade -y sudo apt install -y git curl build-essential 安装 Node.js（推荐 20 LTS） curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt install -y nodejs node -v npm -v 安装 Clawdbot 全局 CLI + 本地运行目录 sudo npm install -g clawdbot 创建运行目录：\nmkdir ~/clawdbot cd ~/clawdbot 初始化配置：\nclawdbot onboard 跟着步骤完成各类配置即可：\n启动 Clawdbot 手动启动验证 clawdbot gateway 确认日志中出现：\n🦞 Clawdbot 2026.1.24-3 (885167d) — I speak fluent bash, mild sarcasm, and aggressive tab-completion energy. ... 16:18:27 [heartbeat] started 16:18:27 [gateway] agent model: openai-codex/gpt-5.1 16:18:27 [gateway] listening on ws://127.0.0.1:18789 (PID 36888) 16:18:27 [gateway] listening on ws://[::1]:18789 16:18:27 [gateway] log file: \\tmp\\clawdbot\\clawdbot-2026-01-27.log 16:18:27 [browser/server] Browser control listening on http://127.0.0.1:18791/ ... 通过浏览器打开：http://127.0.0.1:18789/\n即可开始使用！\n结语 Clawdbot 是一款功能强大的开源 AI 个人助手，可将 AI 交互扩展到多种消息平台，并集成丰富的执行能力。通过将 Clawdbot 部署到 Azure，你可以将它打造成一个企业级、自托管的 AI 服务，提高可用性、安全性和可扩展性。上述步骤覆盖了从创建 Azure Bot 到完整部署 Clawdbot 的全流程，希望能帮助你快速搭建并运行自己的智能助理。\n","permalink":"https://wilsonwu.me/zh/blog/2026/clawdbot-on-azure/","summary":"\u003cp\u003e\u003cimg alt=\"Clawdbot\" loading=\"lazy\" src=\"/zh/blog/2026/clawdbot-on-azure/1-clawdbot.png\"\u003e\u003c/p\u003e\n\u003cp\u003e随着生成式 AI 与智能代理的快速发展，自托管的智能系统正变得越来越受开发者和技术团队关注。\u003cstrong\u003eClawdbot\u003c/strong\u003e 就是这样一款开源、可自托管的个人 AI 助手，它不仅能与用户进行对话，还能执行任务、集成消息平台、提供自动化能力，并可部署在云平台上如 Azure。本文将逐步介绍 Clawdbot 的能力、架构以及如何在 \u003cstrong\u003eMicrosoft Azure\u003c/strong\u003e 上部署并开始使用它。\u003c/p\u003e","title":"Clawdbot：让您在 Azure 上拥有一个强大的 AI 全能助手"},{"content":"\n2025 年末，微软研究院发布了一个面向 3D 内容创作的开源大模型项目 TRELLIS，并伴随学术论文《Structured 3D Latents for Scalable and Versatile 3D Generation》。该项目通过统一的结构化潜在空间与先进的流模型技术，显著提升了文本/图像到 3D 资产生成的质量与灵活性，同时拓展了 3D 模型的多格式输出与编辑能力，成为当前 3D AI 模型生态中的重要技术之一。\n官方 Github 仓库地址：https://github.com/microsoft/TRELLIS\nTRELLIS 是什么？——核心概念与架构 TRELLIS 是微软构建的大型 3D 资产生成模型，支持输入文本提示或图像，并输出高质量的三维模型资产。其技术创新点主要包括：\n统一结构化潜在表示（SLAT）：将三维信息编码为一种可扩展的结构化潜在空间表示，使模型能够以统一方式理解和生成不同表现形式的 3D 结果。 整流流（Rectified Flow Transformers）：专为 SLAT 设计的生成骨干网络，通过适应 sparse 表示提升生成效率与质量。 大规模训练与预训练模型：模型规模最高达约 20 亿参数，并在包含 50 万多样化三维资产的数据集上训练，具备强泛化能力。 TRELLIS 不仅能生成三维几何，还能捕获复杂纹理与外观信息，使得生成的资产更贴近真实世界中可用的三维内容。\n主要功能特性 以下是 TRELLIS 的核心功能模块及技术亮点：\n多模态输入能力 TRELLIS 支持以下输入条件：\n文本提示（Text-to-3D）：通过自然语言描述生成三维资产（提示须清晰准确）。 图像条件（Image-to-3D）：根据一张或多张图片生成对应三维模型。 这种多模态输入支持，使 TRELLIS 适用于从概念设计到真实原型的全流程创作。\n灵活的输出格式 根据下游需求，TRELLIS 输出包括：\n辐射场（NeRF/Radiance Fields）：适合渲染与展示 3D 高斯表示：一种可渲染密度表示 传统网格 Mesh：可导出为标准 3D 文件格式（例如 GLB/OBJ）用于游戏、AR/VR 等应用 这种格式灵活性是模型在实际生产环境中落地的关键能力。\n本地三维编辑与变体生成 与早期单一生成模型不同，TRELLIS 能：\n局部编辑：在已有模型上进行细部修改 生成变体：在保持基本结构不变的前提下，创建风格、细节不同的多版本输出 这种能力特别适合迭代式 3D 内容设计。\n支持大规模数据集训练与可扩展性 TRELLIS 在包含多种来源（如 Objaverse、ABO 等）的大规模 3D 数据集上进行训练，使其具备：\n强泛化能力 更丰富的几何与纹理表现 多任务适配能力 这样的数据基础是其较竞品更具优势的根本原因之一。\n与其他竞品对比：优势与提升点 目前市场上其他比较知名的 3D 生成模型（例如 OpenAI Shap-E、或研究型模型如 MeshGen 等）多存在以下局限：\n有的只能输出单一 3D 表示（如内隐函数或点云） 多数不支持本地编辑与多种格式输出 在复杂纹理、细节表现上效果有限 相比之下：\nTRELLIS 的主要优势包括 结构化潜在表示（SLAT）框架：支持统一的生成与解码机制，同时兼顾几何与纹理细节。 多格式生成输出：与只生成单种数据结构的竞品相比更通用。 本地编辑与变体生成：提升了设计迭代效率。 大规模预训练与更丰富数据集支撑：提升了模型对稀有或复杂对象的生成能力。 总的来说，TRELLIS 更偏向工业级 3D 生成工具，而不只是科研展示或单一生成模型。\n部署和使用 目前，微软尚未在 Azure AI Foundry / Microsoft Foundry 中以 Serverless 或托管模型的形式直接提供 TRELLIS。因此，在 Azure 云环境中使用 TRELLIS 的最可行、也是最稳定的方式，是基于 Azure GPU 虚拟机（VM）进行自托管部署。\n这种方式具备以下优势：\n完全控制模型版本与推理流程 支持大显存 GPU，适合 3D 模型生成场景 可与企业现有 Azure 网络、安全、存储体系无缝集成 1. Azure GPU VM 规格选型建议 TRELLIS 属于显存与算力敏感型 3D 生成模型，对 GPU 资源要求较高，推荐如下规格：\n推荐 VM 系列 Standard_NC A100 v4（A100 80GB，首选） Standard_NC A100 v5（A100 80GB，新一代） Standard_ND H100 v5（H100，若需要更高吞吐） 实践建议：\n推理 / Demo：单卡 A100 80GB 即可 高分辨率 / 批量生成 / 编辑任务：建议单机多卡或更高规格 2. 操作系统与基础环境 推荐环境组合如下：\nOS：Ubuntu 20.04 / 22.04 LTS CUDA：与 VM 驱动匹配的官方版本 Python：3.8 或以上 PyTorch：官方 CUDA 版本 Azure Marketplace 中可直接选择 CUDA + NVIDIA Driver 预装镜像，可显著降低环境准备成本。\n3. TRELLIS 安装与依赖配置 拉取代码 git clone --recurse-submodules https://github.com/microsoft/TRELLIS.git cd TRELLIS 创建独立 Python 环境并安装依赖 官方已提供自动化脚本：\n./setup.sh --new-env --basic 如需支持训练、编辑或高级功能，可根据官方说明启用对应参数。\n4. 基于 Azure VM 的推理运行方式 方式一：命令行推理（适合批量任务） 文本或图像驱动生成 3D 模型：\npython example_text.py \\ --prompt \u0026#34;a futuristic electric motorcycle\u0026#34; \\ --output_dir outputs/ 或多图像条件输入：\npython example_multi_image.py \\ --input_images images/ \\ --output_dir outputs/ 生成结果可直接导出为 Mesh / Radiance Field 等格式，用于后续渲染或工具链集成。\n方式二：Gradio Web UI（推荐用于演示与验证） TRELLIS 官方提供基于 Gradio 的 Web Demo，适合：\n团队内部体验 产品验证 算法效果对比 python app.py 然后通过 Azure VM 公网 IP 或内网负载均衡访问 Web UI。\n5. Azure 上的工程化部署建议 为了更好地在 Azure 上长期运行 TRELLIS，建议采用以下架构实践：\n存储解耦 模型权重：Azure Blob Storage 生成结果：Blob / Azure Files 日志与元数据：Azure Monitor / Log Analytics 服务化封装 使用 FastAPI / Flask 将 TRELLIS 推理封装为 HTTP API GPU VM 作为推理节点 前端或工作流通过 REST 调用 安全与网络 VM 置于 VNet 内 Web UI / API 通过 Application Gateway 或内部访问 支持私有部署，满足企业安全要求 总结与展望 微软 TRELLIS 代表了当前 3D AI 生成技术的重要进步，它在生成质量、格式多样性与本地编辑能力方面具备显著优势。对于希望自动化 3D 内容创建的开发者与设计者而言，它是一个有实力的开源方案。部署方面，虽然目前官方尚未将其作为 Foundry 的 serverless 模型，但可通过 Azure 的常规模型部署管道与 GPU 计算能力在云端高效运行 TRELLIS 推理与自定义训练。\n","permalink":"https://wilsonwu.me/zh/blog/2026/llm-microsoft-trellis-3d/","summary":"\u003cp\u003e\u003cimg alt=\"Trellis-3D\" loading=\"lazy\" src=\"/zh/blog/2026/llm-microsoft-trellis-3d/1-trellis-3d.png\"\u003e\u003c/p\u003e\n\u003cp\u003e2025 年末，微软研究院发布了一个面向 3D 内容创作的开源大模型项目 \u003cstrong\u003eTRELLIS\u003c/strong\u003e，并伴随学术论文\u003ca href=\"https://arxiv.org/abs/2412.01506\"\u003e《Structured 3D Latents for Scalable and Versatile 3D Generation》\u003c/a\u003e。该项目通过统一的结构化潜在空间与先进的流模型技术，显著提升了文本/图像到 3D 资产生成的质量与灵活性，同时拓展了 3D 模型的多格式输出与编辑能力，成为当前 3D AI 模型生态中的重要技术之一。\u003c/p\u003e","title":"微软 TRELLIS 大语言模型：面向生产级 3D 资产生成的大模型及基于 Azure 部署使用指南"},{"content":"当大语言模型的参数规模跨过几十亿门槛之后，一个事实变得不可回避：单卡时代已经结束。无论是训练还是推理，模型本身的参数规模、KV Cache 的增长速度，以及真实业务中并发请求带来的显存与算力压力，都决定了大模型必须运行在多 GPU、甚至多节点的环境中。\n然而，用多张卡跑模型并不是一个简单的资源堆叠问题。在实践中，工程团队很快会发现：不同的拆分方式，带来的性能表现差异可能是数量级的。有的配置 TTFT 极低，却在高并发下迅速失速；有的方案吞吐惊人，却难以满足在线服务对延迟的要求；还有一些组合在理论上看似合理，却在真实硬件拓扑下被通信成本完全吞噬。\n在训练与推理阶段，业界逐步沉淀出四类核心并行策略：\nTP（Tensor Parallelism，张量并行） DP（Data Parallelism，数据并行） PP（Pipeline Parallelism，流水线并行） EP（Expert Parallelism，专家并行，主要用于 MoE） 大语言模型四种核心并行策略基础 Tensor Parallelism（TP）：算子级拆分 核心思想：将单层内部的矩阵计算（如 Linear / Attention）沿维度切分到多张 GPU 上并行计算。\n典型拆分方式：\nLinear 层按 hidden_size 或 num_heads 切分 Attention 的 Q / K / V 或 FFN 的中间层拆分 优点：\n单层显存占用显著下降 单 token 延迟较低（适合推理） 代价：\n每一层都需要 All-Reduce / All-Gather 通信频繁，对 NVLink / IB 依赖高 适用场景：\n单模型过大但层数不多 对 TTFT（Time To First Token）敏感的在线推理 Data Parallelism（DP）：请求级复制 核心思想：模型完整复制到多张 GPU，每张 GPU 处理不同请求或 batch。\n优点：\n实现简单 无模型内部通信 吞吐线性扩展 代价：\n显存需求最高（每卡一整模型） 单请求延迟不变 适用场景：\n模型能放进单卡 离线推理 / 高并发吞吐 Pipeline Parallelism（PP）：层级切分 核心思想：将模型按层切分到多张 GPU，请求在 GPU 间流水线式流动。\n优点：\n几乎无层内通信 显存效率高（比 DP 好） 代价：\npipeline bubble（气泡）带来空转 小 batch 下延迟显著升高 适用场景：\n超深模型（如 70B+） 长序列推理或训练 Expert Parallelism（EP）：MoE 专属并行 核心思想：在 MoE（Mixture of Experts）模型中，不同 Expert 分布在不同 GPU 上，仅被激活的 Expert 参与计算。\n优点：\n参数规模大但计算稀疏 性能/参数比极高 代价：\nToken 路由与负载不均 通信模式复杂 适用场景：\nMoE 模型（如 Mixtral、DeepSeek-MoE） 高参数规模、可接受一定调度复杂度 并行策略并非互斥，而是可组合的 在真实系统中，并行策略通常组合使用：\n组合方式 典型用途 TP × DP 推理最常见组合 TP × PP 超大模型训练 / 推理 TP × EP MoE 推理 DP × EP 高吞吐 MoE 服务 TP × PP × DP 大规模分布式集群 问题不在于能不能用，而在于：不同组合，在真实推理负载下，性能表现差异极大。\n这正是 vLLM 非常适合做验证的原因。\nvLLM 中的并行能力概览 vLLM 是一个面向高性能 LLM 推理的引擎，其核心优势包括：\n高效 KV Cache 管理（PagedAttention） 原生支持 TP / PP 对 DP 友好（多实例、多副本） MoE 模型中支持 EP（依赖模型实现） 在 vLLM 中，并行配置主要通过启动参数 + 资源拓扑控制。\n基于 vLLM 的并行策略配置示例 Tensor Parallelism（TP） vllm serve meta-llama/Llama-2-70b-hf \\ --tensor-parallel-size 4 含义：\n模型每一层被拆分到 4 张 GPU 适合单请求低延迟验证 Pipeline Parallelism（PP） vllm serve meta-llama/Llama-2-70b-hf \\ --pipeline-parallel-size 2 \\ --tensor-parallel-size 2 含义：\n2-stage pipeline 每 stage 内 2-way TP 总 GPU 数 = 4 Data Parallelism（DP） vLLM 本身不显式声明 DP，而是通过多实例部署实现：\n# GPU 0-3 CUDA_VISIBLE_DEVICES=0,1,2,3 vllm serve ... # GPU 4-7 CUDA_VISIBLE_DEVICES=4,5,6,7 vllm serve ... 再通过上层负载均衡（如 Kubernetes / Gateway）实现 DP。\nExpert Parallelism（EP） 对于 MoE 模型（如 Mixtral）：\nvllm serve mistralai/Mixtral-8x7B-Instruct \\ --tensor-parallel-size 4 vLLM 会自动将 Expert 分布到 TP 组内 GPU（模型内部实现 EP）。\n如何在 vLLM 中“验证”并行组合的真实性能 核心评测指标 建议至少关注以下指标：\n指标 含义 TTFT 首 token 延迟 Tokens/s 生成吞吐 GPU 利用率 并行效率 KV Cache 命中率 长上下文效率 通信开销 TP / EP 成本 推荐对比实验矩阵 以 8 GPU 为例：\n方案 TP PP DP 适用目标 A 8 1 1 最低延迟 B 4 2 1 显存均衡 C 2 2 2 吞吐优先 D 1 1 8 最大并发 通过固定输入长度与请求并发，对比上述方案即可得到清晰结论。\n实际经验结论（常见规律） TP 越大，TTFT 越低，但通信越重 PP 提升显存利用率，但对小 batch 极不友好 DP 是吞吐的唯一线性放大手段 MoE 的 EP 在低并发下可能反而变慢 总结 并行策略没有最优解，只有最适解，TP / DP / PP / EP 并不是竞争关系，而是：\n在模型规模、请求模式、硬件拓扑三者约束下的工程权衡。\nvLLM 提供了一个极其适合做并行策略验证与对比实验的平台。真正有价值的工作不是“支持了什么并行”，而是：\n在你的模型 在你的硬件 在你的真实请求负载下 验证哪种组合，才是最优解。\n","permalink":"https://wilsonwu.me/zh/blog/2025/llm-inference-parallelism-in-vllm/","summary":"\u003cp\u003e当大语言模型的参数规模跨过几十亿门槛之后，一个事实变得不可回避：\u003cstrong\u003e单卡时代已经结束\u003c/strong\u003e。无论是训练还是推理，模型本身的参数规模、KV Cache 的增长速度，以及真实业务中并发请求带来的显存与算力压力，都决定了大模型必须运行在多 GPU、甚至多节点的环境中。\u003c/p\u003e","title":"大语言模型推理并行策略全景解析：TP / DP / PP / EP 原理与 vLLM 实战性能验证"},{"content":"随着大语言模型（LLM）在对话系统、智能助手和 Agent 场景中的广泛落地，推理系统面临的核心挑战已从“能不能跑”转向“如何以更低延迟、更高吞吐、更稳定的方式运行”。在这一背景下，PD Disaggregated（Prefill / Decode 解耦） 逐渐成为大规模在线推理系统中的关键架构思想。\n本文将不依赖任何具体推理框架，从 模型推理执行流 的角度，系统阐述什么是 PD Disaggregated、为什么需要它，以及它为 LLM 推理系统带来的核心优势。\nLLM 推理的两个本质阶段 所有自回归大语言模型的推理过程，本质上都可以拆解为两个阶段：\nPrefill（预填充阶段） Prefill 阶段负责处理用户输入的完整 Prompt，其主要任务包括：\n对输入序列进行一次完整前向计算； 构建上下文对应的 KV Cache； 生成第一个输出 Token。 这一阶段具有如下特征：\n计算密集型（Compute-bound） 大规模矩阵乘法（GEMM）占主导 序列长度通常较长 对批处理（Batching）高度友好 Decode（解码阶段） Decode 阶段进入逐 Token 生成过程，每一步都会：\n输入上一个生成的 Token； 读取历史 KV Cache； 生成下一个 Token，直到结束条件满足。 其特征与 Prefill 明显不同：\n访存密集型（Memory-bound） 计算规模小，但 KV Cache 访问频繁 强延迟敏感 执行步数随生成长度线性增长 问题根源：Prefill 与 Decode 的“异构性” Prefill 与 Decode 并非同一类工作负载，它们在多个维度上呈现出显著异构性：\n维度 Prefill Decode 计算模式 大算子、密集计算 小算子、频繁访存 批处理需求 大 batch 效果最好 batch 过大反而增加延迟 延迟容忍度 相对较高 极低 资源瓶颈 算力 显存带宽 / KV Cache 如果将这两种阶段混合部署、统一调度，就会导致资源使用上的结构性低效。\n什么是 PD Disaggregated PD Disaggregated（Prefill / Decode 解耦），指的是在推理系统架构层面：\n将 Prefill 与 Decode 视为两类不同的工作负载，并在调度、资源分配甚至部署拓扑上对它们进行解耦设计。\n需要强调的是：\n这并不是模型结构的拆分； 也不是参数的拆分； 而是推理执行路径与资源使用方式的解耦。 PD Disaggregated 的核心思想是： 让 Prefill 为吞吐服务，让 Decode 为延迟服务。\n为什么必须进行 PD 解耦 GPU 利用率的结构性浪费 混合执行时常见现象包括：\nDecode 阶段算子太小，GPU 算力无法被充分利用； Prefill 阶段的大算子又会阻塞 Decode； 结果是整体 GPU 利用率“看起来不低，但效率很差”。 PD 解耦后，可以针对不同阶段采用不同的调度和批策略，显著改善有效算力利用。\n延迟不可预测，尾延迟恶化 在在线推理场景中，Decode 阶段直接决定用户体验：\n首 Token 延迟（TTFT） 单 Token 延迟（TPOT） P99 / P999 延迟 如果 Decode 被 Prefill 阻塞，哪怕吞吐提升，用户体验也会显著下降。 PD Disaggregated 能从架构层面隔离延迟敏感路径。\n扩展性受限 在混合架构下：\nPrefill 和 Decode 只能按同一比例扩容； 无法根据实际负载特征独立扩展某一阶段。 而真实业务中：\nChat、Agent 场景 Decode 占比极高； 离线生成、批处理 Prefill 占比更高。 PD 解耦使得 按阶段独立扩展成为可能。\nPD Disaggregated 的典型架构形态 在工程实践中，PD Disaggregated 通常体现为以下形式之一：\n调度级解耦（Soft Disaggregation） Prefill 与 Decode 共享模型参数； 但在调度层面对二者区别对待； Decode 拥有更高优先级。 这是最轻量、最容易落地的形态。\n资源级解耦（Resource Disaggregation） Prefill 与 Decode 使用不同的 GPU 资源池； Prefill GPU 偏向算力型； Decode GPU 偏向显存带宽与 KV Cache 容量。 该模式在大规模集群中尤为常见。\n服务级解耦（Service Disaggregation） Prefill 与 Decode 成为独立服务； 中间通过 KV Cache 或状态句柄连接； 可独立伸缩、独立调度。 这是 PD Disaggregated 的“完全形态”，也是复杂度最高的一种。\nPD Disaggregated 带来的核心优势 更高吞吐与更低延迟并存 传统架构往往需要在吞吐与延迟之间取舍，而 PD 解耦允许：\nPrefill 极致批量化； Decode 极致低延迟。 更稳定的尾延迟 Decode 不再被大规模 Prefill 阻塞，P99 / P999 延迟显著改善，这对生产系统至关重要。\n更合理的资源利用 不同阶段匹配不同资源类型，避免“用算力型 GPU 跑访存密集任务”这类结构性浪费。\n更强的系统演进能力 PD Disaggregated 为后续优化打开空间，例如：\nKV Cache 分层存储； Decode 专用硬件； 面向 Agent 的长时会话优化。 总结 PD Disaggregated 并不是某个框架的特性，而是对 LLM 推理本质的抽象与再设计：\nPrefill 和 Decode 在计算形态上天然异构； 强行混合只会掩盖效率问题； 解耦是走向规模化、高质量推理服务的必经之路。 可以预见，随着模型规模继续增长、应用形态日益复杂，PD Disaggregated 将从“高级优化”演变为 LLM 推理系统的默认架构假设。\n","permalink":"https://wilsonwu.me/zh/blog/2025/llm-pd-disaggregated/","summary":"\u003cp\u003e随着大语言模型（LLM）在对话系统、智能助手和 Agent 场景中的广泛落地，推理系统面临的核心挑战已从“能不能跑”转向“\u003cstrong\u003e如何以更低延迟、更高吞吐、更稳定的方式运行\u003c/strong\u003e”。在这一背景下，\u003cstrong\u003ePD Disaggregated（Prefill / Decode 解耦）\u003c/strong\u003e 逐渐成为大规模在线推理系统中的关键架构思想。\u003c/p\u003e","title":"大语言模型（LLM）推理加速：预填充（Prefill）和解码（Decode）解耦（PD 分离）"},{"content":"\n近年来，生成式 AI 技术迅速演进。在自然语言处理之外，图像生成与编辑能力成为 AI 创新的重要前沿。在这一趋势下，OpenAI 推出了 GPT Image 系列模型，在 Azure OpenAI 服务内同样可用。而其中最新发布的 GPT-Image-1.5 可视为图像生成领域的新旗舰，在性能、效率和可控性方面均有显著提升。\n什么是 GPT-Image-1.5 GPT-Image-1.5 是 OpenAI 最新发布的多模态图像生成模型，属于 GPT Image 系列，目前是效果最佳的版本。与前代（如 GPT-Image-1）相比，它在指令遵循能力、图像质量、生成速度和成本效率方面都有明显提升。\n官方定义上，GPT Image 模型是原生多模态语言模型（natively multimodal），能接收文本和图像输入，并生成图像输出。在功能上，它不仅支持从纯文本生成图像，还可以对已有图像进行编辑、修改、风格化转换等操作。\n简而言之：\n多模态：同时理解文本和图像输入。 图像生成与编辑：支持从文本生成全新图像，也支持对已有图像进行局部修改和增强。 高质量与高效：相比前代生成更精细、更快速，同时成本更低。 核心能力与特点 1. 指令遵循及表达精准 GPT-Image-1.5 在理解自然语言提示方面得到显著提升，尤其擅长：\n对复杂描述的视觉表达，细节理解更准确 在图像中生成清晰可读的文本内容 遵循用户指令进行定制化修改和组合 相比最初的图像生成模型（例如 DALL·E 系列），这类 GPT Image 系列模型对于提示语的响应更直观、可控性更高。\n2. 图像质量与生成速度提升 GPT-Image-1.5 的生成速度比上一代快得多（据报道最高可达大约 4 倍提升），这对于需要快速迭代视觉内容的场景（如设计、产品原型等）尤为重要。\n此外，图像生成结果在细节、现实感和一致性方面表现更好，尤其是在脸部、纹理、光影等关键元素上有显著进步。\n3. 编辑与增强功能 GPT-Image-1.5 同时支持图像编辑，包括：\n局部修改：对选中区域进行变更 添加或移除元素 风格化调整与概念转换 无需完全重新生成整幅图像，大幅提升工作流效率。\n4. 成本与效率优化 GPT-Image-1.5 在 API 调用成本上较前代降低约 20%，使得高质量图像生成在更大范围内可持续使用，特别适合企业级批量生成需求。\n5. 安全性与合规性 作为 Azure OpenAI 服务的一部分，GPT-Image-1.5 的使用受 Azure 的安全性、合规性和内容过滤体系保障，支持多种定制化审核与责任使用防护。\n在 Microsoft Foundry 中部署与使用 GPT-Image-1.5 随着 Microsoft Foundry 的推出（当前为预览版），微软正在将 Azure OpenAI、模型目录、推理端点、Playground 与企业治理能力整合为统一的 AI 应用构建平台。在该预览版中，GPT-Image-1.5 已作为一等模型（first-class model）被直接纳入 Foundry 的模型体系，无需再通过传统的 “Azure AI Foundry / Azure AI Studio” 路径进行管理。\n1. 在 Microsoft Foundry 中部署 GPT-Image-1.5 在 Preview 版 Foundry 中，模型部署流程更接近“产品级模型托管”而非传统 Azure 资源配置：\n首先进入 Microsoft Foundry（Preview）控制台。在左侧选择 Models（模型库）。在模型列表中找到 GPT-Image-1.5（Image / Multimodal 分类）。\n打开模型详情页后，点击 Deploy，创建一个新的推理部署。\n选择默认部署，简单选择 Region 后即可完成部署，整个过程非常简单：\n部署完成后会自动进入 GPT-Image-1.5 的 Playground：\n完成后，Foundry 会自动生成：\n一个可调用的 Inference Endpoint 与之绑定的身份、配额与监控策略 这一流程强调的是模型即服务（Model as a Service），开发者不再需要直接关心底层 Azure OpenAI 资源的复杂配置。\n2. 使用 Foundry Playground 进行图像生成与 Prompt 验证 Microsoft Foundry（Preview）内置了统一的 Playground，用于对 GPT-Image-1.5 进行交互式试验。\n在 Playground 中，你可以：\n直接输入文本 Prompt 生成图像 调整图像尺寸、质量等级等参数 快速验证复杂描述、风格化指令的效果 对比不同 Prompt 的生成结果 这一能力特别适合以下角色协作使用：\n产品经理：验证视觉创意可行性 设计师：探索生成风格与构图 工程师：在写代码前固化 Prompt 模板 Playground 在 Preview 版 Foundry 中的定位是 Prompt 即资产，验证通过的 Prompt 可以直接迁移到生产代码中使用。\n生成图片后效果如下：\n3. 从 Foundry 到应用代码的集成方式 在应用层面，Microsoft Foundry（Preview）部署的 GPT-Image-1.5 本质上仍然以标准推理 Endpoint 的形式对外提供服务：\n代码侧只需要关心：\nEndpoint URL Deployment Name 身份认证方式 不再直接依赖模型原始名称或版本号\n在企业实践中，常见模式是：\n开发 / 测试 / 生产环境使用不同 Deployment 模型版本升级（例如 GPT-Image-1.5 → 后续版本）在 Foundry 层完成 应用代码保持不变，仅消费稳定的 Endpoint 这种模式显著降低了 模型演进对业务系统的侵入性。\n在 Azure OpenAI 中如何使用 GPT-Image-1.5 你可以通过 Azure OpenAI 提供的 API、SDK 或 Azure AI Foundry 平台来调用 GPT-Image-1.5。基本流程如下：\n1. 获取访问权限与部署模型 在模型的详情页可以查看通过代码嗲用 GPT-Image-1.5 的所有信息：\n2. 使用 REST API 生成图像 使用 POST 请求调用图像生成端点：\nPOST https://\u0026lt;your_resource\u0026gt;.openai.azure.com/openai/deployments/\u0026lt;your_deployment\u0026gt;/images/generations?api-version=\u0026lt;api_version\u0026gt; Content-Type: application/json api-key: \u0026lt;your_api_key\u0026gt; { \u0026#34;prompt\u0026#34;: \u0026#34;一位穿宇航服的猫在火星表面散步\u0026#34;, \u0026#34;size\u0026#34;: \u0026#34;1024x1024\u0026#34;, \u0026#34;n\u0026#34;: 1, \u0026#34;quality\u0026#34;: \u0026#34;high\u0026#34; } 常见参数说明：\nprompt: 文本描述 size: 图像尺寸（如 1024x1024） n: 生成图像数量 quality: 生成质量（low/medium/high） 3. 图像编辑（若支持） 上传已有图片并通过 prompt 指令结合修改：\nPOST /images/edits 此类调用可以指定 mask、输入图片等字段来控制局部变化。\n4. SDK 与开发环境 Azure 支持多种语言 SDK（例如 Python、C#），可借助 Azure Identity 认证库、安全地调用 API 并处理生成结果。\n总结 GPT-Image-1.5 在图像生成领域代表了下一阶段 AI 创意工具的能力边界。它在指令理解、生成速度、图像质量、成本效率与多模态处理方面均显著提升，为开发者和企业提供了一套强大且可扩展的视觉 AI 解决方案。\n对于希望构建中长期 AI 能力平台的团队而言，Foundry + GPT-Image-1.5 是比直接裸用 API 更具工程化和可持续性的选择。\nAzure OpenAI 服务结合了模型能力与企业级安全合规，让 GPT-Image-1.5 不仅适用于实验探索，也能支撑生产级应用。\n如果你正在构建需要视觉理解或自动创意生成的应用，无论是批量生成还是交互式体验，GPT-Image-1.5 都值得深入实践。\n","permalink":"https://wilsonwu.me/zh/blog/2025/azure-openai-gpt-image/","summary":"\u003cp\u003e\u003cimg alt=\"GPT-Image-1.5\" loading=\"lazy\" src=\"/zh/blog/2025/azure-openai-gpt-image/0-gpt-image-1.5.png\"\u003e\u003c/p\u003e\n\u003cp\u003e近年来，生成式 AI 技术迅速演进。在自然语言处理之外，\u003cstrong\u003e图像生成与编辑能力成为 AI 创新的重要前沿\u003c/strong\u003e。在这一趋势下，OpenAI 推出了 GPT Image 系列模型，在 Azure OpenAI 服务内同样可用。而其中最新发布的 \u003cstrong\u003eGPT-Image-1.5\u003c/strong\u003e 可视为图像生成领域的新旗舰，在性能、效率和可控性方面均有显著提升。\u003c/p\u003e","title":"图像生成进入平台时代：GPT-Image-1.5 在 Microsoft Foundry 中的应用"},{"content":"在电商和物流领域，有一个不起眼但极其耗时的痛点：将复杂的地址格式正确的输入到物流系统固定的字段中。客户发来的地址格式千奇百怪，有时是逗号分隔，有时连成一行，顺序也完全随机，经常还缺少省份或州等关键信息。\n本文我想分享我是如何构建 Auto Address 的，这是一个利用 Azure OpenAI 的强大能力来解决这个问题的 Chrome 及 Edge 浏览器扩展插件，其界面和使用方式及其简单。\n问题：非结构化数据 想象一下收到这段文本：\n\u0026ldquo;张三 13800138000 广东省深圳市南山区科技园南区R2-B三楼\u0026rdquo;\n为了发货，你需要将其拆解为：\n姓名: 张三 电话: 13800138000 地址: 科技园南区R2-B三楼 城市: 深圳市 省份: 广东省 国家: 中国 手动处理成百上千个订单既繁琐又容易出错。正则表达式（Regex）可以处理某些情况，但当格式变化或用户有错别字时，它们往往无能为力。\n解决方案：LLM 作为解析器 像 Azure OpenAI（如：GPT-4 或 GPT-5）这样的大语言模型在理解上下文方面表现出色。它们能区分街道名称和城市名称，不是因为死板的规则，而是因为它们理解地址的语义。\n我选择 Azure OpenAI 进行此项目主要有两个原因：\n企业级隐私：发送到 Azure OpenAI 的数据不会用于训练公共模型。 可靠性：稳定的正常运行时间和性能。 技术揭秘：它是如何工作的 插件的核心逻辑出奇地简单。它获取用户的输入，并将其连同一组非常具体的指令发送到 Azure OpenAI API。\n1. System Prompt (系统提示词) “魔法”在于系统提示词。我们需要确切地告诉模型要做什么，更重要的是，如何格式化输出。\n这是代码中实际使用的提示词：\n{ role: \u0026#34;system\u0026#34;, content: \u0026#34;You are an address parser. Return ONLY a raw JSON object (no markdown code blocks) with keys: name, province, city, address, zip_code, country. If province/state is missing in the input, infer it from the city. Do not separate house number, include it in the address field.\u0026#34; } 这里使用的关键技巧：\n角色定义: \u0026ldquo;You are an address parser.\u0026rdquo; (你是一个地址解析器) 输出约束: \u0026ldquo;Return ONLY a raw JSON object\u0026rdquo;. (仅返回原始 JSON 对象)。这防止模型添加像 \u0026ldquo;这是您的 JSON：\u0026rdquo; 这样的废话。 Schema 定义: 我们明确列出了我们想要的键 (name, province 等)。 逻辑注入: \u0026ldquo;If province/state is missing\u0026hellip; infer it.\u0026rdquo; (如果省/州缺失\u0026hellip;推断它)。这将逻辑处理卸载给了 AI。 2. API 调用 扩展使用标准的 fetch API 与您的 Azure 部署进行通信。\nconst url = `${baseUrl}openai/deployments/${azureDeployment}/chat/completions?api-version=2024-05-01-preview`; const response = await fetch(url, { method: \u0026#39;POST\u0026#39;, headers: { \u0026#39;Content-Type\u0026#39;: \u0026#39;application/json\u0026#39;, \u0026#39;api-key\u0026#39;: azureApiKey }, body: JSON.stringify({ messages: [ /* ... prompt ... */ ], temperature: 0.1 // 以获得一致、确定的结果 }) }); 我们使用低 temperature (0.1)，因为我们不需要创造力；我们需要精确度。\n3. 智能省份推断（“双重检查”） 有时，即使是最好的模型也会遗漏细节，或者输入太模糊。一个常见的问题是缺少省/州信息。\n我在 popup.js 中实现了一个“回退”机制。如果解析结果有城市但没有省份，扩展程序会发起第二次定向 API 调用：\nif ((!addr.province || addr.province === \u0026#39;\u0026#39;) \u0026amp;\u0026amp; addr.city) { // ... 二次调用 ... messages: [ { role: \u0026#34;system\u0026#34;, content: \u0026#34;You are a geography helper. Return ONLY the province/state name for the given city. Do not return JSON, just the name.\u0026#34; }, { role: \u0026#34;user\u0026#34;, content: `Which province/state is the city \u0026#34;${addr.city}\u0026#34;${countryContext}?` } ] } 这确保了在用户看到数据之前，数据尽可能完整。\nChrome / Edge 扩展架构 Manifest V3 与 Side Panel (侧边栏) 该扩展基于 Manifest V3 构建，这是 Chrome / Edge 扩展平台的最新迭代。\n我没有使用点击后就会关闭的传统弹出窗口，而是使用了 Side Panel API (\u0026quot;permissions\u0026quot;: [\u0026quot;sidePanel\u0026quot;])。这允许扩展程序停留在浏览器窗口的一侧。这对工作流程至关重要：用户可以在左侧查看发货单页面，同时在右侧查看解析出的地址数据。\n隐私优先 首先使用前需要通过配置界面设置您要使用的 Azure OpenAI 信息：\n主要的设计决策是零中间服务器。\n扩展程序使用 chrome.storage.sync 将您的 API 密钥本地存储在浏览器中。 请求直接从您的浏览器 -\u0026gt; Azure OpenAI。 扩展开发者不收集任何数据。 结语 通过结合 Chrome / Edge 扩展的灵活性和 Azure OpenAI 的智能，我们可以将繁琐的数据录入任务转化为一键操作。\n代码已开源。欢迎查看、Fork 并构建您自己的 AI 驱动工具！\nGitHub Repo: https://github.com/wilsonwu/autoaddr\n如果您使用的是 Microsoft Edge 浏览器，可以通过扩展插件市场安装本插件的最新版本：Edge 浏览器扩展插件\n","permalink":"https://wilsonwu.me/zh/blog/2025/edge-extension-with-azure-ai/","summary":"\u003cp\u003e在电商和物流领域，有一个不起眼但极其耗时的痛点：\u003cstrong\u003e将复杂的地址格式正确的输入到物流系统固定的字段中\u003c/strong\u003e。客户发来的地址格式千奇百怪，有时是逗号分隔，有时连成一行，顺序也完全随机，经常还缺少省份或州等关键信息。\u003c/p\u003e","title":"基于 Azure OpenAI 构建智能地址解析 Chrome / Edge 浏览器扩展插件"},{"content":"过去五年，大语言模型（Large Language Models, LLMs）的发展几乎完全重塑了人工智能的技术版图。从 GPT 到 LLaMA，从 Transformer 到 Mixture-of-Experts（MoE），从单体模型到大规模分布式参数服务器体系，架构演进直接推动了能力跃迁。\n本文将从架构层面系统梳理 LLM 的主流技术路径，并从应用视角分析其优劣及适配场景，为研发与业务团队提供技术选型参考。同时也希望可以为初学者们打开迈入大模型世界的一条门缝！\n大语言模型的主流架构体系 目前的 LLM 架构，大概可以分为这么几类：\n传统 Transformer（Dense Transformer） Mixture-of-Experts（MoE）架构 多模态扩展架构（Vision-Language / Audio-Language） 检索增强生成（RAG）与混合推理架构 基于代理（Agentic）架构的系统级 LLM 超大规模分布式训练架构（例如 Parameter Server / Fully Sharded） 下面逐一展开。\n传统 Transformer：主流 LLM 的基础架构 Dense Transformer 是绝大多数 LLM 的基座，包括 GPT 系列、LLaMA、Mistral、Qwen 等。\n架构 简单来说，就是“大力出奇迹”：\n所有 token 通过全连接注意力进行计算； 全部参数在每次前向推理中都会被激活； 结构相对规则，训练稳定； 模型规模靠堆叠层数与扩大 hidden size 线性提升。 优势 推理路径稳定、可预测； 训练过程成熟，生态支持丰富； 对所有任务通用，不需要额外路由机制。 局限 参数规模大时推理成本高（全部参数激活）； 扩展模型能力的成本几乎与参数规模线性相关。 应用场景 场景 适用性 通用对话 高 编程、数学推理 高 对实时需求强的应用（低延迟） 较低 资源受限设备（边缘推理） 一般，可用量化缓解 Dense 模型依然是绝大多数企业首次采用 LLM 时的首选。\nMixture-of-Experts（MoE）：高效扩展能力的关键技术 MoE 是当前最重要的新架构之一，代表模型包括：\nGoogle Switch Transformer DeepSeek-V2/V3 Mixtral 系列 GPT-4 技术论文暗示其有 MoE 结构成分 架构 MoE 不再让所有参数参与每次计算，而是通过路由（Routing）机制让每个 token 只激活若干专家层（Expert）。\n举个例子，一个 300B 参数的庞然大物，实际每次推理可能只动用了 30B 的参数。\n优势 训练扩展性极强：参数规模可以以更低成本提升到数千亿甚至上万亿。 推理成本显著降低：激活专家减少推理 FLOPs。 擅长复杂推理与多任务能力。 局限 路由训练复杂，稳定性需求更高； 推理并行规模更大，对系统工程要求高； 不适合极端低延迟场景，因为专家调度带来额外开销。 应用场景 场景 适用性 多领域任务（代码/数学/语言混合） 极高 超大规模模型（百亿+） 极高 边缘设备、小型模型 低 多租户大规模在线推理 依实现而定 MoE 已成为大型商业 LLM 的主流趋势。\n多模态 LLM 架构：模型能力向感知延伸 代表模型：GPT-4o、Gemini、Qwen-VL、LLaVA、Yi-VL\n架构 多模态 LLM 通过在 Transformer 基座上引入额外编码器，将视觉、音频、视频等信号映射为 token 表示。\n典型结构通常包含：\n视觉编码器（Vision Encoder，比如 ViT） 投影层（Projection Layer） 统一的 Transformer 多模态注意力机制 优势 输入能力扩展显著：图像、音频、视频、文档统合处理； 极适合作为通用 AI Agent 的感知模块。 局限 成本比纯文本 LLM 更高； 多模态数据构建成本大； 输出的稳定性与高保真图像/视频理解仍有上限。 应用场景 场景 适用性 OCR / 文档理解 极高 多模态搜索 高 视频理解 中（依赖模型） 紧凑型设备部署 低 随着 GPT-4o 全模态输入的普及，多模态 LLM 已成为企业 AI 转型的必备能力层。\nRAG + LLM 混合架构：以知识增强为核心的系统形态 RAG（Retrieval-Augmented Generation）通过将外部知识库融合到推理中解决 LLM 本身无法持久更新和存储大量事实的问题。\n架构 由三个核心组件构成：\n向量数据库（Retriever） 文档预处理管线（Chunking, Indexing） 核心 LLM（Generator） 优势 模型无需重新训练即可持续更新知识； 显著增强长尾知识与事实性能力； 数据可控，便于企业级治理。 局限 依赖检索质量； 对 prompt 工程与 pipeline 工程化要求高； 多跳检索在 latency 上存在开销。 应用场景 场景 适用性 企业知识库问答 极高 法律、医疗等高准确性应用 高 实时 LLM 应用（对时延敏感） 较低 RAG 是企业级 LLM 落地中最常见的架构。\n基于代理的架构：基于智能体的系统级 LLM 架构 代表：AutoGPT、GPT-o1、GPT-o3、DeepSeek-R1、自主推理 Agent Framework\n随着模型具备规划（Planning）、执行（Acting）和反思（Reflection）能力，Agent 架构正在成为应用层主流。\n架构 多工具整合（Tool Use） 工作流管理 多轮子任务规划 Memory 与外部状态管理 优势 可执行复杂任务链； 强化模型可控性； 工具能力扩展模型本身的局限。 局限 系统复杂度显著增加； 可靠性、可追踪性和可验证性要求高； 调度能力直接影响实际效果。 应用场景 场景 适用性 自动化运营 极高 数据分析助手 高 多步骤计算任务 高 回答型问答 一般 超低延迟场景 不适合 Agent 架构代表了 LLM 应用的未来方向。\n分布式训练架构：支撑万亿参数模型的底层工程 这类方案通常没有特定的架构模型，依照不同的需求，技术厂商会演进出各类方案，比如：\nZeRO / FSDP（Fully Sharded Data Parallel） Megatron-LM（Tensor / Pipeline 并行） DeepSpeed Colossal-AI Parameter Server（SageMaker、Ray、DeepSeek 自研） 优势 高效训练超大模型； 利用 GPU 集群最大化吞吐； 优化显存占用。 局限 工程复杂度极高； 部署成本昂贵； 对 GPU 集群架构高度依赖。 应用场景 主要适用于自研大模型或千卡级 GPU 训练任务的企业或科研团队。\n各架构侧重点及优势对比 以下为一张综合对比表：\n架构类型 优势 劣势 应用重点 Dense Transformer 稳定、成熟、推理链简单 成本随规模线性提升 通用模型、边缘部署 MoE 高效扩展、高性价比 路由复杂、工程门槛高 大规模模型、混合任务 多模态 LLM 输入能力强 成本高、训练复杂 文档/图像/视频/音频场景 RAG 强事实性、可更新 检索依赖高 企业知识库、法律/医疗 Agent 工具能力强、任务链执行 系统复杂 自动化任务、数据分析 分布式架构 支撑万亿模型 成本与复杂度高 训练基建 如何选择适合的 LLM 架构？ 基于上述介绍的 LLM 架构，我们初步了解了大语言模型本身的架构以及叠加一些应用场景的方案架构，而在 LLM 结合各个行业的真实应用场景中，架构远远不止这些，结合已有的计算机系统架构，LLM 的架构也在不断进化和丰富中，下面简单根据应用需求，推荐如下路径：\n目标是开发一个企业级问答系统 Dense Transformer + 强化 RAG：可靠、易控、低成本、可持续更新。\n目标是打造一个全能基础大模型系统 MoE 是优选：性能与性价比兼得。\n目标是构建文档/图像/视频 AI 系统 多模态 LLM：例如 GPT-4o、Qwen-VL、Gemini。\n需要自动化任务执行 基于代理的架构：模型本身不需要特别大，但需要良好的工具接口和规划能力。\n需要训练和运行自研模型 FSDP / Megatron / PS-based 分布式架构，这类需求其实可选范围非常广，由于是训推整体方案，只要沿用市面成型的一体方案即可，不需要花大量时间在训练和推理框架之间做兼容。\n总结 大语言模型架构的演进已经进入百花齐放阶段。Dense 模型提供稳定基座，MoE 架构带来成本与能力的突破，多模态扩展了 AI 的感知边界，而 RAG 与 Agent 架构将 LLM 推向真正可控、可执行的智能体时代。\n对企业而言，选对架构比盲目追求“更大模型”更重要。依据实际需求、工程能力和预算进行架构选择，既能优化效果，也能确保长期可维护性。\n","permalink":"https://wilsonwu.me/zh/blog/2025/llm-architecture/","summary":"\u003cp\u003e过去五年，大语言模型（Large Language Models, LLMs）的发展几乎完全重塑了人工智能的技术版图。从 GPT 到 LLaMA，从 Transformer 到 Mixture-of-Experts（MoE），从单体模型到大规模分布式参数服务器体系，架构演进直接推动了能力跃迁。\u003c/p\u003e","title":"玩转大语言模型：为初学者扫清大语言模型（LLM）架构盲区"},{"content":"Kubernetes 官方在 2025 年 11 月 11 日发布博客，正式宣布 Ingress NGINX 项目进入退役（Retirement）阶段，并将于 2026 年 3 月彻底停止维护。\n这一举措标志着 Kubernetes 在集群入口与流量管理方面正式进入 Gateway API 时代。对于正在使用 Ingress NGINX 的团队，这不仅是一次技术升级，更是一项需要尽快规划的风险管理工作。\n本文将基于 Kubernetes 官方退役公告、Gateway API 最新生态与社区最佳实践，系统介绍：\n为什么 Ingress NGINX 被退休？ Ingress 模型的根本局限与 Gateway API 的优势 新的推荐替代方案：Envoy Gateway（基于 Gateway API） 如何从 Ingress 迁移到 Gateway API（完整迁移路径） 关键注意事项与生产实践建议 Ingress NGINX 为什么从 Kubernetes 正式退役？ 根据官方博客（2025–11–11），Ingress NGINX 的退役原因包括：\n● 1. 项目长期缺乏维护者 虽然 Ingress NGINX 使用极广，但维护压力巨大，而核心维护者数量越来越少，社区贡献下滑。项目经过数月讨论，确定无法持续投入。\n● 2. 无法跟上 Kubernetes 网络模型的发展 Ingress 诞生于 Kubernetes 早期，仅支持 HTTP(s) 基础路由；而随着 Service Mesh、多协议服务、网关统一治理等场景爆发，Ingress 模型难以满足现代需求。\n● 3. 功能严重依赖 Controller 私有扩展 如 NGINX Ingress 的大量 Annotation、Rewrite、Header 管理等，使得 Ingress 配置在不同控制器之间高度不兼容，难以形成云原生网络的统一抽象。\n● 4. Gateway API 已经成熟，能够全面替代 Gateway API 于 2023–2024 进入 GA，解决了 Ingress 的所有痛点，是 Kubernetes SIG-Network 未来 10 年的官方战略方向。\n官方公告明确：\n从 2026 年 3 月起将不再提供任何补丁，包括安全补丁。 项目代码、容器镜像、Helm Chart 虽然仍可访问，但不会再更新。 官方建议用户“尽快迁移”。 Ingress 的局限性：为什么它必须退出历史舞台？ Ingress 的设计非常简单，只能做最基础的 L7 HTTP(s) 路由：\n能力 Ingress Gateway API Host / Path 路由 ✔ ✔ Header 匹配 ❌ ✔ Query 匹配 ❌ ✔ HTTP Method 匹配 ❌ ✔ 流量拆分 / 灰度发布 ❌ ✔ 多监听端口 ❌ ✔ TCP / UDP / gRPC 多协议 ❌ ✔ 多租户 / RBAC 边界 弱 强 可扩展性 Annotation 混乱且不兼容 CRD 标准扩展 Ingress 的能力已无法支撑现代云原生系统的需求，而且控制器之间的行为极不一致。Gateway API 的出现正是为了解决上述所有问题。\n推荐替代方案：Envoy Gateway + Gateway API 目前官方推荐的方向是：转向 Gateway API，并选择任意兼容的 Gateway 实现。\n在众多实现中，我们推荐 Envoy Gateway，原因包括：\n1. 对 Gateway API 支持最完整 完美支持 HTTPRoute / TCPRoute / GRPCRoute 这是 Ingress 无法做到的。\n2. 基于 Envoy Proxy，性能与可扩展性行业领先 Envoy 是 CNCF 毕业项目，得到大规模生产验证。\n3. 社区活跃，长期维护无风险 与 Ingress NGINX 的“维护者不足”相比，Envoy Gateway 是明确的长期支持项目。\n4. 与 Service Mesh（如 Istio）原生兼容 Gateway API 未来将成为 Service Mesh 入口的新标准。\n换句话说：Envoy Gateway 是最稳妥的迁移目标。\n迁移路径：从 Ingress NGINX → Gateway API（Envoy Gateway） 大多数企业推荐采用 双栈迁移：\nIngress（旧） 和 Gateway API（新） 并存 → 服务逐个迁移 → 完全切流 → 卸载 Ingress NGINX 以下是完整步骤。\n步骤 1：安装 Gateway API 和 Envoy Gateway 安装标准 Gateway API CRD：\nkubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/latest/download/standard-install.yaml 然后安装 Envoy Gateway 控制器（官方提供 Helm / YAML）。\n步骤 2：创建 Gateway（替代 Ingress Controller + LoadBalancer） apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: main-gateway namespace: infra spec: gatewayClassName: envoy listeners: - name: http port: 80 protocol: HTTP - name: https port: 443 protocol: HTTPS tls: mode: Terminate certificateRefs: - name: demo-cert 这相当于原来的：\nIngress Controller LoadBalancer Service TLS 配置 步骤 3：把 Ingress 转换为 Gateway API 路由 原 Ingress：\napiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: demo spec: rules: - host: demo.example.com http: paths: - path: / pathType: Prefix backend: service: name: demo-svc port: number: 80 新 HTTPRoute：\napiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: demo spec: parentRefs: - name: main-gateway namespace: infra hostnames: - \u0026#34;demo.example.com\u0026#34; rules: - matches: - path: type: PathPrefix value: \u0026#34;/\u0026#34; backendRefs: - name: demo-svc port: 80 步骤 4：灰度切流（可选但强烈推荐） Gateway API 支持权重：\nbackendRefs: - name: demo-svc-v1 port: 80 weight: 90 - name: demo-svc-v2 port: 80 weight: 10 用于：\n灰度发布 蓝绿切换 测试新网关 逐步迁移流量 这是 Ingress 以前必须靠 Nginx snippet 才能做到的。\n步骤 5：服务逐个迁移 → 最终替换所有 Ingress 建议迁移顺序：\n无状态服务 内部服务（非公网） 低风险流量 高风险 / 主业务 全局入口流量 完成后卸载 Ingress NGINX 迁移注意事项（生产环境必读） 1. Annotation 不会自动迁移 Ingress 的 Annotation（rewrite、headers、timeouts 等）要重写为 Gateway API 的 Policy / Filter。\n2. TLS 配置方式不同 Gateway API 是 Listener 层统一处理 TLS。\n3. 流量切换建议逐步进行 不要一次性替换大量入口。\n4. 建议提前半年开始迁移 要赶在 2026 年 3 月停止维护前完成迁移。\n总结：Gateway API 是 Kubernetes 入口的未来 Ingress NGINX 的退役并不是坏事，而是 Kubernetes 网络体系完成演进的标志。\nIngress → Gateway API 是必然趋势，也是一次全面升级：\n更强的路由能力 更清晰的多租户设计 更丰富的现代云原生场景支持 更长期的社区维护保障 对于已经依赖 Ingress NGINX 的团队，现在就应该开始迁移规划。\nGateway API + Envoy Gateway 是当前生态最稳、最现代、最推荐的方案。\n","permalink":"https://wilsonwu.me/zh/blog/2025/ingress-to-gateway-api/","summary":"\u003cp\u003eKubernetes 官方在 \u003cstrong\u003e2025 年 11 月 11 日\u003c/strong\u003e发布博客，正式宣布 \u003cstrong\u003eIngress NGINX 项目进入退役（Retirement）阶段\u003c/strong\u003e，并将于 \u003cstrong\u003e2026 年 3 月\u003c/strong\u003e彻底停止维护。\u003c/p\u003e\n\u003cp\u003e这一举措标志着 Kubernetes 在集群入口与流量管理方面正式进入 \u003cstrong\u003eGateway API 时代\u003c/strong\u003e。对于正在使用 Ingress NGINX 的团队，这不仅是一次技术升级，更是一项需要尽快规划的风险管理工作。\u003c/p\u003e","title":"Kubernetes Ingress NGINX 退役：全面迁移到 Gateway API 的方案与实践指南"},{"content":"\n微软研究院最近发布了 Fara-7B，这是一款专为电脑操作（Computer Use）设计的代理小语言模型（Agentic SLM）。与传统的聊天模型不同，Fara-7B 旨在通过像人类一样使用鼠标和键盘来完成任务。\n什么是 Fara-7B？ Fara-7B 是一个拥有 70 亿参数的模型，基于 Qwen2.5-VL-7B 构建。它的主要特点包括：\n视觉感知：它通过视觉感知网页（截图）来操作，不需要依赖辅助功能树（Accessibility Trees）或额外的解析模型。 高效紧凑：尽管只有 7B 参数，但在同类模型中实现了最先进的性能，甚至在某些基准测试中可以与更大的模型竞争。 端侧运行：由于体积小，它可以在设备上本地运行，从而降低延迟并提高隐私安全性。 核心技术 Fara-7B 的训练利用了微软开发的合成数据生成管道，该管道基于 Magentic-One 框架。它通过“观察-思考-行动”（observe-think-act）的循环来执行任务。\n在每个步骤中，模型会接收：\n用户指令 完整的操作历史 最近的三张屏幕截图 然后，它会输出一个推理信息（“思考”下一步行动），随后调用工具（如 click(x,y)、type() 等）来执行操作。\n性能表现 在 WebVoyager、Online-Mind2Web 等基准测试中，Fara-7B 展现了强大的性能。例如在 WebVoyager 上，它的任务成功率达到了 73.5%，超过了 UI-TARS-1.5-7B (66.4%) 和 GPT-4o (SoM Agent, 65.1%)。\n如何通过 vLLM 使用 Fara-7B Fara-7B 已经发布在 Hugging Face 上。由于它是基于 Qwen2.5-VL 的，我们可以使用 vLLM 来高效地部署和推理。\n安装 vLLM 首先确保你安装了最新版本的 vllm：\npip install vllm Python 代码示例 以下是一个使用 vLLM 加载 Fara-7B 并进行推理的 Python 脚本示例：\nfrom vllm import LLM, SamplingParams from PIL import Image import requests from io import BytesIO def run_fara_7b(): # 模型名称 model_name = \u0026#34;microsoft/Fara-7B\u0026#34; # 初始化 LLM # Fara-7B 基于 Qwen2.5-VL，因此使用类似的配置 llm = LLM( model=model_name, trust_remote_code=True, max_model_len=4096, # 根据显存大小调整 limit_mm_per_prompt={\u0026#34;image\u0026#34;: 1}, mm_processor_kwargs={ \u0026#34;min_pixels\u0026#34;: 28 * 28, \u0026#34;max_pixels\u0026#34;: 1280 * 28 * 28, }, ) # 加载示例图片（这里仅作演示，实际使用中应为屏幕截图） image_url = \u0026#34;https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg\u0026#34; response = requests.get(image_url) image = Image.open(BytesIO(response.content)) # 构建 Prompt # 注意：Fara-7B 使用特定的 Prompt 格式 prompt = ( \u0026#34;\u0026lt;|im_start|\u0026gt;system\\nYou are a helpful assistant.\u0026lt;|im_end|\u0026gt;\\n\u0026#34; \u0026#34;\u0026lt;|im_start|\u0026gt;user\\n\u0026lt;|vision_start|\u0026gt;\u0026lt;|image_pad|\u0026gt;\u0026lt;|vision_end|\u0026gt;\u0026#34; \u0026#34;Describe this image in detail.\u0026lt;|im_end|\u0026gt;\\n\u0026#34; \u0026#34;\u0026lt;|im_start|\u0026gt;assistant\\n\u0026#34; ) # 准备输入 inputs = { \u0026#34;prompt\u0026#34;: prompt, \u0026#34;multi_modal_data\u0026#34;: { \u0026#34;image\u0026#34;: image }, } # 采样参数 sampling_params = SamplingParams(temperature=0.2, max_tokens=512) # 生成结果 outputs = llm.generate([inputs], sampling_params=sampling_params) for o in outputs: print(f\u0026#34;Generated text: {o.outputs[0].text}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: run_fara_7b() 命令行启动 API 服务 如果你想启动一个兼容 OpenAI API 的服务，可以使用以下命令：\nvllm serve microsoft/Fara-7B \\ --trust-remote-code \\ --limit-mm-per-prompt image=1 \\ --mm-processor-kwargs \u0026#39;{\u0026#34;min_pixels\u0026#34;: 784, \u0026#34;max_pixels\u0026#34;: 1003520}\u0026#39; 注意：min_pixels 对应 28x28，max_pixels 对应 1280x28x28。\n总结 Fara-7B 是微软在代理小模型领域的一次重要尝试，它证明了小模型在复杂的电脑操作任务中也能有出色的表现。通过开源权重，开发者可以更轻松地探索和构建基于 GUI 的自动化代理。\n","permalink":"https://wilsonwu.me/zh/blog/2025/microsoft-fara-7b/","summary":"\u003cp\u003e\u003cimg alt=\"Fara-7B\" loading=\"lazy\" src=\"/zh/blog/2025/microsoft-fara-7b/1-fara-7b.png\"\u003e\u003c/p\u003e\n\u003cp\u003e微软研究院最近发布了 \u003cstrong\u003eFara-7B\u003c/strong\u003e，这是一款专为电脑操作（Computer Use）设计的代理小语言模型（Agentic SLM）。与传统的聊天模型不同，Fara-7B 旨在通过像人类一样使用鼠标和键盘来完成任务。\u003c/p\u003e","title":"Fara-7B：微软推出的高效电脑操作代理模型"},{"content":"随着 AI 辅助编程工具的不断进化，GitHub Copilot 也在持续引入更多强大的模型供开发者选择。最近，GitHub Copilot 宣布支持 Google 最新的 Gemini 3 Pro 模型（预览版）。作为一个每天都在使用 Copilot 的开发者，我第一时间进行了体验，发现它在逻辑推理和长上下文理解方面有着令人惊喜的表现。\n在这篇文章中，我将手把手教你如何在 VS Code 中切换到 Gemini 3 Pro，并分享一些使用心得。\n为什么要关注 Gemini 3 Pro？ Gemini 3 Pro 是 Google 推出的最新一代多模态大模型。在代码生成和理解领域，它展现出了极强的竞争力：\n更强的推理能力：面对复杂的算法问题或架构设计，Gemini 3 Pro 往往能给出更深入的分析。 超长上下文窗口：它能够理解更多的项目代码上下文，这对于大型项目的重构和 Bug 修复至关重要。 响应速度：尽管模型参数巨大，但在 Copilot 中的响应速度依然非常流畅。 如何在 GitHub Copilot 中启用 Gemini 3 Pro 启用过程非常简单，只需要确保你的 VS Code 和 Copilot 插件是最新版本。\n步骤 1：更新环境 确保你的 Visual Studio Code 和 GitHub Copilot Chat 扩展都已经更新到最新版本。通常 VS Code 会自动更新，但你也可以手动检查一下。\n步骤 2：打开 Copilot Chat 在 VS Code 侧边栏点击 GitHub Copilot 图标，打开聊天窗口。\n步骤 3：切换模型 在聊天输入框的下方，你会看到当前使用的模型名称（通常默认是 Auto 或 GPT-4o）。\n点击模型名称，在弹出的下拉菜单中，选择 Gemini 3 Pro (Preview)。\n一旦选中，你就可以开始使用 Gemini 3 Pro 进行对话了！\n看看他的效果吧！\n实战体验：Gemini 3 Pro 强在哪里？ 1. 复杂逻辑的代码解释 我尝试让它解释一段复杂的异步并发控制代码。Gemini 3 Pro 不仅解释了代码的表面含义，还指出了其中潜在的死锁风险，并给出了优化建议。这种“多想一步”的能力在 Code Review 时非常有帮助。\n2. 跨文件上下文理解 在处理一个涉及多个模块的重构任务时，我将相关的几个文件都添加到了上下文中（使用 @workspace 或手动引用）。Gemini 3 Pro 能够很好地串联起这些文件之间的依赖关系，生成的修改建议很少出现“幻觉”或引用不存在的变量。\n总结 GitHub Copilot 引入 Gemini 3 Pro 为开发者提供了更多的选择。不同的模型有不同的“性格”和优势领域。我建议大家在处理不同类型的任务时可以尝试切换模型：\n日常简单代码补全：默认模型通常够快够用。 复杂架构设计或疑难 Bug 排查：试试 Gemini 3 Pro，它可能会给你带来新的思路。 快去试试吧，感受一下 Google 最新 AI 模型的编程实力！\n","permalink":"https://wilsonwu.me/zh/blog/2025/use-gemini-3-pro-in-github-copilot/","summary":"\u003cp\u003e随着 AI 辅助编程工具的不断进化，GitHub Copilot 也在持续引入更多强大的模型供开发者选择。最近，GitHub Copilot 宣布支持 Google 最新的 \u003cstrong\u003eGemini 3 Pro\u003c/strong\u003e 模型（预览版）。作为一个每天都在使用 Copilot 的开发者，我第一时间进行了体验，发现它在逻辑推理和长上下文理解方面有着令人惊喜的表现。\u003c/p\u003e","title":"快速为 Github Copilot 配置最新的 Gemini 3 Pro 模型加速开发体验"},{"content":"随着大语言模型（LLM）规模不断增长，推理成本也随之飙升。为了让模型在响应用户请求时更快、更经济地运行，各类优化技术不断涌现。其中，KV-Cache（Key-Value Cache）是目前最关键、影响最深远的推理加速机制之一，被所有主流的推理框架（如 vLLM、TensorRT-LLM、LLama.cpp、llm-d、OpenAI Triton Transformer Engine 等）广泛使用。\n这篇文章将全面介绍什么是 KV-Cache、它如何工作、为什么它能极大提升推理效率、它对行业带来了什么影响，以及在实际使用中的最佳实践。\nKV-Cache 是什么？ 首先，其工作原理架构图如下：\n理解起来非常简单，单其中细节结合复杂度计算还是略有抽象。\nKV-Cache，全称 Key-Value Cache，是在 LLM 推理过程中对 Transformer 解码器中自注意力层（Self-Attention）的中间结果进行缓存的一种方法。\nTransformer 的自回归生成方式决定了： 每生成一个新 token，就需要重新计算它与全部历史 token 的注意力关系。\n如果每次都把过去所有 token 的 Key 和 Value 重新计算一遍，其计算量是：\n推理时间复杂度：O(n²) （n 为上下文长度）\n为了避免重复计算，KV-Cache 会在每步生成 token 时，将计算后的 Key（K） 和 Value（V） 保存下来，这样之后再生成下一步 token 时就能直接引用过去的 Key/Value，而无需重新计算历史部分。\n这让时间复杂度大幅下降为：\n推理时间复杂度：O(n)\n所以简单总结一下，KV-Cache 是在自回归解码中缓存历史 token 的 Key/Value，让后续生成直接复用过去的注意力结果，从而把时间复杂度从 O(n²) 降到 O(n)，节省的成本非常巨大。\nKV-Cache 是如何工作的？ 以当前主流的解码流程为例，我们以是否使用 KV-Cache 来做一个简单的对比。\n如果没有 KV-Cache 每次生成新 token 时需要做：\n重新对全部历史序列做 embedding 通过所有 Transformer 层重新计算 K/V 根据新 token 与整个序列做自注意力 得到新 token 并输出 具体流程图示如下：\n使用 KV-Cache 第一次推理（prefill 阶段）：\n对所有提示词（prompt tokens）进行计算 计算所有 K/V，并将它们保存在 KV 缓存中 之后的每一步（decode 阶段）：\n只计算当前新 token 的 K/V 与缓存中的 K/V 做注意力，得到结果 写回缓存 这样，每生成一个 token 的计算量从全序列规模 → 近似常数开销，越长的上下文收益越大。\n其流程图示如下：\nKV-Cache 带来了什么影响？ 推理速度提升数十倍 对长上下文模型（8k / 32k / 128k tokens）尤其明显。\n例如对 32k token 的上下文，如果没有 KV-Cache：\n每生成一个 token 需要遍历 32k 历史序列 整体是 重复 32k 次的巨量工作 有了 KV-Cache：\n历史部分只算一次 单步 decode 的开销变成固定量（几百维 KV 与注意力点积） 通常加速比可达到：\n10 倍至 100 倍不等 （取决于提示词长度、模型大小、硬件）\n推理成本大幅降低 大模型推理的成本主要由这几个部分构成：\n算力（矩阵乘法） 显存占用 带宽 KV-Cache 能大幅降低重复算力，因此成本成比例下降。\n这就是为什么：\nOpenAI、Anthropic、Meta、Mistral 各大云推理服务 所有开源推理框架 都把 KV-Cache 作为默认推理方式。\n但是显存占用会变高 KV 是每一层每个 token 的结果，因此缓存本身需要巨大的显存。\n例如： Llama-3 70B 模型，64K context，对 1 token 的 KV 占用约 1.5KB。\n那么 64K token 就是：约 96MB × 80 层 ≈ 7.6GB 的 KV-cache\n当你看到推理卡 80GB 显存不够时，很大概率是因为 KV 满了，而不是模型权重太大。\n影响了模型的架构设计与行业走向 KV-Cache 的重要性高到：模型架构都围绕它做优化。\n包括：\nMistral 的滑动窗口注意力（SWA） Meta 的 Grouped Query Attention（GQA） DeepSeek 的 Multi-head Latent Attention（MLA） Google 的 Multi-Query Attention（MQA） 这些技术主要目的之一就是：\n降低 KV-Cache 的显存占用\n因为 Q 数少 → KV 也变少 → 显存压力更低。\n所以 KV-Cache 不只是一个技术点，而是影响模型架构的重要因素。\nKV-Cache 的性能关键因素 不同 Attention 架构带来的 KV 大小差异 Attention 类型 KV 大小 优点 缺点 MHA（多头注意力） 最大 兼容性最好 显存占用高 MQA（多查询注意力） 小很多 更高效 灵活性略差 GQA（分组注意力） 居中 性能好 结构更复杂 MLA 更小 更快 模型支持有限 Prefill 和 Decode 性能壁垒不同 Prefill 阶段：算 K/V 很耗算力，需要好 GPU。 Decode 阶段：显存带宽优先，因为每步生成通常 batch 很小。 这也影响推理框架的优化策略。\nKV-Cache 的布局与管理 例如：\nPagedAttention（vLLM 的核心技术） Block-sparse KV 管理（TensorRT-LLM） 动态缓存分配（llama.cpp） 连续 vs 分块 KV 内存 优秀的分配策略可以极大减少 fragmentation 和显存浪费。\nKV-Cache 的最佳实践 尽量减少上下文长度 上下文越长 → KV 越大 → 推理越慢、越贵。\n如果业务允许：\n使用 RAG 把长文档拆分 使用压缩模型（compressor） 使用短指令模板 引导模型减少“废话”输出 选择合适的 Attention 架构 如果你在自定义模型、微调或部署模型：\n需要高效推理 → 优选 MQA / GQA / MLA 需要兼容许多任务 → MHA 仍然最稳定 使用专业推理框架 优先使用：\nvLLM（商业与开源界第一推理框架） TensorRT-LLM（NVIDIA 官方） llm-d（中小型企业的极高性价比框架） OpenAI Triton 引擎（内部使用） 它们都在 KV-Cache 管理上有深度优化。\n多卡推理时注意 KV 分布问题 KV 会引发：\n多卡同步开销 all-reduce 带宽瓶颈 部分框架提供：\nKV sharding pipeline parallelism block-sparse KV sharing 合理设置可显著提升吞吐量。\n批处理策略影响巨大 适配 KV-Cache 的批处理策略包括：\ncontinuous batching block scheduling prompt+decode 混合 batch 正确 batching 通常带来 \u0026gt;10× 吞吐提升。\nKV-Cache 的未来展望 未来几年，模型推理的发展趋势中，KV-Cache 会继续是核心机制。\n我们将看到：\n更轻量的 KV 结构（更少头、更小维度） 更智能的 KV 重用机制 KV 的跨请求共享（multi-query serving） 部分 KV 的 CPU/NVMe offload Push-down Attention（降低 GPU 带宽要求） 总之，KV-Cache 将继续驱动推理性能提升，是 LLM 推理领域最重要的基础设施之一。\n总结 KV-Cache 并不是一个“可选优化”，而是现代大语言模型推理的 核心基石。 它带来巨大的速度提升，但也带来显存压力，影响模型架构、推理框架设计乃至行业整个技术路线。\n理解 KV-Cache，你就理解了：\n为什么推理这么贵 为什么上下文越长模型越慢 为什么新一代模型都在改注意力结构 为什么推理框架竞争如此激烈 ","permalink":"https://wilsonwu.me/zh/blog/2025/kv-cache-in-llm/","summary":"\u003cp\u003e随着大语言模型（LLM）规模不断增长，推理成本也随之飙升。为了让模型在响应用户请求时更快、更经济地运行，各类优化技术不断涌现。其中，\u003cstrong\u003eKV-Cache（Key-Value Cache）\u003c/strong\u003e是目前最关键、影响最深远的推理加速机制之一，被所有主流的推理框架（如 vLLM、TensorRT-LLM、LLama.cpp、llm-d、OpenAI Triton Transformer Engine 等）广泛使用。\u003c/p\u003e","title":"玩转大语言模型：深入理解 KV-Cache - 大模型推理的核心加速技术"},{"content":"随着 Azure AI Foundry 开放对 Sora 2（OpenAI 生成式视频模型）的支持，开发者现在可以在企业级合规、可管控的环境中使用顶尖的视频生成能力。本教程将带你从零开始，通过 Playground 和 Python SDK 两种方式调用 Sora 2，完成「文本生成视频」的流程。\n准备工作 在开始之前，你需要：\n获取 Azure 订阅 拥有一个 Azure 订阅，如果您不清楚如何获取 Azure 订阅，可以参考之前文章中的注册 Azure 订阅内容进行操作。\n创建 Azure AI Foundry 首先进入您的 Azure 订阅中的 AI Foundry，展开左侧 All Resources，找到 Azure AI Foundry，点击 Create 创建一个 Azure AI Foundry：\n创建时注意区域选择，由于 Sora 2 模型并未在所有 Azure 区域开放预览，这里建议选择 East US 2 区域：\n1. 创建 Azure AI Foundry Project 创建完成后进入您的 Azure AI Foundry 在 All Resources 中找到 Projects，点击 New 创建一个新的 Project：\n2. 打开 Azure AI Foundry Protal 创建完成 Project 后，打开您的 Project，点击 Go to Azure AI Foundry protal：\n正常情况下 Azure AI Foundry Protal 如下：\n3. 启用和部署 Sora 2 模型 又有 Sora 2 模型在本文发布时仍然处于预览状态，所以我们首先需要申请权限，后续开放使用后可调过本步骤，首先点击左侧的 Model catalog，可以看到 Sora 2 模型的标签，点击 Check out model：\n也可以在下方的模型列表找到想要的模型进行使用。\n如果您没有使用 Sora 2 模型的权限，进入 Sora 2 模型介绍界面后将如下所示：\n请点击 Request access 链接进行权限申请。\n如您已经拥有 Sora 2 访问权限，可以点击 Use this model 进行模型部署：\n部署过程非常简单，默认保持各类选项即可进行创建部署：\n部署完成后，切换左侧菜单至 Models + endpoints，查看模型列表：\n至此您就拥有了属于自己的 Sora 2 模型，可以开始使用！\n通过 Azure AI Foundry Playground 使用 Sora 2 Azure AI Foundry 提供了强大的可视化界面，你可以无需写代码就生成视频。\n1. 进入 Playground 打开 Azure AI Foundry 左侧菜单中的 Playgrounds，选择 Video playground：\n进入 Playground 后选择 Sora 2 模型：\n2. 填写提示词 Prompt 示例 Prompt：\nA high-energy cyberpunk chase in the rain with neon reflections on wet streets. A futuristic motorcycle speeds through, dynamic motion blur capturing its sleek design and vibrant city lights. 3. 设置选项 可调节选项包括：\n选项 说明 Attach an image 可以附加图片进行相视频的生成 Aspect ratio 视频长宽比，默认为 16:9 Resolution 分辨率，当前只提供 720p Duration 输出视频时长（如 5s、10s），默认 8s 4. 生成视频 设置好后，点击 Generate 进行视频生成，视频生成成功后，你可以在 Generation history 中预览或下载视频：\n通过 Python 调用 Sora 2 Azure 为 OpenAI 模型提供统一的接口，可以直接在你的后端调用。\n1. 安装 SDK pip install azure-ai-openai pip install python-dotenv 2. 配置环境变量 创建 .env 文件：\nAZURE_OPENAI_ENDPOINT=https://\u0026lt;your-resource-name\u0026gt;.openai.azure.com/ AZURE_OPENAI_KEY=\u0026lt;your-key\u0026gt; AZURE_OPENAI_API_VERSION=2024-10-01-preview 3. Python 调用示例：文生视频 下面是最简可运行示例：\nimport os from dotenv import load_dotenv from azure.ai.openai import OpenAIClient from azure.core.credentials import AzureKeyCredential load_dotenv() client = OpenAIClient( endpoint=os.getenv(\u0026#34;AZURE_OPENAI_ENDPOINT\u0026#34;), credential=AzureKeyCredential(os.getenv(\u0026#34;AZURE_OPENAI_KEY\u0026#34;)), api_version=os.getenv(\u0026#34;AZURE_OPENAI_API_VERSION\u0026#34;) ) prompt = \u0026#34;\u0026#34;\u0026#34; A cinematic shot of a spaceship flying through a nebula filled with colorful cosmic clouds. \u0026#34;\u0026#34;\u0026#34; response = client.get_video( model=\u0026#34;sora-2\u0026#34;, prompt=prompt, duration=5, # seconds resolution=\u0026#34;1080p\u0026#34;, ) # 保存视频 video_bytes = response.video with open(\u0026#34;output.mp4\u0026#34;, \u0026#34;wb\u0026#34;) as f: f.write(video_bytes) print(\u0026#34;Video generated: output.mp4\u0026#34;) 4. 使用结构化 payload Sora 还支持更多控制，例如镜头、分镜头脚本、环境细节。\npayload = { \u0026#34;prompt\u0026#34;: \u0026#34;A cute corgi running on a beach during sunset.\u0026#34;, \u0026#34;duration\u0026#34;: 8, \u0026#34;resolution\u0026#34;: \u0026#34;720p\u0026#34;, \u0026#34;motion\u0026#34;: \u0026#34;medium\u0026#34;, \u0026#34;style\u0026#34;: \u0026#34;realistic\u0026#34; } response = client.get_video( model=\u0026#34;sora-2\u0026#34;, **payload ) with open(\u0026#34;corgi_beach.mp4\u0026#34;, \u0026#34;wb\u0026#34;) as f: f.write(response.video) 生成最佳视频的技巧 1. 提示词结构推荐（General Prompt Template） [主体对象] + [场景环境] + [动作] + [镜头效果] + [光影] + [风格] 示例：\nA high-energy cyberpunk chase in the rain with neon reflections on wet streets. A futuristic motorcycle speeds through, dynamic motion blur capturing its sleek design and vibrant city lights. 2. 控制一致性 使用 seed 可重复生成风格一致的视频 合理设置 motion level 获得更自然的动作 提供清晰场景描述可减少模型误解 常见问题（FAQ） 1. 我试了但看不到 Sora 2？ 目前需要访问权限。你需要：\n联系 Azure 管理员 到模型页面确认已启用 2. 视频最大时长是多少？ 具体限制依区域会有不同，通常为 ≤ 10 秒。\n3. Python 生成的视频过小或无内容？ 确保保存时使用 response.video 而不是错误字段。\n总结 本文介绍了在 Azure AI Foundry 中使用 Sora 2 文生视频模型的两种方式：\n使用可视化 Playground 快速体验 使用 Python SDK 在生产环境自动生成视频 Azure 的企业级安全合规能力让 Sora 2 成为企业落地视频生成的重要选择。\n","permalink":"https://wilsonwu.me/zh/blog/2025/azure-ai-foundry-sora-2/","summary":"\u003cp\u003e随着 Azure AI Foundry 开放对 \u003cstrong\u003eSora 2（OpenAI 生成式视频模型）\u003c/strong\u003e的支持，开发者现在可以在企业级合规、可管控的环境中使用顶尖的视频生成能力。本教程将带你从零开始，通过 \u003cstrong\u003ePlayground\u003c/strong\u003e 和 \u003cstrong\u003ePython SDK\u003c/strong\u003e 两种方式调用 Sora 2，完成「文本生成视频」的流程。\u003c/p\u003e","title":"玩转大语言模型：轻松使用 Azure AI Foundry 提供的 Sora 2 生成视频"},{"content":"在构建 AI 应用的过程中，大语言模型（LLM）的微调是企业和开发者实现“定制化能力”的核心手段。随着行业快速发展，微调技术已经从最早的传统全参数微调，演进到高效、低成本的多种方法，比如 LoRA、QLoRA、Adapters、指令微调（SFT）、奖励模型训练（RM）与 RLHF 等等。\n本文将系统介绍主流微调方法，并对比它们的优缺点，最后给出“什么场景适合什么方法”的决策指南，帮助您在项目中做出正确选择。\n为什么需要微调 LLM？ 预训练大模型虽然功能强大，但在具体业务中往往会出现：\n行业术语理解不够（如金融、法律、医疗） 回答不符合企业风格或业务逻辑 需要模型具备专门技能（如 SQL 生成、代码风格限定） 数据结构化能力不佳 多轮对话表现不符合行业预期 因此 —— 您需要微调。\n主流微调方法概览 1. 全参数微调（Full Fine-tuning） 原理： 更新模型中的所有参数（数十亿级），直接用业务数据对大模型做“重训练风格的修改”。\n优点：\n效果最好，可深度定制 可改变模型内在知识结构 缺点：\n昂贵（训练成本高） 需要大量显存 对数据量要求大 适用场景：\n超大企业、科研机构 需要深度改造模型知识，例如专业领域（法律、医学）的专家模型 2. Adapter / Prefix Tuning 原理： 冻结大部分模型，只在中间层插入小的“微调模块”（Adapter），只训练这些模块。\n优点：\n轻量、可插拔 多任务共存方便（一个模型挂多个 Adapter） 效果通常不错 缺点：\n极端任务下效果不如 LoRA / 全参数 适用场景：\n企业想在一个模型上运行多个不同业务 需要模块化、可管理的微调方式 3. LoRA 微调（Low-Rank Adaptation） 原理： 不训练大模型的全量矩阵，而是训练低秩矩阵（A、B），通过“低秩更新”改变模型行为。\n这是目前最主流的微调技术。\n优点：\n显存需求极低 效果接近全参数微调 开源生态成熟（如 HuggingFace PEFT） 缺点：\n针对极难任务时略弱于全参数微调 对某些结构（如 KV cache）敏感 适用场景：\n绝大多数企业项目 指令微调、垂直领域知识注入 全部希望“成本可控”的模型开发 4. QLoRA 原理： LoRA 的升级版，把模型权重量化到 4bit，大幅降低显存占用，同时保持高微调效果。\n优点：\n显存需求进一步下降 允许在单卡（甚至消费级 GPU）微调大型模型 缺点：\n训练速度受量化影响 对某些任务有小幅精度损失 适用场景：\n单卡/小规模设备的开发者团队 调研性质的快速实验 成本敏感型企业 5. SFT（Supervised Fine-tuning，监督式指令微调） 原理： 用成对的指令→输出的数据训练模型的行为。\n优点：\n建模简单，效果直观 对风格/格式控制非常有效 缺点：\n不能保证模型“对齐”到最优行为 会在不稳定任务中出现幻觉 适用场景：\n构建任务型模型（如客服、知识问答） 文本生成任务（如摘要、改写） 企业业务流程规范化 6. RM（Reward Model）+ RLHF / PPO 原理： 用奖励模型（Reward Model）评估 LLM 输出质量，然后用强化学习（如 PPO）调整模型行为。\n优点：\n能塑造“符合价值观”“更自然”的对话 可解决 SFT 本身的局限 缺点：\n架构复杂、成本高 数据标注要求很高 适用场景：\n构建通用助手 构建开放式对话大模型 企业要训练“高安全性、高对齐”的模型 微调方法对比表 微调方式 成本 效果 数据要求 适用场景 全参数微调 ⭐⭐⭐⭐⭐（高） ⭐⭐⭐⭐⭐ 大 专业领域深度定制 Adapter ⭐⭐ ⭐⭐⭐ 中等 多业务挂载、模块化开发 LoRA ⭐⭐ ⭐⭐⭐⭐ 中等 绝大多数应用 QLoRA ⭐ ⭐⭐⭐⭐ 中等 单卡、小预算项目 SFT ⭐⭐ ⭐⭐⭐ 需要成对数据 指令任务、格式化任务 RLHF / PPO ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 高 通用助手、用户安全对齐 不同应用场景如何选择微调方式？ 场景 1：打造客服机器人、业务问答系统 建议：LoRA / QLoRA + SFT 理由：成本低、数据一般是结构化问答，效果好。\n场景 2：法律、医疗等专业知识注入 建议：全参数微调 或 LoRA 微调 + 大规模领域语料 理由：需要深度学习专业知识。\n场景 3：企业想统一不同部门的 AI 角色 建议：Adapter 理由：一个模型挂多个部门的“工作流”。\n场景 4：构建 ChatGPT 类通用对话助手 建议：SFT → RM → RLHF（经典 pipeline） 理由：需要安全性、价值观对齐、对话自然度。\n场景 5：预算有限但想训练自己的模型 建议：QLoRA 理由：单卡即可训练，大幅降低门槛。\n场景 6：生成类任务（写作、摘要、改写） 建议：LoRA + SFT 理由：格式、风格任务的最佳方案。\n总结 大语言模型微调方法众多，并不存在“最好的方法”，只有“最适合的方案”。 选择微调方式时，可以按以下三条原则：\n看预算 → 小预算选 QLoRA / LoRA 看任务类型 → 结构化任务用 SFT，通用对话用 RLHF 看效果需求 → 专业领域深度需求选全参数微调 ","permalink":"https://wilsonwu.me/zh/blog/2025/llm-fine-tuning/","summary":"\u003cp\u003e在构建 AI 应用的过程中，\u003cstrong\u003e大语言模型（LLM）的微调\u003c/strong\u003e是企业和开发者实现“定制化能力”的核心手段。随着行业快速发展，微调技术已经从最早的传统全参数微调，演进到高效、低成本的多种方法，比如 LoRA、QLoRA、Adapters、指令微调（SFT）、奖励模型训练（RM）与 RLHF 等等。\u003c/p\u003e","title":"玩转大语言模型：大语言模型（LLM）微调主流方式和使用场景全面解析对比"},{"content":"原文链接：The Best Choice for AI Inference -\u0026gt; vLLM\n注意：本译文已获得的原文作者翻译授权，为避免广告嫌疑，本文中移除了特定产品和品牌的商业宣传内容，仅保留技术特性及企业级产品功能描述，相关内容请以原文为准。\n随着各组织从大语言模型（LLM）的试验阶段迈向生产部署，选择哪种推理平台就成了一项关键的业务决策。这个选择不仅影响性能，也影响灵活性、成本优化，以及应对快速变化业务需求的能力。\n对于技术人员和方案架构师在评估 LLM 推理平台时，应该重点考虑以下三大因素：\n架构灵活性：能否在不同硬件加速器和混合云环境间部署，而不会被某一家厂商锁定。 运行可扩展性：支持从单 GPU 部署扩展到分布式多节点的高级部署模式。 生态开放性：对最广泛的模型与内核支持，以及能与各种企业软件生态系统整合。 vLLM 在开源基础、先进内存管理能力，以及即将推出的分布式部署蓝图方面，独特地满足这些需求。与专有或硬件专用方案不同，这套组合提供了在成本、性能和运营需求上随时优化调整的自由。\n本文将深入分析为何 vLLM 在其技术架构与能力上（尤其是其 KV-Cache 管理、并行策略，以及未来的 llm-d 分布式能力）提供了最可持续的生产级 LLM 部署路径。\n开源优势 社区驱动的大规模创新 LLM 推理的发展，根本上受到开源创新的推动。过去一年半以来 vLLM V1: A Major Upgrade to vLLM’s Core Architecture | vLLM 博客（英文版），vLLM 在支持多样模型、功能和硬件后端方面取得显著成绩，从伯克利大学的研究项目成长为开源 AI 生态中的事实标准之一 vLLM 2024 Retrospective and 2025 Vision | vLLM 博客（英文版）。\nvLLM 社区发展参考链接\nvLLM 现在隶属于 PyTorch 基金会托管项目（GitHub — vllm-project/vllm: A high-throughput and memory-efficient inference and serving engine for LLMs），这保证了其长远的可持续性和治理机制。\n企业支持与开源结合 许多企业及产品及解决方案对 vLLM 的态度类似于其对 Linux/OpenStack/Kubernetes 的策略：在社区驱动创新基础上，加入企业级支持、安全性和运维工具。这样模型即可部署在本地、公有云或混合云中，避免绑定。\n这种方式带来的好处包括：\n无厂商绑定：用户可自由选择部署环境 透明开发：安全漏洞与缺陷公开追踪与快速修复 社区贡献共享：一个组织开发的功能惠及整个生态 可定制性：源代码开放，可针对特定需求修改 此外，vLLM 在以下方面具备战略优势：\n硬件无关性：不局限于 NVIDIA，支持 AMD、Intel、TPU 等多种平台（GitHub — vllm-project/vllm: A high-throughput and memory-efficient inference and serving engine for LLMs） 快速功能迭代：vLLM v1 对其核心模块（调度器、KV 缓存管理、worker、采样器、API 服务等）进行了全面重构（vLLM V1: A Major Upgrade to vLLM’s Core Architecture | vLLM 博客（英文版）） 生态整合：原生兼容 Hugging Face、OpenAI API、Kubernetes 生态 成本优化：可以为不同工作负载选择最具性价比的硬件 架构灵活性与并行化策略 理解并行化（Understanding Parallelization） 大型语言模型带来了特殊的扩展挑战。例如：一个拥有 70B 参数 的模型，仅其权重（使用 FP16 精度）就需约 140GB 内存，这远远超过单一卡片的容量。基于 vLLM 及相关产品和解决方案通过四种互补的并行化策略来应对这些挑战，每一种策略都解决不同类型的扩展问题。\n数据并行（Data Parallelism, DP）：跨请求扩展 数据并行是最简单的一种扩展模式：在多个服务器上运行完整模型的多个副本，每个副本处理不同的请求批次。在这种模式下：\n维护完整模型：每个加速器／服务器保存整个模型的权重。 分配请求：请求由负载均衡器分发给不同的副本。 支持线性扩展：增加服务器数目可以线性提升吞吐量。 简化部署：部署相对简单，不需要对模型进行切分（sharding）。 [负载均衡器] | ------------------------ | | | [Server1] [Server2] [Server3] [Model A] [Model A] [Model A] | | | [Batch 1] [Batch 2] [Batch 3] 关键：每台服务器都有完整的模型副本并行处理不同的请求 此模式与基于 KServe 的 Replica-Sets 用于模型副本服务很好的契合，能自动根据请求负载进行扩展，同时保有模型服务的简单性。\n流水线并行（Pipeline Parallelism, PP）：按层分配 流水线并行将模型按照层（layers）分拆，不同的加速器处理不同的网络层：\n顺序处理：请求在 GPU 间依次通过模型层进行处理。 内存均衡：内存需求在多个设备间较为均匀分布。 灵活部署：部署灵活，可以跨多个节点，不一定依赖特别高速的互联。 微批处理：通过微批处理（micro-batching）来保持 GPU 利用率。 Request → [GPU1:NN-Layers 1-8] → [GPU2:NN-Layers 9-16] → [GPU3:NN-Layers 17-24] → Response ↓ ↓ ↓ [Memory] [Memory] [Memory] 时间线：请求按顺序流经 GPU 但流水线并行在现代 LLM 应用中也面临一些限制：\n“流水线气泡”（pipeline bubble）效率低下：某些 GPU 在等待上游阶段的数据时处于空闲状态。 通信开销高：阶段间数据传输成为瓶颈。 延迟敏感：每个阶段都会增加延迟，对“第一个 token 的时间”（time-to-first-token）有负面影响。 对 MoE（Mixture of Experts，混合专家模型）不友好：MoE 模型中只激活某些专家（非密集执行），这样无法很好地映射到按层划分的流水线结构中。 因为这些限制，很多生产环境更倾向于将张量并行（Tensor Parallelism）用于节点内部扩展，将数据并行（Data Parallelism）用于跨节点扩展。尽管如此，在内存受限或与其他并行策略组合使用的场景中，流水线并行仍然有价值。\n张量并行（Tensor Parallelism, TP）：切分模型权重 当模型太大，单个加速器无法容纳其所有权重时，就使用张量并行来将模型权重拆分到多个 GPU 上：\n水平层拆分：每次矩阵乘法操作被分配到多个 GPU，按部分计算。 同步计算：GPU 之间通过高速互联（如 NVLink、Infinity Fabric 等）进行同步和数据交换。 内存效率：可使得模型可服务的规模比单个 GPU 容量大出约 4～8 倍。 低延迟：如果硬件条件合适，通信开销低的话，张量并行还能保持较低的延迟。 Original Matrix Operation: [Large Weight Matrix] × [Input] = [Output] With Tensor Parallelism: [Weight Part 1] → GPU1 ↘ [Weight Part 2] → GPU2 → [Combine] → [Output] [Weight Part 3] → GPU3 ↗ 关键：单矩阵运算跨 GPU 拆分，需要高速互连（NVLink） vLLM 的张量并行实现是硬件无关的，支持多种互联技术和不同类型的加速器。\n专家并行（Expert Parallelism, EP）：MoE 模型中的专家分布策略 对于混合专家模型（MoE），专家并行把不同的专家（experts）分配给多个 GPU 或多个节点，而不是让每个 GPU 都保存所有专家，由一个路由层（router layer）动态地将 token 分发给需要的专家：\n分布式专家分片：专家（experts）被分片（shard）分布在不同的 GPU／节点，使拥有上百个专家的模型能扩展到远超过单设备的内存容量。 动态 token 路由：每个 token 只被发送到它应使用的一个或多个专家，以减少计算开销。 专家并行负载平衡（Expert Parallel Load Balancing, EPLB）：防止某些热门专家／专家组被频繁调用导致不均匀负载，通过动态复制或分布专家，以分散负载。 分层调度（Hierarchical Scheduling）：在多节点集群中，路由与复制先在节点间协调，再在节点内部的 GPU 间协调，确保整体利用率均衡且减少跨节点通信。 性能收益：对大型的 MoE 模型，这种专家并行在具有高速互联（NVLink、InfiniBand 等）的环境中可以实现接近线性扩展的吞吐效率。 [Router Layer] | --------------------------------- | | | | GPU1 GPU2 GPU3 GPU4 [Exp 1,2] [Exp 3,4] [Exp 5,6] [Exp 7,8] 令牌流：路由器 → 选定专家 → 输出 vLLM 的统一策略 vLLM 的区别与优势在于它能够无缝结合这些并行策略，为分布式推理提供高度灵活性，使组织能够：\n混合使用策略：例如，在节点内部用张量并行，在节点间用流水线并行。 根据硬件特性调整：基于可用的互联速度、GPU 内存、延迟敏感性等因素做最优规划。 动态扩展：可以从单节点部署起步，随着需求增长扩展到多节点／多 GPU。 保持 API 的一致性：无论使用哪种并行策略，道线路径、调用接口等对使用者尽量透明，不需要因为并行方式不同而改很多逻辑。 这种灵活性在混合云环境中尤其关键，因为不同环境（本地、云端、不同厂商硬件）之间的配置可能大不相同。\nHybrid TP+PP+EP+DP Deployment (MoE Model): Pipeline Stage 1: Pipeline Stage 2: Pipeline Stage 3: Node 1 (Attention Layers) Node 2 (MoE FFN Layers) Node 3 (Output Layers) [Attn Part A] ←→ [Attn Part B] [Router + Experts Subset] [Out Part A] ←→ [Out Part B] GPU1 GPU2 GPU3: Experts 1-4 GPU5 GPU6 ↕ NVLink ↕ GPU4: Experts 5-8 ↕ NVLink ↕ ↕ NVLink ↕ Expert Distribution Detail (Node 2): [Router] GPU3 | GPU4 -------|------- Exp1 | Exp5 Exp2 | Exp6 Exp3 | Exp7 Exp4 | Exp8 Request Flow: → [Attention] → [MoE Layer/Router] → [Output] → Response ↓ [Route to specific experts] Parallelization Breakdown: - Tensor Parallel (TP): Attention and Output layers split within nodes. - Pipeline Parallel (PP): Different model stages across nodes . - Expert Parallel (EP): MoE experts distributed across GPUs in Node 2. - Data Parallel (DP): Entire pipeline can be replicated for more throughput. 更好的内存管理：KV-Cache（Key-Value 缓存） 在 LLM 推理中理解 KV-Cache KV-Cache（键-值缓存）是 LLM 推理优化中最关键的特性之一。在注意力计算期间，模型必须访问先前 token 的表示 —— 随着序列长度增长，这个过程变得非常消耗内存。\n高效的 KV-Cache 管理能决定同一硬件上能同时服务 10 个并发用户或 100 个并发用户，因为缓存复用将计算换成内存利用率。\n(Q * K^T) * V computation process with caching Step 1 (Prefill Phase) Keys Transpose ↓ Queries Keys Values Results | | | | [ Q ] × [ K ] → [Scores] × [ V ] → [Output] | | | | 64 64 64 64 ↓ ↓ [Storing K] [Storing V] ↓ ↓ ←── Cache memory (stores both K and V) ──→ Step N (Decode Phase) Keys Transpose ↓ Query Cached K Cached V Results | | | | [ q ] × [K₁...Kₙ] → [Scores] × [V₁...Vₙ] → [output] (new) (from cache) (from cache) (new) | | | | 1 64 64 1 ↑ ↑ ←── Reading from cache (both K and V) ──→ Key Points: - Prefill: Computes K and V for all input tokens, stores both in cache - Decode: For each new token, reads ALL previous K and V values from cache - Both K and V matrices must be cached (not just V) - Cache size grows with sequence length Prefill 阶段 vs Decode 阶段：两个截然不同的阶段 LLM 推理包含两个本质不同的阶段：\nPrefill 阶段（Prompt 处理阶段） 并行处理所有输入 token。 计算密集型，GPU 利用率高。 为 prompt 中的所有 token 生成初始的 KV-Cache 条目。 延迟与 prompt 长度成正比。 大批量（larger batch sizes）带来好处。 Decode 阶段（Token 生成阶段） 一个 token 一个 token 顺序生成。 此阶段受限于内存带宽（memory-bandwidth bound）。 每新增一个 token，都要读取整个 KV-Cache 来自缓存的所有先前的 K 和 V。 延迟与输出 token 数量成正比。 依赖高效的缓存管理。 PagedAttention：vLLM 的内存革命 vLLM 引入 PagedAttention，这是一种在 KV-Cache 管理方面的突破性设计，它将 GPU 内存视为类似操作系统中的虚拟内存（virtual memory）来处理，其关键设计包括：\n非连续存储（Non-contiguous Storage）：KV-Cache 块可以存储在 GPU 内存的任意地方，不必是连续的。 动态分配（Dynamic Allocation）：随着序列长度增长时才分配内存。 内存共享（Memory Sharing）：完全相同的 prompt 前缀（prompt prefixes）可以共享 KV-Cache 块。 几乎零浪费（Near-Zero Waste）：消除静态分配中常见的内部碎片问题。 这种设计让 vLLM 可以维持更大的批量大小、更高的并发性、以及比那些依赖静态、大型 KV-Cache 缓冲区的系统更好的 GPU 利用率。\n连续批处理（Continuous Batching）：最大化 GPU 利用率 vLLM 的连续批处理机制带来了几个举措：\n动态请求加入：新的请求可以在 decode 步骤之间加入正在运行的 batch。 提前完成处理：已经完成的序列立即释放资源。 GPU 使用最优化：通过混合处理 prefill 与 decode 操作以保持高利用率。 支持抢占（Preemption）：可以暂停低优先级的请求以优先处理紧急请求。 在部署中的实际意义 这些内存管理上的创新在实际部署中带来了具体的效益：\n更高的并发性：在相同硬件上服务更多用户。 更佳的成本效率：大幅减少基础设施需求。 改善延迟：通过高效调度缩短 time-to-first-token。 灵活性：可以处理变长的序列长度，而无需重新配置系统。 KV-Cache 优化在即将推出的 llm-d 分布式架构中变得尤为关键，因为高效的内存使用使得过去因内存约束而无法实现的新部署模式成为可能。\n使用 llm-d 扩展：Kubernetes 原生的分布式推理 超越单服务器部署 虽然 vLLM 自身是高性能推理引擎，但在大规模生产环境中，还需要复杂的编排和智能请求调度。2025 年 5 月，红帽、Google Cloud、IBM 研究院、NVIDIA 和 CoreWeave 联手推出 llm-d，构建在 vLLM 之上的 Kubernetes 原生分布式推理栈。\nllm-d 并不是 vLLM 的内置功能，而是一个补充的编排层。可以把它比作 Linux 与 Kubernetes 的关系：vLLM 负责推理引擎，llm-d 负责跨实例的调度与编排。它结合了三项开源技术：vLLM、Inference Gateway（AI 路由层）和 Kubernetes。\nKServe 也在集成 llm-d，通过新的 LLMInference CRD，实现统一的服务接口，简化生成式 AI 的企业上手路径。\nllm-d 将三大基础开源技术集成到一个统一的服务栈中：\nvLLM - 执行模型推理的高性能推理引擎。 推理网关（IGW） - 一个官方 Kubernetes 项目，通过 AI 感知路由扩展了 Gateway API。 Kubernetes - 用于部署和扩展的行业标准编排平台。 通过整合这些技术，llm-d 使企业能够在混合云环境中大规模部署 LLM 推理，并实现最快的价值实现时间和极具竞争力的性价比。\n三项关键创新 传统的负载均衡采用简单的轮询（round-robin）路由，对所有服务器一视同仁。llm-d 的 vLLM 感知调度程序能够做出智能决策，将请求路由到具有匹配缓存前缀的实例，根据实例是处理计算密集型预填充操作还是内存密集型解码操作来分配负载，并使用来自 vLLM 的实时可观测数据来避免实例过载，同时优先处理低延迟路径。这种智能路由可将基础设施成本降低 30% 到 50%，同时保持延迟 SLO。\n解耦式服务（Disaggregated Serving） llm-d 在生产规模上协调 vLLM 的本机分解服务（通过 KVConnector API），将预填充和解码分离到专门的工作节点中：\n预填充工作节点：负责计算密集型的提示处理，使用高性能 GPU（如 H100、MI300X），并可根据需求独立扩展。\n解码工作节点：专注于受内存带宽限制的 token 生成任务，使用具成本效益的 GPU（如 A100、L40S），并根据并发会话数进行弹性扩缩。\nKV 缓存传输：使用 NVIDIA NIXL 通过 UCX 提供高效的缓存移动，支持卸载到存储后端（未来交付）以及跨集群的全局缓存感知。\n这允许适当规模的基础设施：昂贵的 GPU 仅用于预填充，成本优化的硬件可为数千个并发用户提供服务。\n分布式前缀缓存（Distributed Prefix Caching） llm-d 将 vLLM 的前缀缓存扩展到多个实例之间，通过两种方式实现：\n本地缓存：在每个实例上将缓存卸载（offload）到内存或磁盘，几乎没有运维成本。\n共享缓存（计划中）：通过实例之间的 KV 传输实现共享，并使用全局索引实现集群范围的缓存感知。\n部署模式（Deployment Patterns） llm-d 支持多种先进的企业部署模式：\n异构硬件：根据工作负载混合 GPU 供应商和代数——用于预填充的高端 GPU、用于解码的成本优化 GPU 或用于低频请求的 CPU 集群。\n动态扩缩：在高峰时段独立调整预填充容量，同时保持活动会话的稳定解码容量，并具有自动资源分配和故障转移功能。\n地理分布（规划中）：在主数据中心部署集中式预填充工作者，并在用户附近部署边缘解码工作者，以实现低延迟响应。\n多加速器与混合云支持 摆脱硬件锁定 AI 加速器快速演进，使市场变得多样化。专优化 NVIDIA 的方案如 TensorRT-LLM 虽然性能优秀，但容易导致锁定。vLLM 的硬件中立设计则允许用户自由选择最适合的加速器。\n广泛硬件支持 vLLM 支持 NVIDIA GPU（针对 H100 进行一流的优化，支持 V100 及更新版本的所有 NVIDIA GPU）、AMD GPU（MI200、MI300 和 Radeon RX 7900 系列）、Google TPU（v4、v5p、v5e 和最新的 v6e）、AWS Inferentia 和 Trainium（trn1/inf2 实例）、Intel Gaudi（HPU）和 GPU（XPU）以及支持 x86、ARM 和 PowerPC vLLM 2024 Retrospective and 2025 Vision | vLLM 博客（英文版）。\n这种广泛支持带来以下战略优势：\n成本优化 选择 AMD MI300X，在特定工作负载下实现高性价比。 使用 AWS Inferentia，在 AWS 上进行经济高效的推理。 在低吞吐量用例中，可部署在现有 CPU 基础架构上。 供应链弹性 避免依赖单一 GPU 供应商的可用性。 通过多种选择协商更优惠的价格。 根据区域硬件可用性进行调整。 工作负载匹配 NVIDIA H100 可实现最佳性能。 AMD GPU 适用于开源部署。 TPU 适用于 Google Cloud 部署。 Intel Gaudi 适用于特定企业协议。 混合云部署模式 在企业级产品及解决方案中，vLLM 支持真正的混合云：\n本地核心 在本地基础架构上处理敏感数据。 满足合规性要求的工作负载。 可预测的基线负载容量。 云爆发式扩展 利用云资源处理峰值负载。 试用新硬件（H100s、TPU v6e）。 无需基础设施投资即可实现地域扩展。 边缘推理 部署在适合边缘计算的硬件上。 CPU 或更小的 GPU 推理。 与集中管理集成。 统一运维跨环境 借助企业级产品的相关功能，用户可从单一控制平面管理全部部署（公有云 + 私有云），主要从一下几个方面考虑。\n单一控制平面：通过统一界面管理所有部署。 一致的 API：跨环境实现相同的应用程序集成。 统一监控：跨混合部署的聚合指标。 策略执行：一致的安全和合规策略。 模型生态与兼容性 支持广泛的开源模型体系 随着人工智能“淘金热”步伐的加快，vLLM 已发展到支持超过 100 种模型架构的高性能推理：涵盖几乎所有著名的开源大型语言模型（LLM）、多模态模型（图像、音频、视频）、编码器-解码器模型、推测解码模型、分类模型、嵌入模型和奖励模型。vLLM 2024 Retrospective and 2025 Vision | vLLM 博客（英文版）。与专注于有限模型系列的专业解决方案相比，这种全面的支持具有根本性的优势。\n不只是传统 LLM vLLM 的架构支持更多样的模型类型：\n语言模型 Llama 系列。 Mistral 和 Mixtral 多模态模型。 Google 的 Gemma 模型。 IBM 的 Granite 系列。 阿里巴巴的 Qwen 模型。 多模态模型 视觉-语言模型（LLaVA, Qwen-VL）。 文档理解模型。 音频-语言模型。 视频理解能力。 专用架构 状态空间语言模型的生产支持，探索非 Transformer 语言模型的未来。vLLM 2024 Retrospective and 2025 Vision | vLLM 博客（英文版）。 混合专家（MoE）模型。 编码器-解码器架构。 嵌入和重排序模型。 模型集成简易流程 将新模型加入 vLLM 通常包括以下步骤：\n模型架构定义：使用熟悉的 PyTorch 模式实现。 Attention 后端集成：利用现有的优化内核。 Tokenizer 支持：兼容 Direct Hugging Face。 配置映射：基于标准 YAML 的配置。 这样的标准化流程意味着组织能更快将新模型投入生产。例如，在 2025 年 8 月 GPT-OSS 发布后不久，vLLM 的 v0.11.0 版本就加入了对其生产部署的支持。\n与 Hugging Face 生态整合 vLLM 与 Hugging Face 的原生兼容带来了便捷性：\n直接模型加载 直接从 Hugging Face Hub 和/或兼容 S3 的对象存储后端（例如 MinIO）加载模型。 支持私有模型库。 自动权重转换处理。 分词器兼容性 使用现有的分词器实现。 支持自定义分词器。 快速分词器优化 配置保存 尊重特定于模型的配置。 支持自定义模型参数。 兼容经过微调的变体。 生产级模型管理 在一些企业级产品和方案中增加了模型生命周期的企业功能：\n模型注册集成 已部署模型的版本控制。 A/B 测试功能。 回滚机制。 模型监控 每个模型的性能跟踪。 使用情况分析和成本归因。 偏差检测功能。 面向未来的模型支持 模型创新速度极快，推理平台必须具备适应能力：\n社区贡献 活跃的社区不断添加新模型。 供应商中立的开发流程。 快速集成突破性架构。 灵活的架构 模块化设计支持新的模型范式。 不受特定模型假设的约束。 为后转型架构做好准备。 企业验证 企业的测试和认证流程。 模型工件的安全扫描。 跨硬件性能验证。 这种全面的模型支持确保组织能够在新模型出现时立即采用它们，而无需进行平台迁移或架构更改，这是人工智能领域持续快速发展的一项关键能力。\n使用企业级产品进行企业部署 从第一天起就是生产级 大多数企业级产品提供了一个灵活、可扩展的 MLOps 平台，提供构建、部署和管理 AI 应用的工具。它采用开源技术构建，为团队提供值得信赖且操作一致的功能，用于实验、模型服务和交付创新应用。了解更多关于企业级产品如何应对这一挑战的信息。\n与 KServe 的整合：智能模型服务 在许多产品和方案中对 vLLM 与 KServe 的集成提供了企业级服务功能：\nGenAI 功能 使用 vLLM 服务运行时进行多节点/多 GPU 推理。 通过 vLLM + LMCache 集成卸载键值缓存。 通过模型缓存高效重用模型。 集成 KEDA，允许基于外部指标自动扩缩。 通过与 Envoy AI Gateway 集成实现速率限制和请求路由。 通过 LLMInferenceService CRD 访问 llm-d 功能。 高级自动扩缩 基于请求的扩缩，实现资源利用率优化。 缩减​​至零功能，实现成本优化。 基于流量模式的预测性扩缩。 多指标扩缩（GPU 利用率、队列深度、延迟）。 流量管理 金丝雀部署，确保模型更新安全。 蓝绿部署，实现即时回滚。 A/B 测试，用于模型比较。 影子流量，用于验证。 服务网格集成 使用 Istio 进行端到端加密。 高级路由和负载均衡。 熔断和重试逻辑。 用于调试的分布式跟踪。 自动化运维 健康检查和自动恢复。 资源优化建议。 自动化证书管理。 日志聚合和分析。 安全与合规 在企业部署中，必须重视安全：\n访问控制 RBAC 与企业身份提供商集成。 模型级访问权限。 API 密钥管理。 所有操作的审计日志记录。 数据保护 静态和传输中加密。 私有端点选项。 网络策略实施。 合规性报告工具。 供应链安全 签名容器镜像。 SBOM（软件物料清单）生成。 漏洞扫描。 基于策略的部署控制。 MLPerf 验证的性能 一些企业级解决方案和产品与众多知名服务器原始设备制造商（OEM）合作，使用搭载 NVIDIA GPU 和 vLLM 推理运行时的产品发布了令人印象深刻的 MLPerf 推理结果，在应对这一挑战方面取得了重大进展。加速生成式 AI 的采用，这些结果验证了：\n规模化生产级性能。 高效的资源利用率。 负载下一致的延迟。 多实例协调能力。 集成可观测性 无需额外工具即可进行全面监控：\n指标和仪表板 预建 Grafana 仪表板，用于 vLLM 指标。 预建 Grafana 仪表板，用于驱动路由决策的请求调度程序指标。 令牌生成率和延迟。 GPU 利用率和内存使用率。 队列深度和拒绝率。 警报和响应 SLA 违规自动警报。 与企业监控系统集成。 Runbook 自动化功能。 容量规划洞察。 成本管理与优化（未来交付） 专为企业成本控制设计的功能：\n计费/显示：按团队或项目跟踪使用情况。 资源配额：防止成本失控。 竞价型实例支持：降低批量工作负载的成本。 空闲检测：自动缩减未使用的资源。 这个企业级平台将 vLLM 从高性能推理引擎转变为完整的生产解决方案，可用于关键任务部署。\n特性对比：vLLM vs TGI vs TensorRT-LLM ✓ = 全面支持，◐ = 部分/有限支持，✗ = 不支持\n企业级产品与开源 vLLM 的主要区别 硬件灵活性：最广泛的加速器支持，包括 AMD、Intel、Google TPU 和 CPU。 模型生态系统：支持超过 100 种模型架构，而其他替代方案仅有 25-40 种。 分布式架构：即将推出的 llm-d 支持分解式预填充/解码和系统级 kv-cache 路由，以实现分布式最优扩展。 企业集成：企业级产品集成 KServe 自动扩展。 内存效率：先进的 PagedAttention 和 KV-Cache 管理。 开放开发：PyTorch 基金会项目，社区创新迅速。 如何作出正确选择 文中给出一个简单的决策框架：对每个判断点打 “Yes”：\n0–2 个 “Yes”：可考虑专用方案（若其满足你的特定需求） 3–5 个 “Yes”：vLLM 在你的场景中将提供显著优势 6+ 个 “Yes”：基于 vLLM 的企业级产品是战略性选择 每个决策点都涉及基本的架构约束：\n灵活性要求决定您是否需要与硬件无关的解决方案。 运营复杂性评估简化的运营是否值得采用开源技术。 模型与创新评估快速发展是否需要一个适应性强的平台。 长期可持续性考虑总体拥有成本和战略风险。 战略推荐 如果你对多个维度都倾向 “Yes”，那 vLLM + 企业级产品将提供：\n最大的灵活性：可在任何受支持的硬件上部署。 优秀的运维体验：企业级平台，复杂性极低。 面向未来的能力：支支持新兴模型和架构。 成本优化：为每种工作负载选择最佳硬件。 风险控制：开源基础，提供企业级支持。 这些因素的融合使得 vLLM 成为构建可持续人工智能基础设施的组织的战略选择。\n采纳步骤建议 立即阶段 在开发环境部署 vLLM + 企业级产品 用自己的模型和工作负载进行测试 验证硬件灵活性与性能 短期阶段 实施 KServe 的自动扩缩能力 建立监控与可观察性体系 培训团队操作流程 中期阶段 设计生产部署架构 若需要，实施混合云模式 为 llm-d 分布式架构做准备 长期阶段 优化成本（选硬件、资源调度） 引入更高级部署模式 将自己的改进贡献给社区 总结 选择 LLM 推理平台代表着一项战略承诺，它将影响您组织未来几年的人工智能能力。\n我们的分析表明，企业级产品上的 vLLM 独特地满足了企业 LLM 部署的三个关键要求：\n灵活性：可在任意硬件与混合云环境部署 可扩展性：先进内存管理与 llm-d 架构使资源利用效率提升 10–100 倍 可持续性：开源基础 + 企业支持，避免陷入锁定 虽然 TensorRT-LLM 提供了 NVIDIA 专属优化，TGI 也提供了 Hugging Face 集成，但“只有 vLLM 能够提供快速发展的 AI 领域所需的架构灵活性”。 凭借对 100 多种模型架构的支持、与硬件无关的设计以及 PyTorch 和 Linux 基金会的支持，-\u0026gt;“搭载 vLLM 的企业级产品为长期成功奠定了最坚实的基础”。\n","permalink":"https://wilsonwu.me/zh/blog/2025/the-best-ai-inference-vllm/","summary":"\u003cp\u003e原文链接：\u003ca href=\"https://medium.com/open-5g-hypercore/episode-xxx-the-best-choice-for-ai-inference-vllm-286b2af2df71\"\u003eThe Best Choice for AI Inference -\u0026gt; vLLM\u003c/a\u003e\u003c/p\u003e\n\n\u003cblockquote\u003e\n  \u003cp\u003e注意：本译文已获得的原文作者翻译授权，为避免广告嫌疑，本文中移除了特定产品和品牌的商业宣传内容，仅保留技术特性及企业级产品功能描述，相关内容请以原文为准。\u003c/p\u003e\n\n\u003c/blockquote\u003e","title":"AI 推理的最佳选择 - vLLM"},{"content":"大模型在推理部署时，往往存在显存瓶颈：\n模型参数（Parameters, P）动辄上百 GB，需要长期驻留显存。 输入/输出数据（Data, D）则随请求动态变化，但往往和参数耦合在同一设备上，导致显存占用不均衡，扩展性受限。 为了解决这一问题，可以借助 vLLM 框架实现参数 / 数据（P/D）分离，提升推理系统的灵活性和吞吐。\n大模型推理的资源瓶颈 以一个 70B 规模的 Transformer 模型为例：\n参数权重（FP16 存储）约需 140GB 显存； 每次请求输入的序列数据、KV Cache 会消耗额外显存，并随 batch size 增长而急剧膨胀。 如果不加区分地将 P 与 D 放在同一块 GPU：\n参数长期驻留，挤压了用于动态数据的显存； 多实例并发时，数据显存不足，限制了吞吐。 因此，在分布式推理系统中，业界逐渐采用 参数与数据分离（P/D Separation） 的架构思路。\nvLLM 简介 vLLM 是一个高性能的大模型推理引擎，核心优势包括：\nPagedAttention：高效管理 KV Cache，支持大批量并发； 高吞吐率：相较于 Hugging Face Transformers 推理，吞吐提升数倍； 灵活的分布式支持：可结合 DeepSpeed、Megatron 等方案，支持参数/数据分布式存储与调度。 vLLM 的模块化架构，使其天然适合实现 P/D 分离。\nP/D 分离的实现思路 在 vLLM 中，推理流程大致分为两个部分：\n参数侧（P）\n模型权重加载与存放； 可通过 ZeRO-3 / Tensor Parallel 等策略将参数分布在多 GPU 节点上； 参数在整个推理生命周期中保持常驻，不随请求波动。 数据侧（D）\n动态输入序列、KV Cache、采样状态等； 可使用 PagedAttention 管理显存分页； 支持把部分 KV Cache 放到 CPU/Host 内存，再动态迁移到 GPU。 核心机制 其核心机制是显存资源隔离：\n权重（P） 固定驻留在专门的 GPU（参数节点）； 数据（D） 可以按需分配到其他 GPU（数据节点），甚至利用 CPU 内存 / NVMe。 这样：\n参数显存不与动态数据抢占空间； 数据可以根据请求动态扩展，提升并发能力。 在 vLLM 中的实现路径 实现 P/D 分离的常见方法：\n参数分片（Parameter Sharding）\n借助 vLLM 对 Megatron-LM / DeepSpeed 的支持，将模型参数按张量维度切分到多 GPU； 仅在推理计算时聚合，不需要在每个设备上完整存放参数。 KV Cache 分离（Data Sharding）\n通过 vLLM 的 PagedAttention，可以把 KV Cache 存在独立的 GPU 或 Host 内存； 在推理过程中按需调页，类似操作系统的虚拟内存机制。 Pipeline 并行化\n将模型计算划分为不同阶段，前几层专门占用参数节点，后几层在数据节点完成； 在流水线中，数据逐层传递，而参数保持分片驻留。 基于 vLLM 的 P/D 分离实战 下面给出两部分内容：\n配置示例：如何在多 GPU 环境下指定参数和数据分布； 代码示例：如何加载模型并运行推理，展示参数常驻、数据动态分配。 配置示例 假设我们有 4 张 GPU：\nGPU 0,1：专门存放模型参数（P）； GPU 2,3：主要承载 KV Cache 等动态数据（D）。 我们可以在启动 vLLM 时设置：\nCUDA_VISIBLE_DEVICES=0,1,2,3 \\ python -m vllm.entrypoints.api_server \\ --model /path/to/llama-2-70b \\ --tensor-parallel-size 2 \\ --gpu-memory-utilization 0.85 \\ --enable-chunked-prefill \\ --enforce-eager \\ --kv-cache-dtype fp16 \\ --max-num-batched-tokens 4096 这里的关键点：\n--tensor-parallel-size 2：只在 GPU 0,1 上做参数分片存放； --gpu-memory-utilization：控制参数占用的比例，留出余量给数据； --kv-cache-dtype fp16：降低 KV Cache 精度，减少数据显存占用； vLLM 内部的 PagedAttention 会自动把 KV Cache 分配到 GPU 2,3，如果溢出则回退到 CPU 内存。 代码示例 下面是一个 Python 调用示例：\nfrom vllm import LLM, SamplingParams # -------- 参数（P）分离 -------- # tensor_parallel_size=2 表示参数分布在前两张 GPU（0,1） llm = LLM( model=\u0026#34;/path/to/llama-2-70b\u0026#34;, tensor_parallel_size=2, gpu_memory_utilization=0.85, enforce_eager=True, # 避免显存碎片 enable_chunked_prefill=True, kv_cache_dtype=\u0026#34;fp16\u0026#34; # 数据（D）使用更轻量的缓存精度 ) # -------- 数据（D）分离 -------- # PagedAttention 会自动管理 KV Cache 的存放位置（GPU2, 3, 甚至 CPU 内存） sampling_params = SamplingParams( temperature=0.8, top_p=0.95, max_tokens=256 ) prompt = \u0026#34;解释一下通过 vLLM 实现 P/D 分离的好处。\u0026#34; # 运行推理 outputs = llm.generate([prompt], sampling_params) print(\u0026#34;模型输出：\u0026#34;) for output in outputs: print(output.outputs[0].text) 验证方式 运行时可以用 nvidia-smi 观察：\nGPU 0,1 显存几乎恒定，主要承载参数； GPU 2,3 的显存随请求数量/序列长度波动，主要承载 KV Cache； 如果输入过大，KV Cache 会溢出到 CPU 内存，但仍可继续推理。 实际效果与收益 通过 P/D 分离，可以得到以下好处：\n提升 GPU 利用率：参数和数据分工明确，避免显存冲突； 支持更大 batch size：数据显存可扩展，吞吐率提升显著； 降低成本：参数可驻留在高端 GPU，而数据可放到中低端 GPU 或 CPU 内存，形成分层存储； 灵活性：适配多种硬件资源组合（A100 + H100, GPU + CPU, GPU + NVMe）。 在实践中，结合 vLLM 的 PagedAttention，P/D 分离的效果尤为明显，可让大模型推理在同样硬件下实现 数倍并发提升。\n总结 vLLM 框架凭借其高效的 KV Cache 管理和分布式能力，为 大模型推理的 P/D 分离 提供了坚实基础。 通过将 参数（P） 和 数据（D） 解耦，可以更好地利用硬件资源，提升推理性能与扩展性。\n未来，随着 混合精度存储、分层缓存（GPU+CPU+NVMe） 的发展，P/D 分离架构将逐渐成为大模型推理的主流范式。\n","permalink":"https://wilsonwu.me/zh/blog/2025/vllm-pd-separation/","summary":"\u003cp\u003e大模型在推理部署时，往往存在\u003cstrong\u003e显存瓶颈\u003c/strong\u003e：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e模型参数（Parameters, P）动辄上百 GB，需要长期驻留显存。\u003c/li\u003e\n\u003cli\u003e输入/输出数据（Data, D）则随请求动态变化，但往往和参数耦合在同一设备上，导致显存占用不均衡，扩展性受限。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e为了解决这一问题，可以借助 \u003cstrong\u003evLLM 框架\u003c/strong\u003e实现\u003cstrong\u003e参数 / 数据（P/D）分离\u003c/strong\u003e，提升推理系统的灵活性和吞吐。\u003c/p\u003e","title":"玩转大语言模型：基于 vLLM 框架的大模型推理优化实现参数 / 数据（P/D）分离"},{"content":"什么是 VibeVoice？ VibeVoice 是 Microsoft Research 发布的一套面向长篇、多说话人、对话式语音合成的研究框架，目标场景例如整集播客、有声剧或访谈：能在单次生成中维持说话人一致性并处理自然的换手（turn-taking）。模型家族包含多个规模（例如 1.5B、7B 等），并在 Hugging Face 上以 microsoft/VibeVoice-1.5B 形式开放（模型卡、模型文件、model card 中有安装/使用与责任使用说明）。\n它解决了传统 TTS（Text-To-Speech）系统的一些痛点，比如：\n难以维持长时间对话的语音一致性（speaker consistency）； 多说话人的切换自然性（turn-taking）差； 效率低 — 长文本 + 多说话人时，资源消耗大。 核心创新与架构 VibeVoice 有几个比较新的或者关键的技术设计：\n组件 功能 / 目的 Continuous Speech Tokenizers（声学 + 语义两种） 用来把音频压缩成低帧率（7.5 Hz）表示，同时保留语义与音质信息。声学 token 与语义 token 分别负责声音细节和内容表达。 LLM 基础模型（Large Language Model） 在该版本里用的是 Qwen2.5-1.5B，用来处理文本、说话人信息以及上下文对话流。 Diffusion Head 对声学 VAE 的特征进行预测，是生成高保真声音细节的模块。这个模块较轻 (大致 4 层结构)，在推理阶段使用 diffusion 的技术（包括去噪等）。 上下文长度 \u0026amp; 多说话人 支持高达 90 分钟语音生成，最多 4 个说话人。 架构图如下：\n优点和局限 优点 长篇幅对话能力 — 能生成近 90 分钟的连续对话，并维持说话人一致性。 多说话人支持 — 最多支持 4 个不同说话人的切换，且对话流程自然。 压缩效率高 — 用 7.5 Hz 帧率的 speech tokenizer，音频被高度压缩但仍保证质量。 语义 + 声学分离 — 语义 tokenizer（semantic）负责内容，声学 tokenizer（acoustic）负责声音细节，两者协同。 局限 语言支持有限 — 目前主要是英语与中文，跨语言能力还在研究中。 背景音乐 / 音效不支持 — 当前专注纯语音合成，没有加入背景音或重叠说话人（speaker overlap）。 仓库暂时关闭 — 官方声明因 “部分用途超出初衷 / 不符合责任使用” 的情况，仓库暂时被 disable。意味着可能无法立即拿到源码或全部功能。 硬件要求 / 资源消耗 — 虽然压缩效率高，但生成长篇语音 + 多说话人 + 高保真度仍需要较好的 GPU 资源。 潜在滥用风险 — 用来做语音克隆、假扮、deepfake 等可能的恶意用途。官方在文档中也提到要谨慎。 快速上手 下面给出具体实现步骤及上手流程。注意：由于官方仓库目前不包含完整脚本，实际推理通常依赖社区实现或 Hugging Face Spaces / Colab 示例。所列步骤已参考 Hugging Face 模型页与社区示例仓库。\n先决条件 Python 3.8+；建议在隔离虚拟环境（venv / conda）中操作。\nGPU：带 CUDA 的 NVIDIA GPU（具体 VRAM 与性能见第 5 节）。若使用 Apple Silicon / macOS，可参见社区讨论与专门脚本。\n安装工具（示例）：\npip install huggingface_hub transformers accelerate torch soundfile torchaudio numpy einops 本地需要 ffmpeg（用于音频切分/拼接与格式转换）。 具体版本请按 PyTorch 官网或社区脚本说明选择（不同 CUDA 版本的 wheel 不同）。参考社区仓库的 requirements 列表以确保兼容。\n下载模型权重 from huggingface_hub import snapshot_download model_dir = snapshot_download(\u0026#34;microsoft/VibeVoice-1.5B\u0026#34;) print(\u0026#34;模型已下载到\u0026#34;, model_dir) 注意：snapshot_download 会把 HF 模型文件和模型卡同步拉取到本地；若模型文件较大，请确保磁盘与网络条件。\n使用社区推理脚本 很多社区仓库或 Hugging Face Spaces 提供了 demo/inference_from_file.py 之类的脚本。示例命令：\n# 在 Colab / 本地环境中操作 git clone https://huggingface.co/technicalheist/vibevoice-1.5b cd vibevoice-1.5b # 安装 Python 依赖（按模型页 “Requirements” 部分） pip install torch transformers soundfile ffmpeg-python # plus 其他必要包 # 创建一个 transcript 文件，比如 transcript.txt echo \u0026#34;Speaker 1: Hello, how are you doing? Speaker 2: I\u0026#39;m good, thanks. What about you?\u0026#34; \u0026gt; transcript.txt # 用 demo 脚本生成语音 python demo/inference_from_file.py \\ --model_path /path/to/technicalheist/vibevoice-1.5b \\ --txt_path transcript.txt \\ --speaker_names \u0026#34;Alice,Bob\u0026#34; 社区实现（例如 technicalheist/vibevoice-1.5b、shijincai/VibeVoice 等）提供了可运行的示例与依赖说明；使用前请仔细阅读其 README 并依据其中的依赖和指引来配置环境。示例脚本名与参数在不同实现中可能不同，请以具体仓库中的文档为准。\n简单示例 Speaker 1: Hello, welcome to our podcast. Speaker 2: Thanks — happy to be here. Let\u0026#39;s talk about VibeVoice. Speaker 1: Sure, start with the key idea... 将上面保存为 transcript.txt 并传入 --txt_path 即可（脚本会解析说话人标记并输出对应音轨）。社区脚本通常会把每个说话人的音色/说话风格作为参数或示例内置。\n结论 微软的 VibeVoice 是当前 TTS / 语音合成领域一个非常令人兴奋的进展。它在长篇幅语音 + 多说话人上取得了突破，尤其是在对话流畅性和说话人一致性方面，技术设计（如低帧率的双 tokenizers + next-token diffusion + LLM 架构）也很前沿。\n如果你正在做播客、有声书或者对话剧本的语音合成，这可能是一个非常有用的工具／研究基础。不过在商业部署或者包含敏感语音身份／法律边界用途时，需要格外注意责任使用，以及是否有完整的许可与风险评估。\n","permalink":"https://wilsonwu.me/zh/blog/2025/microsoft-vibevoice/","summary":"\u003ch2 id=\"什么是-vibevoice\"\u003e什么是 VibeVoice？\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e\u003ca href=\"https://microsoft.github.io/VibeVoice/\"\u003eVibeVoice\u003c/a\u003e\u003c/strong\u003e 是 Microsoft Research 发布的一套面向\u003cstrong\u003e长篇、多说话人、对话式\u003c/strong\u003e语音合成的研究框架，目标场景例如整集播客、有声剧或访谈：能在单次生成中维持说话人一致性并处理自然的换手（turn-taking）。模型家族包含多个规模（例如 1.5B、7B 等），并在 Hugging Face 上以 \u003ca href=\"https://huggingface.co/microsoft/VibeVoice-1.5B\"\u003e\u003ccode\u003emicrosoft/VibeVoice-1.5B\u003c/code\u003e\u003c/a\u003e 形式开放（模型卡、模型文件、model card 中有安装/使用与责任使用说明）。\u003c/p\u003e","title":"玩转大语言模型：微软最新开源长语音大模型 VibeVoice 入门"},{"content":"随着大语言模型热度的升级，企业和个人使用者的研究重心逐步从训练转移至推理（说白了是由造轮子转变为务实的使用）。而在模型推理领域，最炙手可热的两大框架无疑是 vLLM 和 SGLang，而作为后起之秀的 SGLang，其表现也值得大家关注，今天就基于 SGLang 为大家带来一篇入门教程文章，希望能帮助更多希望了解大语言模型及 SGLang 框架的朋友。\nSGLang 简介 SGLang 是一款面向大语言模型（LLM）和视觉语言模型（VLM）的高性能推理框架，通过精心设计的后端运行时与前端语言协同工作，使模型交互更加高效且可控。其核心优势包括：\n高效后端运行时：采用创新的 RadixAttention 技术实现前缀缓存，支持跳跃式受限解码、零开销 CPU 调度、连续批处理、令牌注意力（分页注意力）、张量并行、FlashInfer 内核、分块预填充以及多种量化技术（FP8/INT4/AWQ/GPTQ），显著提升推理效率。 灵活前端语言：提供直观且强大的 LLM 编程接口，支持链式生成调用、高级提示工程、复杂控制流、多模态输入、并行处理及外部系统交互。 广泛模型兼容性：支持多种主流生成式模型（Llama、Gemma、Mistral、QWen、DeepSeek、LLaVA 等）、嵌入模型（e5-mistral、gte）及奖励模型（Skywork），并提供简便的新模型扩展机制。 活跃开源生态：拥有蓬勃发展的社区支持，已获得广泛业界认可（截至 2025 年 3 月 17 日，GitHub 累计星标超过 12,000）。 其技术架构如下：\n除此之外，对于初学者，需要了解其以下特性：\nOpenAI 兼容 API：直接用 openai Python SDK 或 cURL 调用，不用改你的上层业务代码。 高吞吐与低延迟：结合连续批处理与前缀缓存等技巧，让相同前缀的请求复用计算。 生产友好：支持多并发、流式输出、可与 Hugging Face 模型库直接对接。 环境准备 工欲善其事必先利其器，要完成本文的新手实验，需准备如下环境：\n操作系统：建议 Linux（常用为 Ubuntu 20.04+）。WSL2 也可尝试。 Python：建议 3.10 或 3.11。 GPU：建议 NVIDIA 显卡，24GB 显存可跑 7B/8B 级（如 Llama 3.1 8B）。没有 GPU 也能跑小模型或量化模型，但性能有限。 模型来源：Hugging Face（如 meta-llama/Llama-3.1-8B-Instruct）或本地路径。 安装 SGLang 环境就绪后，使用 pip 安装 SGLang（确保已安装 pip、可选创建虚拟环境）。\npip install sglang openai huggingface-hub 如果你需要从 Hugging Face 自动下载私有或受限模型，请先登录或设置 Token：\nhuggingface-cli login # 或 export HF_TOKEN=您的Token 启动 SGLang 服务 可以通过以下两种启动方式进行 SGLang 服务的启动。不同版本的 SGLang 命令行参数可能略有差异，如果出现参数名称不匹配，先执行 \u0026ndash;help 查看本机版本支持的参数。\n通过 launcher 启动 python -m sglang.launcher \\ --model meta-llama/Llama-3.1-8B-Instruct \\ --host 0.0.0.0 \\ --port 30000 \\ --tp 1 \\ --max-model-len 8192 其中常用参数说明如下：\n\u0026ndash;model：Hugging Face 模型名称或本地模型权重路径 \u0026ndash;host/\u0026ndash;port：服务监听地址与端口 \u0026ndash;tp：Tensor Parallel 并行度（单卡设为 1；多卡可根据需要设为 2、4 等） \u0026ndash;max-model-len：上下文最大长度 另外需要注意以下两点：\n如果使用的是 Hugging Face 模型，首次运行会下载模型权重，请确保网络通畅。 如果显存吃紧，可优先尝试更小模型，或减少 \u0026ndash;max-model-len。 通过 sglang 命令启动 如果您使用的版本提供 sglang serve 命令，也可使用如下命令进行启动：\nsglang serve -m meta-llama/Llama-3.1-8B-Instruct --host 0.0.0.0 --port 30000 快速验证 服务启动后，OpenAI 兼容接口即可使用：\ncurl http://127.0.0.1:30000/v1/chat/completions \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -H \u0026#34;Authorization: Bearer EMPTY\u0026#34; \\ -d \u0026#39;{ \u0026#34;model\u0026#34;: \u0026#34;meta-llama/Llama-3.1-8B-Instruct\u0026#34;, \u0026#34;messages\u0026#34;: [ {\u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;You are a helpful assistant.\u0026#34;}, {\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;用两句话介绍一下你自己。\u0026#34;} ], \u0026#34;max_tokens\u0026#34;: 128, \u0026#34;temperature\u0026#34;: 0.2 }\u0026#39; 如果运行正常，将返回标准的 ChatCompletion JSON 响应。\n使用 Python OpenAI SDK 进行调用 非流式调用（最简单的问答）：\nfrom openai import OpenAI client = OpenAI( base_url=\u0026#34;http://127.0.0.1:30000/v1\u0026#34;, api_key=\u0026#34;EMPTY\u0026#34; # 本地服务通常不校验 ) messages = [ {\u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;You are a helpful assistant who answers in Chinese.\u0026#34;}, {\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;请用三句话介绍帕累托最优。\u0026#34;} ] resp = client.chat.completions.create( model=\u0026#34;meta-llama/Llama-3.1-8B-Instruct\u0026#34;, messages=messages, temperature=0.2, max_tokens=256 ) print(resp.choices[0].message.content) 流式输出（边生成边打印）：\nfrom openai import OpenAI client = OpenAI(base_url=\u0026#34;http://127.0.0.1:30000/v1\u0026#34;, api_key=\u0026#34;EMPTY\u0026#34;) messages = [ {\u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;You are a helpful assistant that speaks Chinese.\u0026#34;}, {\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;简述 Transformer 的核心思想，并给出一个现实类比。\u0026#34;} ] stream = client.chat.completions.create( model=\u0026#34;meta-llama/Llama-3.1-8B-Instruct\u0026#34;, messages=messages, temperature=0.2, max_tokens=300, stream=True ) for chunk in stream: if chunk.choices and chunk.choices[0].delta: delta = chunk.choices[0].delta.content if delta: print(delta, end=\u0026#34;\u0026#34;, flush=True) print(\u0026#34;\\n[Done]\u0026#34;) 通过以上两种方式，我们可以构建自己的推理服务应用，也可以通过市面上开源的软件来接入您部署的推理服务，如 Open-WebUI 等。\n常见问题与排错 启动时报显存不足（CUDA OOM） 尝试更小的模型（如 7B/8B）、减小 --max-model-len、或降低并发。 如果有多卡，增加 \u0026ndash;tp 并用多卡分摊显存。 拉取模型失败或速度慢 先执行 huggingface-cli login；或提前手动把模型权重下载到本地路径，并在 --model 里填本地路径。 API 报 Unknown argument 不同版本的命令行参数可能有差异。请运行：python -m sglang.launcher --help 根据你安装的版本调整参数名，如 --model / --model-path、--tp / --tensor-parallel-size 等。 Python 客户端 401/Key 错误 本地服务通常不校验 Key，只要带上一个占位即可（如 EMPTY）。 流式输出不打印 确认传了 stream=True；迭代 chunk 时取 choices[0].delta.content。 并发性能不达预期 尝试用 Async 客户端并发请求；避免在单线程里串行等待。 检查 GPU 利用率；确认没有 CPU/GPU 数据传输瓶颈。 总结 至此，你已经完成了：\n安装与启动 SGLang 服务 用 OpenAI 兼容接口进行非流式与流式推理 祝你上手顺利，玩得开心！如果遇到命令行参数或 API 的小差异，参考本地版本的 \u0026ndash;help 与官方文档进行快速适配即可。\n","permalink":"https://wilsonwu.me/zh/blog/2025/getting-started-with-sglang/","summary":"\u003cp\u003e随着大语言模型热度的升级，企业和个人使用者的研究重心逐步从训练转移至推理（说白了是由造轮子转变为务实的使用）。而在模型推理领域，最炙手可热的两大框架无疑是 vLLM 和 SGLang，而作为后起之秀的 SGLang，其表现也值得大家关注，今天就基于 SGLang 为大家带来一篇入门教程文章，希望能帮助更多希望了解大语言模型及 SGLang 框架的朋友。\u003c/p\u003e","title":"玩转大语言模型：使用 SGLang 框架实现大语言模型推理入门教程"},{"content":"DeepSeek 的爆火让人们再一次看到了 AI 的魅力，而随之而来的不仅不是对算力需求的减少，而是在低成本亲民化人工智能中构建更多 AI 业务场景带来的另一波算力需求。今天我们来通过 Azure AI Foundry（原 Azure AI Studio）快速体验 DeepSeek 的风采。\n先决条件 首先需要拥有 Azure 订阅，新用户参考玩转大语言模型：无需任何代码通过 Azure OpenAI 服务构建个人专属 ChatGPT中步骤进行开通。然后在 Azure 订阅中创建 AI Foundry 及相关资源，可以参考创建 Azure AI Foundry 服务中的步骤，这里不再赘述。\n一切完成后进入 Azure AI Foundry 首页，并打开名为 main 的 Project，界面如下：\n部署 DeepSeek-R1 大语言模型 点击左侧菜单中的 Model catalog 进入模型列表页：\n这里可以看到 Azure 提供了超过 1800 种模型，满足用户全方位的需求。\n在搜索框输入 DeepSeek 来查看 Azure 支持的 DeepSeek 模型种类：\n其中，第一个是全量版 DeepSeek-R1 671B 模型：\n另一个是经过 NPU 优化的基于 Qwen 的 DeepSeek-R1 1.5B 蒸馏版：\n我们选择 671B 版本进行部署尝试，进入模型详情后点击 Deploy 进行部署确认：\n再点击 Deploy 完成部署，部署结束后会自动跳转至 My assets 中的 Models + endpoints 详情中：\n至此 DeepSeek-R1 671B 部署完成，整个过程非常简单。\n调用 DeepSeek-R1 进行推理 接下来我们尝试调用一下刚才部署的模型进行对话，进入刚才部署的模型详情中，点击 Consume Tab：\n这里我们选择 Python 代码来进行尝试，代码如下：\n# pip install azure-ai-inference import os from azure.ai.inference import ChatCompletionsClient from azure.core.credentials import AzureKeyCredential api_key = os.getenv(\u0026#34;AZURE_INFERENCE_CREDENTIAL\u0026#34;, \u0026#39;这里填写该部署在详情页面看到的 Key 值\u0026#39;) if not api_key: raise Exception(\u0026#34;A key should be provided to invoke the endpoint\u0026#34;) client = ChatCompletionsClient( endpoint=\u0026#39;https://DeepSeek-R1-gxmog.eastus.models.ai.azure.com\u0026#39;, credential=AzureKeyCredential(api_key) ) model_info = client.get_model_info() print(\u0026#34;Model name:\u0026#34;, model_info.model_name) print(\u0026#34;Model type:\u0026#34;, model_info.model_type) print(\u0026#34;Model provider name:\u0026#34;, model_info.model_provider_name) payload = { \u0026#34;messages\u0026#34;: [ { \u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;我即将前往北京，我能在那里看到什么景色？\u0026#34; }, { \u0026#34;role\u0026#34;: \u0026#34;assistant\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;北京，中国的首都\u0026#34; }, { \u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;关于 #1 有哪些令人惊叹的内容？\u0026#34; } ], \u0026#34;max_tokens\u0026#34;: 2048 } response = client.complete(payload) print(\u0026#34;Response:\u0026#34;, response.choices[0].message.content) print(\u0026#34;Model:\u0026#34;, response.model) print(\u0026#34;Usage:\u0026#34;) print(\u0026#34;\tPrompt tokens:\u0026#34;, response.usage.prompt_tokens) print(\u0026#34;\tTotal tokens:\u0026#34;, response.usage.total_tokens) print(\u0026#34;\tCompletion tokens:\u0026#34;, response.usage.completion_tokens) 创建一个 .py 文件（如：ds-demo.py），将上面的代码粘贴到其中，替换部署自身的 Key 到 api_key 变量中，然后执行如下命令安装依赖包：\npip install azure-ai-inference 然后执行代码：\npython ds-demo.py 正常情况下输出结果类似如下内容：\nModel name: deepseek-r1 Model type: chat-completion Model provider name: DeepSeek Response: \u0026lt;think\u0026gt; \u0026lt;/think\u0026gt; 北京作为中国的首都，拥有悠久的历史和丰富的文化遗产，同时也是现代化大都市的典范。以下是一些您在北京可以体验到的精彩内容： 1. **历史文化景点**： - **故宫（紫禁城）**：明清两代的皇家宫殿，世界文化遗产，展示了中国古代建筑和宫廷文化的精华。 - **天坛**：明清皇帝祭天的场所，以其独特的建筑风格和象征意义闻名。 - **长城（如八达岭、慕田峪段）**：世界奇迹之一，是中华民族的象征，登临可感受壮丽的山川与历史厚重感。 2. **现代都市风貌**： - **北京CBD**：国贸大厦、中央电视台总部大楼（“大裤衩”）等现代建筑群，体现了北京的经济发展活力。 - **奥林匹克公园**：2008年奥运会主会场，鸟巢、水立方等标志性建筑夜晚灯光璀璨。 3. **文化体验**： - **胡同与四合院**：南锣鼓巷、什刹海周边保留了老北京的传统居住形态，可体验胡同骑行或品尝京味小吃。 - **京剧与杂技表演**：老舍茶馆、梨园剧场等地可欣赏中国传统艺术。 4. **自然景观**： - **颐和园**：皇家园林典范，融合江南园林设计与北方山水，适合泛舟昆明湖或漫步长廊。 - **香山公园**：秋季红叶漫山，是赏秋热门目的地。 5. **美食推荐**： - **北京烤鸭**：全聚德、大董等名店提供地道的烤鸭体验。 - **涮羊肉**：东来顺等老字号以铜锅涮肉为特色。 - **小吃街**：牛街、簋街汇聚各类京味及全国风味小吃。 6. **其他亮点**： - **798艺术区**：旧工厂改造的当代艺术聚集地，适合摄影和艺术爱好者。 - **国家博物馆**：馆藏丰富，可系统了解中华文明五千年历史。 北京融合了古老与现代，传统与创新，无论是对历史感兴趣，还是想感受都市繁华，都能在这里找到独特的体验。建议根据季节选择合适的游览时间（如秋季气候宜人），并提前规划行程以避开高峰人流。 Model: deepseek-r1 Usage: Prompt tokens: 31 Total tokens: 522 Completion tokens: 491 总结 DeepSeek 的优势在于其强大的自然语言处理能力和高效的推理性能。通过 Azure AI Foundry 部署，用户可以快速上手并体验其强大的功能。DeepSeek-R1 提供了多种模型版本，满足不同需求，从全量版到经过优化的蒸馏版，用户可以根据实际应用场景选择合适的模型。\n","permalink":"https://wilsonwu.me/zh/blog/2025/deploy-deepseek-r1-on-azure/","summary":"\u003cp\u003eDeepSeek 的爆火让人们再一次看到了 AI 的魅力，而随之而来的不仅不是对算力需求的减少，而是在低成本亲民化人工智能中构建更多 AI 业务场景带来的另一波算力需求。今天我们来通过 Azure AI Foundry（原 Azure AI Studio）快速体验 DeepSeek 的风采。\u003c/p\u003e","title":"玩转大语言模型：基于 Azure AI Foundry 轻松部署使用 DeepSeek-R1"},{"content":"原文地址（英文）：The Istio Service Mesh for People Who Have Stuff to Do\n我最近为开源服务网格项目 Istio 做出了一点贡献。 我的贡献包括为 Istio CLI 命令之一添加一些测试。如果您想查看详细信息， 可以在此处找到 Pull Request。 这不是一个巨大的变化，但却是一次很棒的学习经历。在 Istio 上工作帮助我更深入地了解服务网格。 我很高兴能做出更多贡献。在这篇文章中，我将解释 Istio 是什么、它为什么有用以及它是如何工作的。\nIstio 是什么？ 从本质上讲，Istio 是一种服务网格。服务网格管理微服务之间的通信， 处理诸如路由流量、保护通信和提供可观察性等事务。随着微服务数量的增加， 管理这些交互会变得复杂。Istio 可以自动执行其中许多任务， 因此您可以专注于构建应用程序，而不是管理服务到服务的通信。\n为什么要使用 Istio？ 随着架构变得越来越复杂，您将面临新的挑战。 服务需要以可靠、安全和高效的方式进行通信。Istio 可在三个关键领域帮助您实现此目标：\n管理流量：Istio 让您可以控制服务之间的流量流动方式。 您可以在服务的不同版本之间拆分流量，在部署期间重新路由请求，或设置重试和超时策略。 确保通信安全：Istio 可以轻松启用双向 TLS（mTLS）。 这可确保服务之间的所有通信都经过加密和身份验证，从而阻止未经授权的服务进入。 可观察性：Istio 自动收集指标、日志和链路，让您实时了解服务。这有助于监控、故障排除和性能调整。 这三个领域 - 流量管理、安全性和可观察性是运行健康的微服务架构的关键，Istio 可以轻松处理它们。\n通过 Istio 管理流量 Istio 的主要功能之一是管理服务之间的流量。在微服务设置中， 您可能同时运行多个版本的服务。例如，您可能正在测试支付服务的新版本， 并希望将大部分流量发送到版本 1，但将部分流量路由到版本 2。\n以下是如何使用 Istio 在服务的两个版本之间分割流量的示例：\napiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: payments spec: hosts: - payments.myapp.com http: - route: - destination: host: payments subset: v1 weight: 90 - destination: host: payments subset: v2 weight: 10 在这个示例中：\n90% 的流量发送到 payments 服务版本 1，10% 发送到版本 2。 hosts 字段指定 VirtualService 适用的域 - 在本例中为 payment.myapp.com。 route 部分定义流量如何在服务的两个 subset 之间分配：v1（用于版本 1）和 v2（用于版本 2）。weight 字段控制流量分配。 这对于金丝雀发布非常有用，在全面推出新功能之前，您可以先与一小部分用户一起测试新功能。\nEnvoy 代理和 Sidecar 容器 Istio 的数据平面依赖于 Envoy 代理，这是一个七层代理， 用于管理服务之间的所有流量。网格中的每个服务都有自己的 Sidecar 代理， 它位于服务旁边并管理其所有入站和出站流量。\nEnvoy 允许您应用重试、超时和熔断等流量策略，而无需更改应用程序代码。 它还收集有关流量的详细指标，帮助进行监控和调试。\n由于 Envoy 作为 Sidecar 容器运行，因此它可以执行这些规则并收集数据， 而不会干扰应用程序的逻辑。简而言之，Envoy 充当服务网格中所有通信的“交通警察”。\n可观察性：了解系统中发生的情况 运行包含许多微服务的系统可能会让您难以看清正在发生的事情。 Istio 的内置可观察性功能可帮助您跟踪服务之间所有通信的指标、日志和链路。 这对于监控系统运行状况、发现性能问题和修复错误至关重要。\nIstio 的可观察性工具让您清晰地了解系统的运行情况。您可以尽早发现问题，让您的服务运行得更顺畅。\n安全性：启用 mTLS 和访问控制 管理微服务时，安全性是一大问题。Istio 可以轻松实现双向 TLS（mTLS）， 它可以加密服务之间的所有通信，并确保服务在交换数据之前相互进行身份验证。\nIstio 还允许您设置访问控制策略，以指定允许哪些服务进行通信。 这有助于限制哪些服务可以交互，从而减少系统的攻击面。\n以下是 Istio 策略的示例，该策略仅允许 billing 服务与 payments 服务通信：\napiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: payments-to-billing spec: selector: matchLabels: app: payments rules: - from: - source: principals: [\u0026#34;billing.myapp.com\u0026#34;] 在这个策略中：\nselector 使用标签 app: payments 指定此规则适用于 payments 服务。 rules 部分仅允许由主体 billing.myapp.com 标识的 billing 服务与 payments 进行通信。 其他任何服务都不允许向 payments 发送流量。 此策略限制除 billing 之外的所有服务访问 payments，从而加强微服务的安全性。\n什么是 SPIFFE？ Istio 使用 SPIFFE（面向所有人的安全生产身份框架：Secure Production Identity Framework for Everyone）来管理服务身份。 SPIFFE 提供了一种为服务分配安全、可验证身份的方法。网格中的每个服务都会获得一个 SPIFFE 可验证身份文档（SVID）， 该文档与 mTLS 一起使用以确保安全通信。该身份系统是 Istio 安全模型的基础。\nIstio 中的网络 Networking in microservices can be difficult, especially when it comes to controlling traffic inside and outside the mesh. Istio provides several tools for managing network traffic: 微服务中的网络可能很复杂，特别是在控制网格内部和外部的流量时。Istio 提供了几种用于管理网络流量的工具：\nService Entry：允许外部服务与网格内的服务进行通信，反之亦然。 Virtual Service：定义网格内部流量的路由方式。 Destination Rule：将流量策略（例如负载平衡或 mTLS）应用于服务。 Gateway：管理进出网格的流量。 示例配置：Gateway，Service Entry，Virtual Service 以及 Destination Rule 假设您的网格内有一个 API 服务器，它通过负载均衡器接收来自互联网的流量。 以下是配置 Gateway，Service Entry，Virtual Service 以及 Destination Rule 来处理此流量的方法。\nGateway Configuration apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: api-gateway spec: selector: istio: ingressgateway servers: - port: number: 80 name: http protocol: HTTP hosts: - \u0026#34;api.myapp.com\u0026#34; 这里发生了什么？Gateway 在端口 80 上侦听来自域 api.myapp.com 的 HTTP 流量。选择器字段将此网关连接到 Istio 入口网关，后者处理进入网格的入站流量。\nService Entry 配置 假设您的 API 服务器需要调用外部身份验证服务。您可以按如下方式配置 Service Entry：\napiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: auth-service-entry spec: hosts: - \u0026#34;auth.external-service.com\u0026#34; location: MESH_EXTERNAL ports: - number: 443 name: https protocol: HTTPS resolution: DNS endpoints: - address: 203.0.113.1 这里发生了什么？ Service Entry 告诉 Istio 如何将流量路由到在端口 443（HTTPS）上运行的外部服务（auth.external-service.com）。location: MESH_EXTERNAL 表示此服务存在于 Istio 服务网格之外。endpoints 字段包含外部服务的 IP 地址，允许网格内的 API 服务器发送请求。\nVirtual Service 配置 以下是如何在网格内路由流量：\napiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: api-virtualservice spec: hosts: - \u0026#34;api.myapp.com\u0026#34; gateways: - api-gateway http: - match: - uri: prefix: \u0026#34;/v1\u0026#34; route: - destination: host: api-service subset: stable 这里发生了什么？Virtual Service 定义了流量路由规则。在本例中，通过 api-gateway 到达 api.myapp.com/v1 的流量被路由到网格中的 api-service。subset: stable 指的是 api-service 的特定版本（您可以拥有同一服务的多个版本）。\nDestination Rule 配置 最后，这是应用负载平衡和 mTLS 的 Destination Rule：\napiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: api-destination-rule spec: host: api-service trafficPolicy: loadBalancer: simple: ROUND_ROBIN tls: mode: ISTIO_MUTUAL 这里发生了什么？目标规则将策略应用于路由到 api-service 的流量。它使用循环负载平衡在实例之间均匀分配请求。mTLS 已启用 tls.mode: ISTIO_MUTUAL，确保服务之间的加密通信。\n弹性：使用重试、超时和断路处理故障 在分布式系统中，故障时有发生。服务可能会中断，网络可能会变慢，或者用户可能会遇到延迟。Istio 可通过重试、超时和断路器帮助您处理这些问题。\n重试：自动重试失败的请求以处理临时故障，而不会中断用户体验。 超时：定义服务在放弃并继续之前应等待响应的时间。 断路：如果某个服务出现故障，Istio 可以停止向其发送流量，从而防止可能导致系统其他部分崩溃的级联故障。 以下是如何在 Istio 中配置重试和超时的示例：\napiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: my-service spec: hosts: - my-service http: - route: - destination: host: my-service retries: attempts: 3 perTryTimeout: 2s timeout: 5s 这里发生了什么？如果对 my-service 的请求失败， Istio 将重试该请求最多 3 次。每次重试尝试都有 2 秒的限制。请求允许的总时间为 5 秒。此后，Istio 将停止等待响应。\n对于熔断，你可以使用这样的 Destination Rule：\napiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: my-service spec: host: my-service trafficPolicy: connectionPool: tcp: maxConnections: 100 http: http1MaxPendingRequests: 50 outlierDetection: consecutive5xxErrors: 2 interval: 10s baseEjectionTime: 30s maxEjectionPercent: 50 这里发生了什么？如果 my-service 在 10 秒内连续返回两个 5xx 错误， Istio 将停止向其发送流量。该服务将从负载均衡池中被逐出 30 秒，然后再重新考虑。\n总结 Istio 是一款功能强大的工具，可简化微服务的流量管理、安全性和可观察性。对 Istio 的贡献让我深入了解了它如何帮助解决运行分布式系统所带来的一些复杂挑战。\n如果您正在运行微服务架构或计划扩展，Istio 可以帮助您提高系统弹性并更易于管理。如果您有任何疑问或想了解有关 Istio 的更多信息，请随时联系我们 - 我很乐意分享我所学到的知识。\n","permalink":"https://wilsonwu.me/zh/blog/2024/the-istio-service-mesh-for-people-who-have-stuff-to-do/","summary":"\u003cp\u003e\u003ca href=\"https://www.lucavall.in/blog/the-istio-service-mesh-for-people-who-have-stuff-to-do\"\u003e原文地址（英文）：The Istio Service Mesh for People Who Have Stuff to Do\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e我最近为开源服务网格项目 \u003cstrong\u003eIstio\u003c/strong\u003e 做出了一点贡献。\n我的贡献包括为 Istio CLI 命令之一添加一些测试。如果您想查看详细信息，\n可以在\u003ca href=\"https://github.com/istio/istio/pull/51635\"\u003e此处\u003c/a\u003e找到 Pull Request。\n这不是一个巨大的变化，但却是一次很棒的学习经历。在 Istio 上工作帮助我更深入地了解服务网格。\n我很高兴能做出更多贡献。在这篇文章中，我将解释 Istio 是什么、它为什么有用以及它是如何工作的。\u003c/p\u003e","title":"为繁忙的人们准备的 Istio 服务网格"},{"content":"日常使用 ChatGPT 来帮助我们解决工作和生活中的问题已经变成习惯，但偶尔重度使用官方 GPT-4o 后发现会出现临时限额问题，今天我们就来手把手教大家如何轻松通过 Azure OpenAI 服务构建一个专属 ChatGPT 应用。\n先决条件 在开始之前，请确保您拥有 Azure 国际版订阅服务，如果您还没有 Azure 订阅，可以通过 Pay-as-you-go 轻松开启一个 Azure 订阅：\n注意：开启 Pay-as-you-go Azure 订阅必须绑定国际信用卡，对于新用户还有 200 美金的免费额度，我们完全可以使用这些额度充分使用 Azure 的各项服务。\n下面介绍的内容除了与 GPT-4o 交互产生的费用外，其他都是免费的，日常不适用的话不会产生任何费用，GPT-4o 的费用也可以完全使用免费额度覆盖，所以大家不用担心信用卡被刷爆。\n成功拥有 Azure 订阅后，然我们开始吧！\n步骤 1：创建 Azure AI Studio 服务 在 Azure 订阅主页上方搜索栏中输入 azure ai，找到 Azure AI Studio：\n点击进入 Azure AI Studio 资源页面：\n点击 + New Azure AI Hub 进入创建页面：\n从界面可以看到创建 Azure AI Hub 共分为 7 个步骤，这里按照指引一路 Next 都选择默认即可，其中需要注意的是区域建议选择 East US，由于免费订阅有一些资源使用限制，目前根据经验来看 East US 区域的使用便利性更佳。\n另外是费用问题，可以参考 Azure OpenAI 费用详情页面来查看各个模型的收费情况：\n创建完成后返回 Azure AI Studio 资源页面：\n可以看到我们创建了一个 Azure AI hub 和一个配套的 Azure AI services 资源。\n步骤 2：开始使用 Azure AI Studio 点击打开我们在第一步中创建的 Azure AI Hub 资源，我们会看到详情中醒目的 Launch Azure AI Studio 按钮：\n点击后进入一个全新的 Azure AI Studio 界面：\n这里将是我们主要工作的区域，点击列表中我们刚创建好的 AI Hub，当然在这里我们依然可以创建新的 AI Hub：\n在 AI Hub 中，我们可以看到其相关联的所有资源：\n首先我们需要创建一个 Project，点击左侧菜单中的 All projects，然后选择列表上方的 + New Project 按钮：\n创建 Project 非常简单，只需要输入名称即可：\n创建完成后将自动进入 Project 详情页面：\n现在可以熟悉一下 AI Studio，Hub，Project 的界面，接下来我们来部署大语言模型！\n步骤 3：部署 GPT-4o 大语言模型 依然在 Project 界面，在左侧菜单栏下放找到 Deployments 菜单，点击进入后选择 + Create Deployment 按钮：\n在创建界面中我们看到 Azure 为我们提供了 1000 多重大语言模型可供使用，当然，它们要求的计算资源也各不相同，有兴趣可以逐个尝试（注意信用卡不要刷爆），这里我们选择 GPT-4o 模型：\n选定后进入部署页面，按需修改相关选项，确认后点击 Deploy 进行部署：\n部署成功后我们可以看到 GPT-4o 已经出现在我们的部署列表中：\n步骤 4：部署专属 ChatGPT Web 应用 确认模型部署正常后，我们进入项目左侧菜单中的 Chat，并选择我们刚才创建的 GPT-4o 模型：\n在 Chat 界面我们可以选择大语言模型，简单测试问答功能等，确认一些无误后，我们就可以开始创建专属 Web App 了，选择顶部菜单中的 Deploy to a web app：\n在部署配置中输入名称，选择 Web App 想要部署的订阅，区域等信息，如果希望保存 ChatGPT 的聊天历史，可以开启最下方的选项，但会额外创建 CosmosDB 数据库服务，会产生额外费用：\n确认部署后等待完成即可，如果部署正确，那么在 Chat 界面中会有一个 Launch 按钮：\n点击后会打开您的专属 ChatGPT Web 界面：\n注意：默认情况下我们部署的 Web App 是基于您订阅中的账户（IAM）来做用户验证的，如果您自己使用可以保持这个默认行为。\n如果您有技术能力且希望对这个专属 ChatGPT Web App 做定制开发，可以前往 Git Repo 查看所有实现细节，可以按需修改为适合自己使用的版本。\n总结 通过 Azure AI 相关的服务我们可以实现很多人工智能相关的实用业务场景，让我们对 AI 和 大语言模型的了解不仅只停留在铺天盖地的消息和无尽的想象，也可以亲身接触实践！\n","permalink":"https://wilsonwu.me/zh/blog/2024/build-own-chatgpt-on-azure/","summary":"\u003cp\u003e日常使用 ChatGPT 来帮助我们解决工作和生活中的问题已经变成习惯，但偶尔重度使用官方 GPT-4o 后发现会出现临时限额问题，今天我们就来手把手教大家如何轻松通过 Azure OpenAI 服务构建一个专属 ChatGPT 应用。\u003c/p\u003e","title":"玩转大语言模型：无需任何代码通过 Azure OpenAI 服务构建个人专属 ChatGPT"},{"content":"Azure 是由 Microsoft 提供的云计算平台和服务。它提供了一系列的基础设施即服务（Infrastructure as a Service，IaaS）、平台即服务（Platform as a Service，PaaS）和软件即服务（Software as a Service，SaaS）解决方案，用于构建、部署和管理各种类型的应用程序和服务。\n总览 Azure 提供了广泛的功能和服务，包括虚拟机、存储、数据库、人工智能、机器学习、区块链、物联网、容器、Serverless 计算等。它还提供了全球范围的数据中心网络，使用户能够在全球范围内部署和扩展应用程序。\nAzure 拥有数百项自身提供或合作伙伴提供的服务，截止至 2024 年 6 月 19 日，其拥有 19 个大分类，共计 413 项服务：\n使用 Azure，您可以轻松地创建和管理虚拟机，存储和管理数据，构建和部署 Web 应用程序，进行大规模数据分析，实现自动化和监控等。Azure 还提供了强大的安全性和合规性功能，以确保您的数据和应用程序的安全性。\n无论您是个人开发者、创业公司还是大型企业，Azure 都提供了灵活的定价模型，以满足不同规模和需求的项目。您可以根据实际使用情况付费，避免了高额的固定成本。\n以下将总体介绍 19 个大分类的服务概述。\nAI + Machine Learning Azure 提供了强大的 AI + 机器学习功能，使用户能够构建和部署智能应用程序和解决方案。通过 Azure 的 AI + 机器学习服务，您可以利用先进的机器学习算法和模型来处理和分析大规模数据，从而获得有价值的洞察和预测能力。\n通过这些 AI + 机器学习服务，您可以实现各种应用场景，如智能推荐、欺诈检测、情感分析、语音识别和自动化决策等。无论您是初学者还是专业人士，Azure 的 AI + 机器学习功能都能帮助您轻松构建和部署智能应用程序。\nAnalytics Azure Analytics 是 Azure 平台下的一个重要分类，提供了一系列强大的数据分析和洞察服务。这些服务可以帮助用户从海量数据中提取有价值的信息，并进行深入的数据分析和可视化展示。\nAzure Analytics 提供了丰富的数据分析和洞察服务，帮助用户从海量数据中提取有价值的信息，并进行深入的数据分析和可视化展示。无论是处理结构化数据还是非结构化数据，无论是批处理还是实时处理，Azure Analytics 都能满足用户的各种数据分析需求。\nCompute Compute 计算服务是 Azure 平台下的一个庞大分类，提供了各种计算资源和服务，用于构建、部署和管理应用程序和服务。\nAzure Compute 提供了多种计算选项，包括虚拟机、容器、Serverless 计算和批处理等。您可以根据应用程序的需求选择适合的计算资源，并根据实际使用情况进行弹性扩展和收缩。\nContainers Containers 容器分类，提供了一系列容器相关的服务和工具，用于构建、部署和管理容器化应用程序。\nAzure Containers 提供了强大的容器编排和管理功能，使用户能够轻松地创建、部署和扩展容器化应用程序。通过 Azure 的容器服务，您可以使用 Docker 容器技术来打包应用程序及其依赖项，并在云端或本地环境中运行。\nAzure Containers 提供了多种容器服务选项，包括 Azure Kubernetes Service (AKS)、Azure Container Instances (ACI) 和 Azure Container Registry (ACR) 等。这些服务提供了灵活的容器部署和管理方式，适用于不同规模和需求的项目。\n使用 Azure Containers，您可以实现容器化应用程序的高可用性、弹性扩展和自动化管理。无论是单个容器应用程序还是复杂的微服务架构，Azure Containers 都能提供可靠的容器化解决方案。\nDatabases Databases 数据库服务提供了多种数据库服务和解决方案，用于存储和管理结构化数据。\nAzure Databases 提供了多种数据库选项，包括关系型数据库、NoSQL 数据库和缓存服务等。您可以根据应用程序的需求选择适合的数据库类型，并根据实际使用情况进行弹性扩展和收缩。\n无论您是需要传统的关系型数据库、灵活的 NoSQL 数据库还是高性能的缓存服务，Azure Databases 都能提供适合您的数据库服务和解决方案。 通过 Azure Databases，您可以轻松地创建和管理数据库实例，进行数据的读写和查询，实现高可用性和数据的持久性。Azure Databases 还提供了强大的安全性和合规性功能，以确保您的数据的安全性和隐私保护。\nDevOps 在 DevOps 中提供了一系列强大的工具和服务，用于实现持续交付和持续集成的 DevOps 实践。\nDevOps 中提供了全面的 DevOps 解决方案，包括混沌测试，持续集成、持续交付、测试自动化、应用程序监控和故障排除等。\nGeneral Azure General 分类提供了一系列通用的云服务和工具，是使用 Azure 整个平台的最基础功能。\n通过 Azure General，您可以找到一系列帮助您快速熟悉和使用 Azure 的工具，并且有效的管理您的各类资源以及计划您的支出，如果您对云计算服务不是特别熟悉或刚刚开始从事相关工作，在 General 中获取帮助是您的很好选择。\nHybrid + Multicloud Azure Hybrid + Multicloud 分类提供了一系列强大的混合云和多云解决方案，帮助用户在不同的云环境中构建、部署和管理应用程序和服务。\nAzure Hybrid + Multicloud 提供了多种混合云和多云服务选项，包括混合云连接、数据集成、应用程序迁移和多云管理等。这些服务使用户能够在不同的云环境中灵活地部署和管理应用程序，实现资源的共享和协同工作。\n通过 Azure Hybrid + Multicloud，您可以实现混合云环境中的数据集成和应用程序迁移，实现跨云平台的协同工作和资源的共享。Azure Hybrid + Multicloud 还提供了强大的多云管理功能，帮助用户统一管理和监控不同云环境中的资源和服务。\nIdentity Azure Identity 分类提供了一系列身份验证和访问管理服务，用于保护应用程序和数据的安全性。\nAzure Identity 提供了多种身份验证和访问管理服务选项，包括 Azure Active Directory (Azure AD)、Azure AD B2C 和 Azure AD Domain Services 等。这些服务提供了全面的身份验证和访问管理解决方案，帮助用户建立安全的身份验证和访问控制机制。\n通过 Azure Identity，您可以实现用户身份验证和访问管理，保护应用程序和数据的机密性和完整性。Azure Identity 还提供了强大的身份验证和访问管理功能，如多因素身份验证、单一登录、角色基于访问控制和身份联合等，帮助用户确保只有授权的用户能够访问应用程序和数据。\nIntegration Azure Integration 提供了一系列强大的集成服务和工具，用于实现应用程序和系统之间的无缝集成和数据交换。\nAzure Integration 提供了多种集成服务选项，包括消息队列、事件网格、逻辑应用程序和数据工厂等。这些服务使用户能够轻松地构建、部署和管理应用程序和系统之间的集成解决方案。\n通过 Azure Integration，您可以实现应用程序和系统之间的实时数据交换和协同工作。Azure Integration 还提供了强大的消息传递和事件处理功能，帮助用户实现高效的数据流和业务流程。\nInternet of Things Azure Internet of Things (IoT) 分类提供了一系列强大的物联网服务和解决方案，用于连接、监控和管理物理设备和传感器。\nAzure IoT 提供了多种物联网服务选项，包括设备连接、数据采集、实时分析和远程监控等。这些服务使用户能够轻松地构建、部署和管理物联网解决方案，实现设备之间的互联和数据的实时处理。\n通过 Azure IoT，您可以实现设备的连接和管理，监控设备的状态和性能，并进行实时数据分析和预测。Azure IoT 还提供了强大的远程监控和控制功能，帮助用户实现远程设备管理和操作。\n使用 Azure IoT，您可以构建各种物联网应用场景，如智能家居、智能工厂、智能农业和智能城市等。无论是个人用户还是企业客户，Azure IoT 都能提供可靠的物联网解决方案，帮助您实现设备的互联和数据的实时处理。\nManagement and Governance Azure Management and Governance 分类提供了一系列强大的管理和治理服务，帮助用户有效地管理和监控 Azure 资源和应用程序。\nAzure Management and Governance 提供了多种管理和治理服务选项，包括资源管理、访问控制、成本管理、策略和合规性等。这些服务使用户能够轻松地管理和监控 Azure 资源和应用程序，实现资源的优化和合规性的保证。\n通过 Azure Management and Governance，您可以实现资源的集中管理和监控，优化资源的使用和成本的控制。Azure Management and Governance 还提供了强大的访问控制和权限管理功能，帮助用户确保只有授权的用户能够访问和操作资源。\n使用 Azure Management and Governance，您可以定义和实施策略和合规性规则，确保应用程序和数据的合规性和安全性。Azure Management and Governance 还提供了丰富的监控和报告功能，帮助用户实时了解资源的状态和性能。\nMigration Azure Migration 分类提供了一系列强大的迁移服务和工具，帮助用户将现有应用程序和数据迁移到 Azure 平台上。\nAzure Migration 提供了多种迁移服务选项，包括虚拟机迁移、数据库迁移、应用程序迁移和数据迁移等。这些服务使用户能够轻松地将现有的应用程序和数据迁移到 Azure 平台上，实现平滑的迁移过程和无缝的应用程序转换。\n通过 Azure Migration，您可以实现虚拟机的迁移和转换，将现有的虚拟机环境迁移到 Azure 虚拟机中。Azure Migration 还提供了强大的数据库迁移功能，帮助用户将现有的数据库迁移到 Azure 数据库服务中，实现数据的平滑迁移和无缝连接。\n使用 Azure Migration，您可以轻松地将现有的应用程序迁移到 Azure 平台上，实现应用程序的云化和弹性扩展。Azure Migration 还提供了强大的数据迁移功能，帮助用户将现有的数据迁移到 Azure 存储服务中，实现数据的安全存储和高可用性。\nMixed Reality Azure Mixed Reality 分类提供了一系列强大的混合现实服务和解决方案，用于构建、部署和管理混合现实应用程序和体验。\nAzure Mixed Reality 提供了多种混合现实服务选项，包括混合现实开发工具、混合现实内容管理和分发、混合现实设备管理和监控等。这些服务使用户能够轻松地构建、部署和管理混合现实应用程序和体验，实现沉浸式的交互和创新的用户体验。\n通过 Azure Mixed Reality，您可以利用先进的混合现实技术和工具来创建逼真的虚拟场景和交互体验。Azure Mixed Reality 还提供了强大的混合现实内容管理和分发功能，帮助用户将混合现实应用程序和体验推送到不同的设备和平台上。\n使用 Azure Mixed Reality，您可以构建各种混合现实应用场景，如虚拟培训、虚拟会议、虚拟展览和虚拟旅游等。无论是个人用户还是企业客户，Azure Mixed Reality 都能提供创新的混合现实解决方案，帮助您实现沉浸式的交互和创新的用户体验。\nMonitor Azure Monitor 分类提供了一系列强大的监控和日志管理服务，帮助用户实时监测和分析应用程序和基础设施的性能和健康状况。\nAzure Monitor 提供了多种监控和日志管理服务选项，包括应用程序性能监视、基础设施监视、日志分析和警报管理等。这些服务使用户能够实时监测和分析应用程序和基础设施的性能和健康状况，及时发现和解决潜在的问题。\n通过 Azure Monitor，您可以实时监测应用程序的性能和可用性，了解应用程序的负载和响应时间。Azure Monitor 还提供了强大的基础设施监视功能，帮助用户监控服务器、虚拟机和容器等基础设施的性能和资源使用情况。\n使用 Azure Monitor，您可以收集和分析应用程序和基础设施的日志数据，了解应用程序的运行状况和事件发生情况。Azure Monitor 还提供了灵活的警报管理功能，帮助用户设置和管理警报规则，及时通知和响应关键事件。\nNetworking Azure Networking 提供了一系列强大的网络服务和解决方案，用于构建、部署和管理网络基础设施和连接。\nAzure Networking 提供了多种网络服务选项，包括虚拟网络、负载均衡、应用网关、VPN 网关和 ExpressRoute 等。这些服务使用户能够轻松地构建和管理灵活、可靠的网络架构，实现应用程序和服务之间的高速连接和通信。\n通过 Azure Networking，您可以创建和管理虚拟网络，定义网络拓扑和子网划分，实现应用程序和服务之间的隔离和安全性。Azure Networking 还提供了强大的负载均衡和应用网关功能，帮助用户实现应用程序的高可用性和性能优化。\n使用 Azure Networking，您可以轻松地创建和管理 VPN 网关和 ExpressRoute 连接，实现安全、可靠的远程连接和混合云环境的扩展。Azure Networking 还提供了强大的网络安全和防火墙功能，帮助用户保护网络和应用程序的安全性和隐私保护。\nSecurity 在 Azure Security 分类中提供了一系列强大的安全性和合规性服务。这些服务可以帮助用户保护应用程序和数据的安全性，防止恶意攻击和数据泄露。\nAzure Security 提供了多种安全性和合规性服务选项，包括身份验证和访问管理、数据保护和加密、威胁检测和防御、安全监控和审计等。这些服务提供了全面的安全性解决方案，帮助用户建立安全的云环境和应用程序。\n通过 Azure Security，您可以实现身份验证和访问管理，保护应用程序和数据的机密性和完整性。Azure Security 还提供了强大的威胁检测和防御功能，帮助用户及时发现和应对安全威胁。\nStorage Azure Storage 提供了可扩展的云存储解决方案，用于存储和管理大规模的结构化和非结构化数据。\nAzure Storage 提供了多种存储服务选项，包括 Blob 存储、文件存储、表存储和队列存储等。这些服务使用户能够轻松地存储和访问各种类型的数据，实现数据的持久性和可靠性。\n通过 Azure Storage，您可以存储和管理大规模的结构化和非结构化数据，如图像、视频、文档和日志等。Azure Storage 还提供了强大的数据复制和备份功能，帮助用户保护数据的安全性和可用性。\n使用 Azure Storage，您可以实现数据的高可用性和弹性扩展，根据实际使用情况进行存储容量的调整。Azure Storage 还提供了灵活的访问控制和权限管理功能，帮助用户控制数据的访问和使用权限。\nWeb + Mobile Azure Web + Mobile 分类提供了一系列强大的云服务和工具，用于构建、部署和管理 Web 和移动应用程序。\nAzure Web + Mobile 提供了多种服务选项，包括 Web 应用程序、移动应用程序、API 管理和通知服务等。这些服务使用户能够轻松地构建和部署高性能、可扩展的 Web 和移动应用程序。\n使用 Azure Web + Mobile，您可以构建各种 Web 和移动应用场景，如电子商务网站、社交媒体应用、企业门户和移动客户端等。无论是个人开发者还是企业客户，Azure Web + Mobile 都能提供可靠的云服务和工具，帮助您构建和部署高性能、可扩展的 Web 和移动应用程序。\n总结 本文介绍了 Azure 101 系列课程中的 Microsoft Azure 概览。Azure 是由 Microsoft 提供的云计算平台和服务，提供了广泛的功能和服务，包括虚拟机、存储、数据库、人工智能、机器学习、区块链、物联网、容器、Serverless 计算等。Azure 还提供了全球范围的数据中心网络，使用户能够在全球范围内部署和扩展应用程序。本文还介绍了 Azure 19 个大分类的服务概述，包括 AI + 机器学习、数据分析、计算、容器、数据库、DevOps、通用云服务、混合云和多云、身份验证和访问管理、集成、物联网、管理和治理、迁移、混合现实、监控、网络、安全、存储、Web 和移动应用程序。这些服务和解决方案可以帮助用户构建、部署和管理各种类型的应用程序和服务，并提供了强大的安全性和合规性功能。无论是个人开发者、创业公司还是大型企业，Azure 都提供了灵活的定价模型，以满足不同规模和需求的项目。\n","permalink":"https://wilsonwu.me/zh/blog/azure-101/overview/","summary":"\u003cp\u003eAzure 是由 Microsoft 提供的云计算平台和服务。它提供了一系列的基础设施即服务（Infrastructure as a Service，IaaS）、平台即服务（Platform as a Service，PaaS）和软件即服务（Software as a Service，SaaS）解决方案，用于构建、部署和管理各种类型的应用程序和服务。\u003c/p\u003e","title":"Azure 101 系列课程：Microsoft Azure 概览"},{"content":"Wilson Wu Wilson Wu 是一位拥有近 20 年云原生企业解决方案及 AI 商业应用经验的软件工程领导者，专长于容器、微服务、DevOps、人工智能（AI）和研发团队管理。\n作为多个顶级开源项目的维护者和参与者，包括 Kubernetes、Istio、Contour、ArgoCD 及 vLLM；曾任微软技术布道师和飞利浦软件架构师。目前专注于研发工程管理，带领 AI 机器学习与大语言模型团队，专长于 AI 基础设施 / AIOps、LLM 推理和 AI 网关架构。\n联系方式 LinkedIn: linkedin.com/in/iwilsonwu GitHub: github.com/wilsonwu 个人网站: wilsonwu.me 关注方向 云原生企业解决方案与 AI 商业应用 容器、微服务、DevOps 与研发团队管理 AI 基础设施 / AIOps、LLM 推理与 AI 网关架构 Kubernetes、Istio、Contour、ArgoCD、vLLM 等开源项目 ","permalink":"https://wilsonwu.me/zh/about/","summary":"\u003ch2 id=\"wilson-wu\"\u003eWilson Wu\u003c/h2\u003e\n\u003cp\u003eWilson Wu 是一位拥有近 20 年云原生企业解决方案及 AI 商业应用经验的软件工程领导者，专长于容器、微服务、DevOps、人工智能（AI）和研发团队管理。\u003c/p\u003e","title":"关于作者"}]