如果你走进一家三明治店,看到案板上摆着两片面包:一片是刚出炉的顶层,一片是带焦边的底层,中间空空如也。老板说:“请复原出这块三明治的夹心。”这不是脑筋急转弯——如果允许你先闻闻底层面包上的酱汁痕迹,再翻看顶层面包的边缘,你多半能猜个八九不离十。这正是 AI 代码补全的核心场景:程序员把光标停在代码中间,光标前是“顶层面包”(前缀),光标后是“底层面包”(后缀),AI 要填的正是中间那块“夹心”。传统的续写模型只会像“从左往右吃饭”的食客,吃完一口才能猜下一口,永远看不到尾巴上的下层面包;而 FIM(Fill-in-the-Middle,中间填充)训练范式,教会模型“先看两头、再猜中间”,让 Copilot 这类工具真正实现了光标处补全。本文就沿着这块三明治的线索,带你完整认识 FIM 的数据构造、特殊标记、训练目标与工业实践。

岗位关联

理解 FIM 是进入代码智能领域的必修课:它是 Copilot 等产品“光标处补全”能力的核心技术,也是工业级代码模型训练管线中的标准范式。掌握它,你才算真正看懂 AI 辅助编程的底层工作原理。

目录
  1. 可视化演示
  2. 问题背景:光标常在代码中间
  3. 因果模型局限:只看得见左边
  4. 训练样本构建:随机切三明治
  5. 特殊标记设计:三张彩色便签
  6. 训练流程:不换模型只换编排
  7. 推理应用:光标处一键补全
  8. 工业实战:连锁店的经验
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

本节配有交互动效(进入视口后自动加载,约 3KB)。

代码分割:Prefix + Middle + Suffix 原始代码:FIM分割策略PSM与SPM重排:适配因果注意力1. \u968f\u673a\u9009\u62e9\u5206\u5272\u70b9, \u524d\u9762=Prefix, \u540e\u9762\u518d\u5207\u5206\u4e3a...

问题背景:光标常在代码中间

一家主打“盲盒三明治”的餐厅请来新厨师。顾客拿到的三明治是不完整的:能看见第一层面包(文件开头的代码),也能看见最底层面包(文件结尾的代码),唯独中间那段夹心被一张纸遮住。顾客只要求一件事——把挡住的夹心复原出来。现实中的程序员就是这位顾客:光标落在代码文件中间,前后的代码都摆在他眼前,唯独光标处那一小段需要补写的逻辑是“迷之夹心”。

代码文件本质上就是一段很长的文本字符串。任何一次“在光标处补全”都可以抽象成:已知光标之前的部分(前缀 Prefix)和光标之后的部分(后缀 Suffix),求中间缺失的部分(Middle)。这正是 FIM 训练范式要解决的问题,给它一个统一的刻画:

D=P⊕M⊕SD = P \oplus M \oplus S

FIM 由 OpenAI 在 2022 年《Efficient Training of Language Models to Fill in the Middle》中正式提出,随后 Codex、InCoder、SantaCoder、StarCoder 等代码模型全面采纳。它的核心思想朴素得惊人:训练时把文档随机切成三段,重排成“前缀+后缀+中间”让模型去预测中间。模型预测中间每个 token 时,注意力仍是单向的,但它的“左侧上下文”里已经包含了原本在右侧的后缀——相当于变相看到了两头的信息。

这就像厨师先研读“上层面包配方”,再研读“下层面包配方”,最后写出中间夹心的配方。有了这种能力,AI 代码助手才真正学会了在句子中间“接话”,而不是只会顺着往下写。

1. D 表示完整代码文件:把整个代码文件看作一个文本字符串,长度记为 L。

DD

2. 三段切分结构:在文件中随机选两个位置切开,得到前缀 P、中间 M、后缀 S 三段。

P⊕M⊕SP \oplus M \oplus S

3. ⊕ 拼接符号:表示按原顺序把三段字符串拼回去,结果就是原来的完整文件。

D=P⊕M⊕SD = P \oplus M \oplus S

因果模型局限:只看得见左边

