VS Code Copilot NES:xtab 单池 Token 预算级联(Global Budget Cascade)设计与实现解析
本文围绕 VS Code 仓库中 Copilot 扩展(NES/xtab 内联编辑)的 globalBudgetCascade.md 规范文档展开,完整解读"单池 Token 预算级联"这一可选(opt-in)机制:它如何用 totalTokens / order / shares 三个旋钮把多个 prompt 片段的预算打通、让未用预算向后续片段捐赠、并保证当前文件最后裁剪时能复用级联剩余;读完你将掌握该机制的算法细节、校验规则、实验配置接入方式(chat.advanced.inlineEdits.xtabProvider.globalBudget),以及源码中 runGlobalBudgetCascade 与 GlobalBudgetOptions 的实际实现依据。
动机:为什么要打通各片段的独立预算上限
在默认的 legacy 路径中,xtab 的每个 prompt 片段都有各自独立的 maxTokens 上限。这些上限是按"最坏情况下组合 prompt"保守配置的,这意味着即使其他片段未使用或几乎为空,某个片段也会被自己的上限截断。级联(cascade)机制让前面片段未使用的预算"捐赠"给后面的片段,其设计原型来自 completions-core 中的 CascadingPromptFactory。
该机制是 opt-in(可选启用) 的:当 PromptOptions.globalBudget 为 undefined 时,getUserPrompt 走 legacy 路径,各片段仍按各自的上限截断,输出与旧路径逐字节一致(生产环境默认值)。源码中这一分支位于 promptCrafting.ts:opts.globalBudget !== undefined 时调用级联(或复用调用方预计算的 precomputedCascade),否则回落到 getRecentCodeSnippets 的旧逻辑。
作用范围:哪些片段参与级联
由级联渲染的片段(GlobalBudgetPart)
GlobalBudgetPart 即 order 中列出、由级联循环依次渲染的片段,只有四个:
recentlyViewedDocuments(最近查看文档)languageContext(语言上下文)neighborFiles(邻居文件)diffHistory(编辑 diff 历史)
类型定义见 xtabPromptOptions.ts。
参与分配但不参与渲染的片段:currentFile
currentFile 参与 份额分配(shares),但 不在渲染顺序(order)中。它在级联运行完之后 最后 被裁剪——围绕光标/编辑窗口裁剪,尺寸为其池内份额 加上 级联未用完的剩余预算。具体地,级联以 0 作为种子先行运行(即当前文件不捐赠任何东西),然后当前文件被裁剪到 currentFileBudget + cascadeFinalSurplus。由于预算只单向流动(级联 → 当前文件,从不反向),当前文件"复用"级联的剩余,从而被裁掉得更少。
能拿到 shares 条目的片段集合为 GlobalBudgetSharePart = GlobalBudgetPart | 'currentFile'。
| 片段 | 与池的关系 |
|---|---|
currentFile |
从池中定量,但在级联 外部且之后 被裁剪:裁剪上限为 floor(totalTokens * shares.currentFile) + cascadeFinalSurplus,围绕光标/编辑窗口由 createTaggedCurrentFileContentUsingPagedClipping 执行(裁剪上限 currentFile.maxTokens 会在 xtabProvider 中被该池预算覆盖)。它 不在 order 中,级联循环从不渲染它;它吸收级联的剩余而不是向其中捐赠。当 globalBudget 为 undefined 时回落到自己的 currentFile.maxTokens 上限。 |
lintOptions |
可选、独立格式化、体量小。完全排除——没有 shares 条目,保留自己的独立形状。 |
当前文件裁剪函数 createTaggedCurrentFileContentUsingPagedClipping 的实现见 promptCrafting.ts:它基于"分页裁剪"围绕 areaAroundEditWindowLinesRange 保留范围,超预算时返回 outOfBudget 错误。
输入参数
| 输入 | 说明 |
|---|---|
globalBudget.totalTokens |
单池大小。默认 7500。通过实验 JSON 字符串 chat.advanced.inlineEdits.xtabProvider.globalBudget 的 totalTokens 字段设置。 |
globalBudget.order |
被渲染 片段的有序列表。排在前面的片段先获得预算,其剩余流向后面的片段。currentFile 不出现在这里。 |
globalBudget.shares |
Record<GlobalBudgetSharePart, number>——每个被渲染片段 以及 currentFile 各占 totalTokens 的一个比例。order 各片段加 currentFile 的总和必须等于 1 ± 1e-3。 |
默认值
GlobalBudgetOptions.DEFAULT_ORDER(xtabPromptOptions.ts):
['languageContext', 'recentlyViewedDocuments', 'neighborFiles', 'diffHistory']
GlobalBudgetOptions.DEFAULT_SHARES(与现有逐片段上限"体量中性",即不增不减):
| 片段 | 份额 | totalTokens = 7500 时的基础预算 |
|---|---|---|
currentFile |
1500/7500 | 1500 |
recentlyViewedDocuments |
2000/7500 | 2000 |
languageContext |
2000/7500 | 2000 |
neighborFiles |
1000/7500 | 1000 |
diffHistory |
1000/7500 | 1000 |
GlobalBudgetOptions.DEFAULT_TOTAL_TOKENS = 7500。
这些份额精确复现了现有逐片段上限:currentFile.maxTokens 1500、recentlyViewedDocuments 2000、languageContext 2000、neighborFiles 1000、diffHistory 1000。池总量(7500)是这些上限之和,因此启用默认的 global budget 既不放大也不缩小任何被渲染片段的基础分配。这与源码中 DEFAULT_OPTIONS 的各片段 maxTokens 一致(xtabPromptOptions.ts 中 currentFile.maxTokens: 1500、recentlyViewedDocuments.maxTokens: 2000、languageContext.maxTokens: 2000、neighborFiles.maxTokens: 1000、diffHistory.maxTokens: 1000)。
默认顺序把 languageContext 放在第一位,因为它经常被禁用或为空,其份额会捐赠给紧随其后、几乎总是启用的 recentlyViewedDocuments。级联未用完的部分进入 finalSurplus,最终交给当前文件的裁剪复用。
GlobalBudgetOptions.currentFileBudget(gb) 返回 floor(totalTokens * shares.currentFile)——这是当前文件 基础 份额的唯一事实来源(xtabPromptOptions.ts)。当前文件实际的裁剪上限 = 该基础值 + 级联的 finalSurplus。
算法
surplus ← 0 // 当前文件从不捐赠,所以级联始终从 0 开始
for part in order:
budget ← max(0, floor(surplus + totalTokens * shares[part]))
consumed ← runSubBuilder(part, maxTokens: budget) // 子构建器 ≤ budget
surplus ← max(0, budget - consumed) // 只流向下一个片段
// 循环结束时的 surplus 作为 finalSurplus 返回,加到当前文件的裁剪上限上
该伪代码与源码实现 runGlobalBudgetCascade 一一对应:循环体中 budget = Math.max(0, Math.floor(surplus + globalBudget.totalTokens * share))(promptCrafting.ts#L280-L283),每轮结束后 surplus = Math.max(0, budget - tokensConsumed)(#L326),最终 finalSurplus: surplus 随 CascadeResult 返回(#L331-L343)。
要点(源码级补充):
-
级联 始终以
0作为种子。当前文件在级联之后被裁剪,只会 接收 剩余而从不捐赠。 -
级联为每个片段调用现有子构建器,只覆盖其
maxTokens(其余选项从opts继承):recentlyViewedDocuments→buildCodeSnippetsUsingPagedClippinglanguageContext→appendLanguageContextSnippetsneighborFiles→appendNeighborFileSnippetsdiffHistory→getEditDiffHistory
这些子构建器分别定义在 recentFilesForPrompt.ts 与 diffHistoryForPrompt.ts 中。在级联的
switch分支里(promptCrafting.ts#L285-L320),recentlyViewedDocuments通过{ ...opts, recentlyViewedDocuments: { ...opts.recentlyViewedDocuments, maxTokens: budget } }覆盖上限;diffHistory通过{ ...opts.diffHistory, maxTokens: budget }覆盖;neighborFiles仅在enabled && neighborSnippets.length > 0时运行。 -
每个子构建器行为不变。
-
每个子构建器返回
tokensConsumed,使用的是它 做预算决策时所用的同一套内部计量(recently-viewed 用分页裁剪行成本、appender 用原始片段成本、diff 历史用逐条 diff 成本)。级联用该上报值计算surplus,使级联与每个片段实际扣减预算的方式保持一致。 -
surplus在级联片段之间 只向前流动。最后一个片段未用完的 token 不会丢失:它构成finalSurplus,由当前文件的裁剪复用。 -
防御性不变量:每个子构建器必须上报
0 ≤ tokensConsumed ≤ budget。级联对每个片段softAssert这一点(不会 静默地把超支截断回预算内,那会掩盖 bug),从而当前文件裁剪所依赖的"守恒论证"成立。对应源码在 promptCrafting.ts#L321-L325:softAssert(tokensConsumed >= 0 && tokensConsumed <= budget, ...)。
文档跟踪(docsInPrompt)
级联用当前活动文档为 docsInPrompt 播种(promptCrafting.ts#L271-L272),recentlyViewedDocuments 步骤会把其收录的文档加入集合;neighborFiles 读取该集合以避免与已有文档重复,随后 appendNeighborFileSnippets 也会把每个收录的邻居文档加入集合。正是这个依赖关系,使得 GlobalBudgetOptions.validate 拒绝 neighborFiles 排在 recentlyViewedDocuments 之前的顺序。
累积的 docsInPrompt 随后传给 getEditDiffHistory,因此当开启 diffHistory.onlyForDocsInPrompt 时,哪些文档在集合中的变化会影响 diff 选择(diff 步骤只输出文档位于 docsInPrompt 中的条目)。
输出结构
级联的输出镜像 legacy getRecentCodeSnippets 的形状,使 getUserPrompt 的其余部分保持不变,外加当前文件裁剪所用的 finalSurplus:
{
codeSnippets, // recentlyViewed + langCtx + neighbor,用 "\n\n" 连接
documents, // docsInPrompt
neighborSnippetsResult, // 不变遥测载荷
editDiffHistory,
nDiffsInPrompt,
subsections, // recentlyViewed/langCtx/neighbor 的字符串,用于逐小节 token 上报
finalSurplus, // 循环结束时的剩余,被当前文件裁剪复用
}
对应类型 CascadeResult 见 promptCrafting.ts#L220-L234,其 finalSurplus 字段的注释明确说明:"provider adds it to the current file's clip budget (currentFileBudget + finalSurplus) so the current file, which is clipped last, reuses whatever the cascade left unused."
保证与边界
设 totalTokens = T、份额为 s_i(校验保证它们有限且非负,见 校验):
- 逐片段下限:索引
i处的片段无论如何总能拿到至少floor(T * s_i)个 token,与前面片段的行为无关。 - 逐片段上限:索引
i处的片段最多拿到逐步取整的部分和floor(…floor(floor(T * s_0) + T * s_1) + … + T * s_i)——即它自己的份额加上所有前面片段的捐赠,且每一步都取floor。注意这通常小于floor(T * (s_0 + … + s_i))。 - 当前文件下限:当前文件总能拿到至少
floor(T * shares.currentFile)(其基础份额),且finalSurplus ≥ 0是额外加成的。 - 池上限:级联管理的 token 总量 ≤ 最后一个片段的上限(恒 ≤
T - floor(T * shares.currentFile))。当前文件随后取自己的基础份额加级联的finalSurplus,因此受预算管理的片段合计消耗 ≤T;lint 与脚手架(scaffolding)位于预算之外。 - 级联片段之间无回流:最后一个级联片段的剩余不会重新分配给更早的片段——它作为
finalSurplus流向当前文件。 - 无片段内公平性:单个片段内的大条目可以吃完该片段的整份分配;级联只解决跨片段捐赠问题。
校验
GlobalBudgetOptions.validate 在 每次 级联调用开始时运行(并且在当前文件裁剪之前于 xtabProvider 中再运行一次),配置错误时直接抛错。因为配置是运行时可调的(实验驱动),"大声失败" 优于静默的少分配/超分配。实现见 xtabPromptOptions.ts#L213-L250:
| 规则 | 错误信息 |
|---|---|
totalTokens 有限且 >= 0 |
globalBudget.totalTokens must be a finite, non-negative number, got X |
order 无重复片段 |
globalBudget.order contains duplicate part 'X' |
order 中每个片段都有数值型 shares[part] |
globalBudget.shares is missing entry for 'X' |
shares.currentFile 是数值 |
globalBudget.shares is missing entry for 'currentFile' |
每个份额(order 片段 和 currentFile)有限且 >= 0 |
globalBudget.shares['X'] must be a finite, non-negative number, got Y |
若两者都在,recentlyViewedDocuments 必须先于 neighborFiles |
globalBudget.order must place 'recentlyViewedDocuments' before 'neighborFiles' |
order 各片段份额加 shares.currentFile 之和 ≈ 1(epsilon 1e-3) |
globalBudget.shares across order must sum to ~1, got ${sharesSum} |
为什么非负规则重要:负份额仍可能通过"和 ≈ 1"检查(例如一个片段
-0.25、另一个1.0)。分配时负份额片段会被截断到0预算,但它仍计入份额总和,于是其他片段超配超过池总量——这会让当前文件的finalSurplus超过真实剩余。在入口处拒绝负数/非有限份额,才能保证Σ consumed ≤ totalTokens可证明。
单元测试对这些抛错路径均有覆盖,见 promptCrafting.spec.ts#L1044-L1105(顺序错误、重复片段、份额和不等于 1、缺 diffHistory、缺 currentFile 等用例)。
配置接入:单个实验驱动的 JSON 字符串
级联由 单个实验驱动的 JSON 字符串 配置,ConfigKey.TeamInternal.InlineEditsXtabGlobalBudget,建模自 modelConfigurationString。该配置键定义在 configurationService.ts#L1036:
export const InlineEditsXtabGlobalBudget = defineTeamInternalSetting<string | undefined>(
'chat.advanced.inlineEdits.xtabProvider.globalBudget', ConfigType.ExperimentBased, undefined);
xtabProvider.ts 的 getGlobalBudget() 读取它并用 GlobalBudgetOptions.fromConfigString 解析(xtabProvider.ts#L1599-L1620):
private getGlobalBudget(): GlobalBudgetOptions | undefined {
const configString = configService.getExperimentBasedConfig(InlineEditsXtabGlobalBudget, expService);
if (!configString) {
return undefined; // 未设置/空 → 禁用,与生产路径完全一致
}
const result = GlobalBudgetOptions.fromConfigString(configString);
if (result.isError()) {
telemetryService.sendMSFTTelemetryEvent('incorrectNesGlobalBudgetConfig', { errorMessage: result.err, configValue: configString });
return undefined; // 坏配置 → 禁用,绝不崩溃
}
return result.val;
}
该 JSON 值把三个预算旋钮定义在一起——totalTokens、order、shares——且 每个字段都是可选的,省略的字段回落到 DEFAULT_TOTAL_TOKENS / DEFAULT_ORDER / DEFAULT_SHARES(解析逻辑见 fromConfigString),因此:
undefined/ 未设置 /""→ global budget 禁用(生产默认,逐字节一致的 legacy 路径);{}→ 启用,使用体量中性的默认值;{"totalTokens":6000}→ 启用,仅覆盖池大小;{"totalTokens":12000,"order":[…],"shares":{…}}→ 完全自定义。
fromConfigString 先做结构化校验(通过 GlobalBudgetOptions.VALIDATOR,xtabPromptOptions.ts#L259-L269,其中 shares 一旦出现就必须列出 全部 片段——渲染片段加 currentFile,部分 shares 对象会被拒绝,保证池被完全分配),再合并到默认值上,最后运行语义 GlobalBudgetOptions.validate;任何解析、结构或语义失败都返回 Result.error 并禁用预算(不抛异常,改由遥测上报)。
启用后,xtabProvider 先汇集级联输入、运行级联,再以上限 currentFileBudget(globalBudget) + cascade.finalSurplus 裁剪当前文件(替代独立的 currentFile.maxTokens)。已运行的级联以 precomputedCascade 传入 getUserPrompt,保证它只渲染 一次。getUserPrompt 只在 globalBudget 已设置时才认 precomputedCascade,因此级联恰好运行一次,渲染出的片段与定量完全一致(promptCrafting.ts#L82-L92)。
实验控制的设置:
| 设置 | 默认值 | 用途 |
|---|---|---|
chat.advanced.inlineEdits.xtabProvider.globalBudget |
undefined |
定义 totalTokens、order、shares 的 JSON 字符串;未设置/空字符串即禁用预算 |
迁移说明:旧的
globalBudget.enabled(布尔)与globalBudget.totalTokens(数值)设置已被这 单个 JSON 字符串 取代。任何钉住旧键的在线实验分组必须迁移:enabled:true+totalTokens:N变成 JSON{"totalTokens":N};裸的enabled:true变成{}。由于totalTokens同时给currentFile供资,钉住旧总量(如6000或8000)的分组应迁移到{}(或7500)以保持体量中性。
演算示例
以下示例均使用 DEFAULT_ORDER、DEFAULT_SHARES 与 totalTokens = 7500。
基础分配:floor(7500 * share) 逐片段 →
| 片段 | 基础分配 |
|---|---|
languageContext |
2000 |
recentlyViewedDocuments |
2000 |
neighborFiles |
1000 |
diffHistory |
1000 |
currentFile(最后裁剪) |
1500 |
总和 = 7500。级联只迭代四个被渲染片段,种子为 0。其循环结束时的剩余(finalSurplus)加到当前文件基础分配上,下表最后一行的 budget 即展示该结果。
示例 A —— 级联片段用量温和;当前文件吸收剩余
| 片段 | 流入 surplus | budget | consumed | 流出 surplus |
|---|---|---|---|---|
languageContext |
0 | 2000 | 0 | 2000 |
recentlyViewedDocuments |
2000 | 4000 | 1500 | 2500 |
neighborFiles |
2500 | 3500 | 500 | 3000 |
diffHistory |
3000 | 4000 | 500 | 3500 |
currentFile(最后裁剪) |
3500(finalSurplus) |
1500 + 3500 = 5000 | 5000 | 0 |
实际放置 token:0 + 1500 + 500 + 500 + 5000 = 7500。级联只消耗了 2500,其 3500 的剩余流入当前文件,使其从 1500 基础长到 5000,被裁得更少。
示例 B —— 级联为空;当前文件复用整个池
| 片段 | 流入 surplus | budget | consumed | 流出 surplus |
|---|---|---|---|---|
languageContext |
0 | 2000 | 0 | 2000 |
recentlyViewedDocuments |
2000 | 4000 | 0 | 4000 |
neighborFiles |
4000 | 5000 | 0 | 5000 |
diffHistory |
5000 | 6000 | 0 | 6000 |
currentFile(最后裁剪) |
6000(finalSurplus) |
1500 + 6000 = 7500 | ≤ 7500 | — |
无语言上下文、历史为空、邻居禁用时,每个级联片段都消耗 0,整个非-currentFile 池(2000 + 2000 + 1000 + 1000 = 6000)全部进入 finalSurplus。当前文件的裁剪上限变成 1500 + 6000 = 7500 = T——它实际上复用了整个池。这是"孤立编辑单个文件"的常见场景。该场景有直接测试佐证:promptCrafting.spec.ts#L1113-L1125 断言 cascade.finalSurplus 恰为 6000,"currentFileBudget 1500 + finalSurplus 6000 = 7500 = T"。
示例 C —— 级联填满池;当前文件只拿到基础份额
| 片段 | 流入 surplus | budget | consumed | 流出 surplus |
|---|---|---|---|---|
languageContext |
0 | 2000 | 2000 | 0 |
recentlyViewedDocuments |
0 | 2000 | 2000 | 0 |
neighborFiles |
0 | 1000 | 1000 | 0 |
diffHistory |
0 | 1000 | 1000 | 0 |
currentFile(最后裁剪) |
0(finalSurplus) |
1500 + 0 = 1500 | 1500 | 0 |
实际放置 token:2000 + 2000 + 1000 + 1000 + 1500 = 7500。每个级联片段恰好填满自己的份额,finalSurplus = 0,当前文件回落到 1500 基础——即逐片段下限。当前文件永远不会缩到该基础值以下。
被禁用的片段
片段可以因配置(languageContext.enabled = false、neighborFiles.enabled = false)或因没有输入数据(无语言上下文响应、邻居片段为空、无编辑历史)而被禁用。这些片段仍留在 order 中——接线的代码总是使用 DEFAULT_ORDER/DEFAULT_SHARES——所以它们的槽位仍然运行,只是 consumed = 0,其全部份额向前捐赠。到达级联末端的一切都会成为 finalSurplus,由当前文件的裁剪复用而不是被浪费。
从源码结构看,各片段"无输入即零消耗"由级联循环内的条件自然保证:languageContext 分支仅在 if (langCtx) 时调用 appender,neighborFiles 分支仅在 opts.neighborFiles.enabled && neighborSnippets && neighborSnippets.length > 0 时运行(promptCrafting.ts#L297-L309),此时 tokensConsumed 保持为 0,其 budget 全额计入 surplus 流出。
当 languageContext 与 neighborFiles 同时关闭时的有效上限
在 DEFAULT_ORDER 与池 T 下,每次捐赠步骤都逐步取 floor(级联种子为 0,设 C_rv = floor(floor(T·langCtxShare) + T·rvShare) 为 recently-viewed 的有效上限):
| 片段 | 有效上限 |
|---|---|
languageContext |
0(被消耗掉) |
recentlyViewedDocuments |
C_rv(langCtx 份额 + 自身份额,逐步 floor) |
neighborFiles |
0(被消耗掉) |
diffHistory |
floor(floor((C_rv − consumed_rv) + T·neighborShare) + T·diffShare)(自身份额 + neighbors 份额 + recently-viewed 剩余,逐步 floor) |
在 T = 7500 时,C_rv = floor(2000 + 2000) = 4000,级联池总上限为 floor(floor(4000 + 1000) + 1000) = 6000。diffHistory 剩下的部分成为当前文件的 finalSurplus:
recentlyViewedDocuments 消耗量 |
diffHistory 上限 |
finalSurplus → 当前文件 |
|---|---|---|
| 4000(填满上限) | 2000 | 0(当前文件为基础 1500) |
| 1500 | 4500 | 最多 4500 |
| 0 | 6000(整个级联池) | 最多 6000(当前文件最多 7500) |
两个启用片段在 T = 7500 下都"饥饿"的演算:
| 片段 | 流入 surplus | budget | consumed | 流出 surplus |
|---|---|---|---|---|
languageContext |
0 | 2000 | 0 | 2000 |
recentlyViewedDocuments |
2000 | 4000 | 4000 | 0 |
neighborFiles |
0 | 1000 | 0 | 1000 |
diffHistory |
1000 | 2000 | 2000 | 0 |
currentFile(最后裁剪) |
0(finalSurplus) |
1500 | 1500 | 0 |
级联放置了 6000(recently-viewed 吸收了 langCtx 的捐赠;diff 历史吸收了 neighbors 的捐赠),finalSurplus = 0,当前文件保持在 1500 基础,总计 7500。
顺序注意事项
recentlyViewedDocuments 在级联捐赠池上有 优先索取权(languageContext 的份额),因为它在 order 中更早。若 recently-viewed 很"饿",diff 历史在 T = 7500 时只能看到"neighbors + 自身份额 = 2000"——它无法直接触达 languageContext 的份额。diff 历史(最后一个级联片段)剩下的部分仍会流向当前文件。
要让 diff 历史分享捐赠,要么重排使 diffHistory 先于 recentlyViewedDocuments,要么重新平衡 shares。顺序仍须满足校验规则:recentlyViewedDocuments 必须先于 neighborFiles(因为 neighborFiles 会查询由 recently-viewed 填充的 docsInPrompt)。
另一种部署方式是把被禁用的片段 移除 出 order 并重新平衡 shares 使其和为 1——捐赠便被静态地"烘焙"进去,而不是从级联中涌现。接线的代码不会这么做;它总是传入 DEFAULT_ORDER 与 DEFAULT_SHARES。
最后裁剪当前文件
当前文件是级联剩余的天然汇聚点,因为它是唯一围绕兴趣点(光标)被裁剪的片段,总能吸收更多上下文。与其"先构建 prompt、测量未用量、再把当前文件放大重构建"(两遍做法有重复计算剩余的风险),实现上直接 最后裁剪当前文件:先运行级联,然后用级联未用掉的全部预算来定当前文件的尺寸。
机制
在 xtabProvider 中,启用 global budget 时的流程(对应 gatherContextAndClipCurrentFile):
- 汇集级联输入(语言上下文、邻居片段)——它们 不依赖 当前文件裁剪,所以可以先行产出;
- 运行
runGlobalBudgetCascade(...)(种子为0;当前文件不捐赠,所以级联只 给出); - 最后裁剪当前文件,上限为
currentFileBudget + cascade.finalSurplus(xtabProvider.ts#L303-L306); - 组装 prompt,把已算好的级联作为
precomputedCascade传给getUserPrompt。getUserPrompt只在globalBudget设置时才认precomputedCascade,因此级联恰好运行 一次,渲染出的片段与定量一致。
当 globalBudget 为 undefined(生产默认)时以上均不适用:当前文件按自己的 currentFile.maxTokens 裁剪,输入在之后汇集,级联根本不运行——与 legacy 路径逐字节一致。裁剪超预算时返回 NoNextEditReason.PromptTooLarge('currentFile')(xtabProvider.ts#L307-L309)。
守恒性(证明)
因为当前文件不捐赠,预算单向流动(级联 → 当前文件),所以不存在重复计算。级联种子为 0 时,循环结束剩余伸缩(telescope)为:
finalSurplus ≤ Σ(totalTokens · shareᵢ) for i in order
− Σ(consumedᵢ) = (T − T·share_cf) − C_cascade
因此当前文件的裁剪上限为:
cfBudget = floor(T · share_cf) + finalSurplus ≤ T · (Σ all shares) − C_cascade
⇒ C_cf + C_cascade ≤ T · (Σ all shares) (总量受池约束) ✅
且由于 finalSurplus ≥ 0,cfBudget ≥ floor(T · share_cf)——当前文件 永远不会缩到 基础份额以下。非负、经校验的份额(见 校验)是第一个不等式成立的前提。
注意事项 1 —— 份额和的容差:
validate接受|Σ shares − 1| ≤ 1e-3,所以上界是T · (Σ shares)而非恰好T。份额和略大于 1 的配置最多超配~1e-3 · T(默认T = 7500时约 7.5 个 token)。份额恰好和为 1 的配置(默认值如此)给出干净的≤ T上界。欠配(和 < 1)只是浪费一点预算。
注意事项 2 —— 内部计量,而非完整渲染 prompt:"≤
T" 是针对受预算片段的 内部 token 计量(分页裁剪行成本、原始片段成本、diff 条目成本)而言的。完整渲染的 prompt 还带有标签包裹、related-info 脚手架、lint 与 postscript,它们位于池之外。所以保证是"受预算片段合计消耗 ≤T",而不是"整个 prompt ≤T字符"。测试断言的是当前文件区域 增长 与内部计量,而非绝对的全 prompt 上界。
演算示例
T = 6000,languageContext 禁用,级联消耗 rv 1500 + neighbor 500 + diff 500 = 2500(C_cascade = 2500),share_cf = 1500/7500 = 1/5 ⇒ 基础 currentFileBudget = floor(6000 · 1/5) = 1200:
- 级联种子为
0;其finalSurplus = (1600 + 1600 + 800 + 800) − 2500 = 2300。 - 当前文件最后被裁剪到
1200 + 2300 = 3500(=6000 − 2500)。
当前文件从 1200 → 3500,恰好吸收了级联未用的 2300——符合"预算 6k,第一次构建消耗 4k ⇒ 把剩下的 2k 给当前文件"的直觉。
取舍与注意
- 当前文件不捐赠。预算只从级联流向当前文件。当前文件较小时,其基础份额 不会 交给级联片段(它们只拿到自己的份额)。若部署目标是丰富 邻居/历史 上下文(而非当前文件上下文),需要重新平衡
shares给那些片段更多。 - 等待(await)顺序变化。因为级联先于当前文件裁剪运行,语言上下文与邻居片段的汇集发生在当前文件裁剪之前。因此任何
PromptTooLarge('currentFile')的提前返回/取消原因在 global budget 下都发生在那些 await 之后。这是该 opt-in 特性可接受的后果;生产路径保持 legacy 顺序。 - Next-cursor 预测器不受影响。
xtabNextCursorPredictor保留自己专用的当前文件上限,从不把 global budget 带入其 prompt,因此无论此特性是否启用都逐字节一致。
测试佐证
该机制的关键性质在 promptCrafting.spec.ts 的 getUserPrompt — globalBudget cascade 测试组中有系统覆盖:
- 预算充足时级联路径与 legacy 路径产生 相同 prompt(#L1031-L1042);
- 顺序/重复/份额校验错误会正确抛错(#L1044-L1105);
- 级联无消耗时
finalSurplus携带全部未用池(6000)(#L1113-L1125); - 级联有消耗时
finalSurplus相应收缩(#L1127-L1141); precomputedCascade与内部计算产生 逐字节相同 的 prompt(#L1143-L1157)。
provider 侧另有实验配置用例('{}' 启用默认、totalTokens: 2000/8000 覆盖池大小),见 xtabProvider.spec.ts#L1272-L1290。
小结
Global Budget Cascade 是 NES/xtab prompt 构建中一个边界清晰、opt-in 的预算打通方案:以 totalTokens / order / shares 单池三旋钮替代碎片化的逐片段上限,用单向 surplus 级联让被禁用或空置片段(如 languageContext)的份额自动流向活跃片段,并让当前文件在级联之后按"基础份额 + finalSurplus"最后裁剪、复用全部剩余;配合严格的 validate(含非负份额与份额和 ≈ 1 的强制)和 softAssert 的消耗量不变量,保证受预算片段合计消耗不超过池总量。默认配置(池 7500、DEFAULT_ORDER、DEFAULT_SHARES)与现有逐片段上限体量中性,未启用时生产路径保持逐字节不变,这使其成为一个低风险、可实验回滚的改进方向。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00