盲盒三明治店里还有一位“老食客”——典型的“从左往右吃”型选手。他永远只咬面前最左边那一口,咽下去之后才根据嘴里的味道猜下一口。你若问他“中间夹心是什么”,他只能耸肩:“我只知道我吃过的,后面还没看到。”传统语言模型(因果语言模型)的补全方式,和这位老食客如出一辙。

主流大语言模型(GPT 系列等)都是因果解码器:预测下一个 token 时,只能使用它左侧的所有 token,右侧的 token 完全不可见。形式化地说,模型学到的其实是这样一个条件概率:

P(xt∣x1,x2,…,xt−1)P(x_t \mid x_1, x_2, \dots, x_{t-1})

这种“只看左边”的训练方式非常适合续写任务——给一段开头让 AI 继续往下写。但真实编程中,绝大多数补全发生在文件中间:函数写到一半、循环体里要补逻辑、return 需要在右括号之前……此时光标右边的代码蕴含着关键约束,比如闭括号、变量后续的使用方式、调用方期望的返回类型。因果模型看不到这些约束,只能靠左侧信息硬猜,生成结果往往是“语法看着顺眼、逻辑完全对不上”。

老食客答不出夹心,不是因为他笨,而是他获取信息的顺序决定了他在看到整块三明治之前,根本接触不到下层面包。想要在中间下刀,就必须改变“看”的顺序——这正是 FIM 的切入点。

1. x_t 表示当前位置的词元:t 是位置下标,x_t 是代码文件中第 t 个最小语义单元。

xtx_t

2. P 表示条件概率:模型学的其实是“给定左侧已生成的词元后,x_t 出现的概率”。

P(xt∣⋅)P(x_t \mid \cdot)

3. 左侧条件集合:条件中只有 x_1 到 x_{t-1},右侧一切不存在。

x1,x2,…,xt−1x_1, x_2, \dots, x_{t-1}

4. 合起来看:完整的因果预测式,代表“用过去预测现在”。

P(xt∣x1,x2,…,xt−1)P(x_t \mid x_1, x_2, \dots, x_{t-1})

训练样本构建:随机切三明治

要给厨师练出手艺,餐厅开始批量收集“盲盒复原”训练样本。做法很巧妙:把每一块完整的三明治随机切两刀,切成上层面包、夹心、下层面包三块,然后把上、下两片装进盒子,夹心单独放——厨师的任务就是看着盒子里的两片面包,还原被藏起来的夹心。因为每次切的位置不同,夹心可长可短,训练样本几乎取之不尽。

训练数据构建是 FIM 的第一步。对每个代码文件,框架会在 token 级别随机选择两个切分点,把文档切成三部分:前缀 P、中间 M、后缀 S。切分点不固定,而是每次随机采样,保证模型见过各种长度的中间区域:

a∼U{1,L−2},b∼U{a+1,L−1};P=D[0:a], M=D[a:b], S=D[b:L]a \sim \mathcal{U}\{1, L-2\},\quad b \sim \mathcal{U}\{a+1, L-1\};\qquad P = D[0:a],\ M = D[a:b],\ S = D[b:L]

随机切分就像厨房里的“花刀”:这次切个小口子,下次切掉大半个面包。只有切法足够多样,厨师才能练出“夹心多长都能复原”的功夫。样本的多样性,正是 FIM 能力的地基。

1. 采样切分点 a:从 1 到 L-2 中均匀随机取一个整数,保证后面至少还能切第二刀。

a∼U{1,L−2}a \sim \mathcal{U}\{1, L-2\}

2. 采样切分点 b:从 a+1 到 L-1 中均匀随机取一个整数,保证三段都非空。

b∼U{a+1,L−1}b \sim \mathcal{U}\{a+1, L-1\}

3. 提取前缀与中间:D[0:a] 是从头到 a 的前缀,D[a:b] 是被挖掉的夹心。

P=D[0:a],M=D[a:b]P = D[0:a],\quad M = D[a:b]

4. 提取后缀:D[b:L] 是剩余的后缀,作为右侧约束喂给模型。

S=D[b:L]S = D[b:L]

特殊标记设计:三张彩色便签

食材摆上案板,厨师还需要便签。两块面包外形相似,不贴标签极易混淆:哪片是上层面包?哪片是下层面包?夹心应该从哪儿开始填?于是餐厅发明了三张便签:一种颜色贴上层面包,一种颜色贴下层面包,还有一种贴在“由此开始填夹心”的位置。

模型同样需要便签。FIM 引入几个词表中额外定义的特殊 token(不会与正常代码冲突的专属符号)来标注三段边界。以常见实现(如 SantaCoder)为例,训练样本会被重排成:

X=⟨PRE⟩⊕P⊕⟨SUF⟩⊕S⊕⟨MID⟩⊕M⊕⟨EOT⟩X = \langle\mathrm{PRE}\rangle \oplus P \oplus \langle\mathrm{SUF}\rangle \oplus S \oplus \langle\mathrm{MID}\rangle \oplus M \oplus \langle\mathrm{EOT}\rangle

这些特殊 token 与普通代码 token 一起参与训练。模型的自注意力会让 ⟨MID⟩ 位置的表示同时聚合前缀和后缀信息,解码时既“记得”左边代码的风格与变量,也“瞄到”右边代码的约束与收尾结构。由于重排后的序列本身仍是正常文本,训练成本几乎没有增加,也不需要改动 Transformer 结构。

三张便签让厨师在信息混杂的案板上瞬间定位:哪片先处理、哪片后处理、夹心从哪下刀。没有它们,模型根本分不清“前缀的结尾”和“后缀的开头”之间该填什么。

1. 前缀与标记组合:先把前缀标记和前缀拼起来,放进输入序列开头。

⟨PRE⟩⊕P\langle\mathrm{PRE}\rangle \oplus P

2. 后缀接在标记后:随后是后缀标记加后缀,让模型在生成中间之前先“看”到右边。

⟨SUF⟩⊕S\langle\mathrm{SUF}\rangle \oplus S

3. MID 标记召唤中间:⟨MID⟩ 是生成位置信号,模型从它之后开始输出中间代码。

⟨MID⟩⊕M\langle\mathrm{MID}\rangle \oplus M

4. EOT 标记收尾:⟨EOT⟩ 告诉模型中间部分结束,序列到此为止。

⟨EOT⟩\langle\mathrm{EOT}\rangle

训练流程:不换模型只换编排

厨师正式学艺:每天练几百道“盲盒复原题”。他先看盒子里上层面包的纹理(前缀),再看下层面包的颜色(后缀),然后开口报出夹心配方。餐厅老板站在旁边,每当他报错一个字,就记一笔“误差账”,并顺着误差的方向调整他的“手感”(模型参数)。

FIM 训练在机制上没有任何新结构:模型依然是因果 Transformer,依然从左到右逐 token 预测。改变的只是数据的编排——把后缀放在前缀之后、中间之前。由于因果注意力的特性,模型预测中间第一个 token 时,左侧已经包含了前缀加后缀的全部信息,效果自然远好于“只有前缀”。

训练目标仍是标准的交叉熵损失。为了让模型把“学习预算”花在刀刃上,常见实现会把损失集中在中间部分 M 上,前缀和后缀只作为条件上下文:

LFIM=−1∣M∣∑xt∈Mlog⁡Pθ(xt∣x<t)\mathcal{L}_{\mathrm{FIM}} = -\frac{1}{|M|} \sum_{x_t \in M} \log P_\theta(x_t \mid x_{<t})

学艺不是只练盲盒复原题。老练的老板会让厨师大部分时间练盲盒题,小部分时间练“从零制作完整三明治”——这样他既会补夹心,也不会忘记从头做起。

1. L_FIM 表示 FIM 训练损失:损失越小,说明中间部分预测得越准。

LFIM\mathcal{L}_{\mathrm{FIM}}

2. 归一化系数 |M|:用中间 token 数做平均,避免长样本主导梯度。

1∣M∣\frac{1}{|M|}

3. 交叉熵求和:对中间每个 token 的预测概率取负对数再求和。

−∑xt∈Mlog⁡Pθ(xt∣x<t)-\sum_{x_t \in M} \log P_\theta(x_t \mid x_{<t})

4. 条件概率带参数:P_θ 是模型输出的概率分布,θ 是要被梯度更新的权重。

Pθ(xt∣x<t)P_\theta(x_t \mid x_{<t})

推理应用:光标处一键补全

学成出师。顾客端上一份“缺夹心三明治”:上层面包、下层面包都在,中间空着。厨师不需要重做整块三明治,他只需先看上层(前缀),再看下层(后缀),然后从空档处开始“挤酱、铺料”——一口一口把夹心填满,直到自己觉得达到收尾标准。

推理阶段,编辑器把光标前的代码作为前缀 P、光标后的代码作为后缀 S,按训练时见过的格式拼好,让模型从 ⟨MID⟩ 之后开始采样生成中间代码。生成的 token 不断追加到中间区域,直到触发结束条件(遇到 ⟨EOT⟩ 标记或达到最大长度限制),再插入编辑器光标处。

M^=arg⁡max⁡M  Pθ ⁣(M∣⟨PRE⟩⊕P⊕⟨SUF⟩⊕S⊕⟨MID⟩)\hat{M} = \arg\max_{M}\; P_\theta\!\left(M \mid \langle\mathrm{PRE}\rangle \oplus P \oplus \langle\mathrm{SUF}\rangle \oplus S \oplus \langle\mathrm{MID}\rangle\right)

出餐台前,厨师一气呵成:瞥一眼两片面包,从空档处把夹心“写”出来。对用户而言,唯一的体验差异就是——光标处的代码“唰”地长出来了,而且与后面的代码严丝合缝。

1. 生成目标是 M̂:M̂ 是模型要输出的中间代码,目标是让条件概率最大化。

M^=arg⁡max⁡M\hat{M} = \arg\max_{M}

2. 模型看到的输入:竖线右边是完整输入:PRE 加前缀、SUF 加后缀、MID 标记。

⟨PRE⟩⊕P⊕⟨SUF⟩⊕S⊕⟨MID⟩\langle\mathrm{PRE}\rangle \oplus P \oplus \langle\mathrm{SUF}\rangle \oplus S \oplus \langle\mathrm{MID}\rangle

3. 整体条件概率:对候选的中间代码 M 计算条件概率并按概率挑选。

Pθ(M∣⋅)P_\theta(M \mid \cdot)

工业实战:连锁店的经验

三明治连锁店把“盲盒复原”模式推广到全部门店,店长们开始比拼数据:一位只训练“从左咬预测下一口”的店员,与一位接受过 FIM 盲盒训练的店员,谁复原夹心更快更准?答案毫无悬念——后者。

在工业界,FIM 已成为代码补全模型的事实标准。GitHub Copilot 背后的 Codex 模型、InCoder、SantaCoder、StarCoder 以及大量开源代码模型,几乎都在训练阶段引入 FIM。衡量标准也从“文本困惑度”转向“补全采纳率”:用户接受 AI 建议的补全占全部建议的比例。FIM 让模型在光标处给出的建议更贴合上下文,采纳率明显上升:

采纳率提升=RFIM−RbaseRbase×100%\text{采纳率提升} = \frac{R_{\mathrm{FIM}} - R_{\mathrm{base}}}{R_{\mathrm{base}}} \times 100\%

餐厅的评分板上,FIM 厨师稳居榜首。从代码模型的技术选型到 IDE 插件的延迟优化,FIM 早已不只是论文里的概念,而是每一位做代码智能的工程师手里标配的“夹心配方”。

1. 采纳率 R:用户接受补全建议的比例,是代码补全产品的北极星指标。

RFIM, RbaseR_{\mathrm{FIM}},\ R_{\mathrm{base}}

2. 相对提升公式:分子是提升量,分母是基线值,乘以 100% 转为百分比。

RFIM−RbaseRbase×100%\frac{R_{\mathrm{FIM}} - R_{\mathrm{base}}}{R_{\mathrm{base}}} \times 100\%

AI 应用场景

IDE 光标处智能补全

阶段:推理部署

解决的问题:传统续写式补全只能在文件末尾追加代码,而程序员的大量操作是修改已有函数、插入循环体、在 return 前补逻辑。FIM 让补全跟随光标位置,把光标前后的代码都作为上下文,生成结果与后续代码衔接自然。好比厨师同时看上下两片面包再填夹心,而不是从最前面开始重做一块三明治。

具体案例:某后端工程师在 VS Code 中写一个需要权限校验的接口,函数写到一半时停在中间回车换行,Copilot 基于函数签名(前缀)和结尾的 return 结构(后缀),在光标处补出完整的校验逻辑,工程师直接按 Tab 采纳。

企业内部代码助手微调

阶段:数据构建 + 模型微调

解决的问题:通用代码模型不了解公司内部 API 与代码风格。用公司私有仓库切出 FIM 样本微调,让模型在中间补全时优先选用内部函数与命名规范,相当于给厨师一本“本店特色酱料手册”。

具体案例:某金融科技公司把内部 Java 微服务代码按随机切分点构建 FIM 数据集,微调后内部开发者的补全采纳率从 20% 提升到 35%,新人上手速度明显加快。

Python 演示

python75 行
import numpy as np TOKENS = ['A', 'B', 'C', 'D']IDX = {t: i for i, t in enumerate(TOKENS)}  def middle_rule(p, s):    # 夹心由前缀最后一个字符与后缀第一个字符共同决定    return TOKENS[(IDX[p] + IDX[s]) % 4]  def build_corpus(n=3000):    rng = np.random.default_rng(42)    data = []    for _ in range(n):        p = TOKENS[rng.integers(4)]        s = TOKENS[rng.integers(4)]        # 构造三明治样本: [前缀, 夹心, 后缀]        data.append([p, middle_rule(p, s), s])    return data  def featurize(seq, fim):    if fim:        # FIM 输入: 前缀 + 后缀 共同作为特征, 共 8 维        f = np.zeros(8, dtype=float)        f[IDX[seq[0]]] = 1.0        f[4 + IDX[seq[2]]] = 1.0    else:        # 普通续写输入: 只知道前缀, 共 4 维        f = np.zeros(4, dtype=float)        f[IDX[seq[0]]] = 1.0    return f  def train(X, Y, epochs=500, lr=0.1):    # 线性 softmax 分类器, 用梯度下降训练    W = np.zeros((X.shape[1], 4))    for _ in range(epochs):        logits = X @ W        probs = np.exp(logits - logits.max(axis=1, keepdims=True))        probs = probs / probs.sum(axis=1, keepdims=True)        W = W - lr * (X.T @ (probs - Y)) / len(Y)    return W  def evaluate(W, pairs):    X = np.array([featurize(seq, fim) for seq, fim in pairs])    Y = np.array([np.eye(4)[IDX[seq[1]]] for seq, _ in pairs])    pred = (X @ W).argmax(axis=1)    return float((pred == Y.argmax(axis=1)).mean())  def main():    data = build_corpus(3000)    train_set = data[:2000]    test_set = data[2000:]     Y = np.array([np.eye(4)[IDX[seq[1]]] for seq in train_set])    X_fim = np.array([featurize(seq, True) for seq in train_set])    X_base = np.array([featurize(seq, False) for seq in train_set])     W_fim = train(X_fim, Y)    W_base = train(X_base, Y)     base_acc = evaluate(W_base, [(seq, False) for seq in test_set])    fim_acc = evaluate(W_fim, [(seq, True) for seq in test_set])     print('基线(只看前缀)夹心预测准确率: {:.1%}'.format(base_acc))    print('FIM(前缀+后缀)夹心预测准确率: {:.1%}'.format(fim_acc))    print('FIM 相对提升: {:.1%}'.format((fim_acc - base_acc) / base_acc))  if __name__ == '__main__':    main()
目的:用一个微型线性模型对比“只看前缀”与“前缀+后缀”两种方式预测中间夹心的准确率,直观演示 FIM 的信息增益。
  1. data.append([p, middle_rule(p, s), s])用规则构造 [前缀,夹心,后缀] 的三明治样本行。
  2. f[4 + IDX[seq[2]]] = 1.0把后缀的独热编码放入特征的高 4 位,让模型能看到“右边”。
  3. probs = np.exp(logits - logits.max(axis=1, keepdims=True))softmax 前减去最大值,数值更稳定。
  4. W = W - lr * (X.T @ (probs - Y)) / len(Y)交叉熵梯度的矩阵化更新,让权重沿损失下降。
  5. X_fim = np.array([featurize(seq, True) for seq in train_set])把所有训练样本编码成 FIM 特征矩阵。
  6. fim_acc = evaluate(W_fim, [(seq, True) for seq in test_set])在测试集上评估 FIM 模型的夹心预测准确率。
  7. 'FIM 相对提升: {:.1%}'.format((fim_acc - base_acc) / base_acc)输出相对提升比例,对应工业界的采纳率提升口径。

常见误区

误区“FIM 训练肯定是发明了一种新神经网络,比如双向 Transformer 或双塔结构,不然怎么能同时看左右两边?”

正解FIM 完全没有改变模型骨架。它依然使用标准的因果注意力解码器,注意力掩码不变;它做的只是把训练数据重新排列——把后缀挪到前缀后面、中间之前,并加上特殊标记。模型在生成中间时“看到的后缀”其实是被当作左侧上下文喂进来的。好比厨师还是那个厨师,只是上菜顺序从“先吃左边”改成了“先看两头”。

为何常见:“同时利用左右信息”让人自然联想到双向编码器(BERT),于是误以为 FIM 是模型结构创新。

误区“FIM 能看右边代码,那它不就是 BERT 那种双向注意力吗?是不是可以直接把它当编码器用?”

正解完全不同。BERT 的双向注意力让每个 token 同时看到左右两边,且它是编码器;FIM 模型推理时仍是自回归解码器,一次只生成一个 token,每个 token 只能注意它左侧的 token。FIM 的“看右边”是一种近似:通过把后缀放在前面,使后缀信息进入左侧上下文。代价是位置信息错位,因此训练时要混合普通样本缓解。厨师并没有透视眼,他只是被允许先看下层面包,再转过头填夹心。

为何常见:“看到了右边的信息”被直观等同于“注意力是双向的”,忽略了因果解码器的生成方式。

误区“往中间补全这么特殊,肯定要设计一个复杂的损失函数,比如给不同位置加不同权重。”

正解FIM 用的就是标准语言模型交叉熵。唯一的差别是实践中常把损失集中在中间部分 M 上计算,前后缀作为条件上下文不参与(或较少参与)梯度更新。模型的学习信号依然是“下一个 token 预测得对不对”。这和厨师练功没有新花样,只是评分时只关注夹心部分的还原度,不考核面包片本身是否重做得像。

为何常见:人们习惯把“新任务”与“新损失”绑定,实际上 FIM 的关键创新在数据构造而非目标函数。

误区“既然 FIM 这么好,那把 100% 的训练样本都切成前缀-后缀-中间格式,效果岂不是更好?”

正解实验与工程经验都表明不能全量 FIM 化。若所有样本都是 FIM 格式,模型会过度依赖“看到后缀才开口”的模式,标准续写能力退化,在遇到没有后缀的场景(文件开头补全、多轮对话等)时表现变差。通常 70%~90% 的 FIM 样本配 10%~30% 的普通续写样本效果最佳。就像厨师不能只练盲盒复原,也要保持正常做三明治的手感。

为何常见:“数据越多越好”的直觉,让人忽视了任务分布多样性对泛化能力的重要性。

面试题

Q1. Fill-in-the-Middle 中的“中间”指的是什么?

单选题 · easy

  • A.神经网络隐藏层的中间维度
  • B.代码文件中被挖空的那段代码← 正确答案
  • C.训练过程中间的某个阶段
  • D.模型注意力矩阵的中间行

答案:B。FIM 把代码文件切成前缀、中间、后缀三段,“中间”就是被挖出来作为预测目标的那段代码。A/C/D 分别混淆了网络结构、训练阶段和注意力矩阵,都不是数据切分意义上的中间。

Q2. 传统因果语言模型补全代码时,最大的限制是什么?

单选题 · easy

  • A.生成速度太慢无法跟上打字
  • B.只能看到光标左侧的上下文← 正确答案
  • C.需要使用特殊的显卡设备
  • D.每次只能输出一个汉字

答案:B。因果模型按从左到右的顺序预测下一个 token,条件中只有左侧 token,右侧不可见。B 是核心原因;A/C 是工程问题,D 是对编码单元的误解。

Q3. FIM 训练时,输入序列的正确排列是哪个?

单选题 · easy

  • A.中间加后缀再加前缀
  • B.前缀加后缀再加中间← 正确答案
  • C.后缀加前缀再加后缀
  • D.前缀加中间再加后缀

答案:B。标准 FIM 把输入排成“前缀 + 后缀 + 中间”,让模型在预测中间前同时看过两边。A 方向错误;C 出现重复后缀;D 是原始文档顺序,属于普通续写而非 FIM。

Q4. 下列哪个是 FIM 引入的特殊标记?

单选题 · mid

  • A.BOS 标记表示文本开始
  • B.PRE 标记表示前缀开始← 正确答案
  • C.CLS 标记表示整句分类
  • D.PAD 标记表示对齐填充

答案:B。FIM 用 ⟨PRE⟩/⟨SUF⟩/⟨MID⟩ 三个标记标出前缀、后缀和中间的位置。A/C/D 分别是其他场景的专用 token,与 FIM 的三段切分无关。

Q5. FIM 训练采用的模型骨架是什么?

单选题 · mid

  • A.双向注意力编码器
  • B.因果注意力解码器← 正确答案
  • C.循环神经网络编码器
  • D.多层感知机分类器

答案:B。FIM 不改变模型结构,仍是 GPT 式的因果注意力解码器,逐 token 自回归生成。A 是 BERT 风格;C/D 也都不符合代码模型的实际架构。

Q6. FIM 推理时,编辑器提供给模型的输入是?

单选题 · mid

  • A.光标前代码加光标后代码← 正确答案
  • B.光标处代码加文件路径
  • C.整个项目的所有文件
  • D.最近修改的代码片段

答案:A。推理时把光标前的代码当前缀、光标后的代码当后缀,按 FIM 格式拼好并采样中间部分。B/C/D 描述的是辅助信息或更大范围的上下文,不构成 FIM 的标准输入。

Q7. 关于 FIM 训练的损失函数,正确的说法是?

单选题 · hard

  • A.在中间部分计算交叉熵损失← 正确答案
  • B.在前缀后缀上使用对比损失
  • C.在全部位置计算均方误差
  • D.使用生成对抗网络损失

答案:A。FIM 沿用语言模型交叉熵,并常把损失集中在中间 M 上,让模型专注提高补全准确率。B 是对比学习;C 是回归任务;D 是 GAN,都与自回归语言建模无关。

Q8. FIM 构建训练样本时,正确的做法有哪些?

多选题 · hard

  • A.随机选择文档切分点← 正确答案
  • B.混合部分普通续写样本← 正确答案
  • C.固定中间部分长度为八
  • D.只保留后缀丢弃前缀

答案:AB。切分点随机采样保证多样性,并混合普通样本防止模型失去续写能力。C 的“固定长度”违背随机性;D 丢前缀会让模型缺乏左侧信息,与 FIM 的设计相悖。

Q9. FIM 相比纯续写带来的收益有哪些?

多选题 · mid

  • A.支持在光标处插入代码← 正确答案
  • B.利用了右侧代码的约束← 正确答案
  • C.模型参数量明显减少
  • D.不需要任何训练即可用

答案:AB。FIM 让补全位置从“文末”扩展到“光标处”,且后缀提供的约束让中间预测更准。C 是网络结构问题;D 违背了“需要训练”这一前提。

Q10. 关于 FIM 与因果注意力的关系,正确的是?

多选题 · extreme

  • A.注意力掩码保持单向不变← 正确答案
  • B.后缀信息被移入左侧上下文← 正确答案
  • C.等价于把注意力改成双向
  • D.位置错位无需任何缓解

答案:AB。FIM 的关键正是“不改掩码、只改数据流”:后缀被当作左侧上下文喂给模型,使因果解码器也能感知右侧。C 混淆了编码器;D 忽略了需混合训练样本缓解位置错位的工程经验。