ACL'26 Long 2026.04 Zero-Shot NER Token Classification

Just Pass Twice:把输入抄两遍,让因果 LLM 做判别式 NER

Ahmed Ewais, Ahmed Hashish, Amr Ali · WitnessAI

原文 ↗ ACL Anthology arXiv:2604.05158
TL;DR

核心问题:能不能让 decoder-only 因果 LLM 像 BERT 那样做逐 token 分类式的 NER?BERT 类编码器有双向注意力,天然适合打标签,但参数小(<1B)、世界知识少;LLM 知识丰富,可因果掩码让每个 token 看不到后文,无法消歧,于是所有 LLM-NER 方案都退回到「让模型生成实体列表」——慢、贵、还会幻觉出原文没有的实体。

JPT 的做法:把输入抄两遍拼成一条序列x₁…xₙ [SEP] x₁…xₙ)。因果掩码不变,但第二遍的每个 token 都排在第一遍全部 token 之后,于是能「回看」完整句子——白拿双向上下文,模型结构一行不改。分类时只取第二遍 token 的 hidden state。

第二个部件:用自然语言「定义」而不是标签名来表示实体类型,并且定义同时注入 prompt 文本和 embedding 向量两条通道。这让用户能直接用一句话规定边界情况(比如 PRICE 要不要包含「budget-friendly」)。

效果:CrossNER + MIT 上平均 74.1 F1,比之前最好的 SaM 高 7.9 分;同时比生成式方法快 20 倍以上——因为翻倍的输入全在并行的 prefill 阶段吃掉,而生成式的开销在串行的 decode 阶段

+7.9
F1 超过此前 SOTA(SaM)
JPT-8B 74.1 vs 66.2
22×
推理加速
JPT-4B vs UniNER-7B
−15.2
去掉输入复制后
掉的 F1(消融)
0.95%
JPT-4B 可训练参数占比
backbone 完全冻结

先约定几个说法,全文保持一致:

  • 判别式 token 分类:一次 forward 给每个 token 直接打一个类型标签,再把连续同类 token 合成 span。对应 BERT 时代的 NER 范式。
  • 生成式抽取:让 LLM 自回归解码出实体列表或 JSON,再解析。当前 LLM-NER 的主流。
  • 第一遍 / 第二遍(first / second pass)指同一条输入序列的前半段和后半段,不是两次 forward。全流程只跑一次前向。
  • 实体类型定义(entity type definition):一段描述「该标什么、不该标什么」的自然语言,取代 PERSON 这种裸标签名。

为什么因果 LLM 不能直接做 token 分类

NER 最经典的做法是逐 token 打标签:给句子里每个 token 分配一个类型,然后把连续同类的 token 合并成实体 span。BERT / RoBERTa / DeBERTa 这类双向编码器天生适合这件事——每个 token 都能同时看到左右上下文。GLiNER、NuNER 等一批 NER 系统都建立在这个范式上。

但编码器有个硬天花板:通常不到 1B 参数,上下文窗口小,世界知识远不如大模型。碰到 CrossNER 里的 AI、文学、音乐这类需要领域常识的场景,编码器就吃力了。

LLM 知识够了,但它是因果注意力:处理第 i 个 token 时只能看到 x₁…xᵢ。论文用一个例子说明这为什么致命——

单次输入 · 标准因果注意力 Paris released a new album 因果掩码屏障:右侧全部不可见 分类 Paris 时模型手上只有「Paris」这一个词 → 是城市(LOCATION)还是音乐人(PERSON)? ✗ 无法判断 决定性线索 「a new album」 出现在它之后

句子 “Paris released a new album” 中,把 Paris 判成人名而非城市的唯一依据是后半句「发了一张新专辑」。因果掩码恰好把这条线索挡在了外面——这正是 decoder-only LLM 做不了标准 token 分类的根本原因。

于是几乎所有 LLM-NER 工作都绕开了 token 分类,改用生成式抽取:把实体类型写进 prompt,让模型吐出实体列表。这条路避开了架构问题,却引入三个新的:

所以这篇论文真正要回答的是:能不能把编码器时代成熟的 token 分类范式,原封不动搬到因果 LLM 上?

已有方案卡在哪

在看 JPT 之前,先理清三类相关工作各自的位置:生成式 LLM 方案提供了知识但牺牲了效率,编码器判别式方案效率高但容量受限,已有的双向化技巧要么需要特殊预训练,要么和 JPT 的目标不同。下面每张卡片折叠时已给出它在本文论证中的角色和留下的缺口,展开可看方法细节。

UniversalNER(UniNER)ICLR'24
生成式 LLM-NER 的代表:从 ChatGPT 蒸馏开放实体词表,但每个实体类型要单独查询一次,N 个类型就是 N 轮解码。JPT 的主要速度对照组(1970 秒 vs 90 秒)。
句子固定,实体类型逐个轮询——每一轮都是一次完整的自回归解码 输入句子 (每轮都重放) “What describes PERSON in the text?” “What describes ALBUM in the text?” … 共 N 个类型,逐个来 自回归解码 ×N 轮 JSON 实体列表 解析成 span 可能幻觉 / 解析失败

它做了什么:UniversalNER 走的是「定向蒸馏」路线——不是通用地模仿 ChatGPT 的指令跟随能力,而是只蒸馏 NER 这一件事。做法很聪明:输入文本不让模型编,而是从 Pile 语料真实采样段落,只让 ChatGPT 产出标注。提示里刻意不给定任何类型清单,让它自由发挥。最终得到 45,889 个段落、240,725 个实体提及、13,020 个不同实体类型的开放词表数据,再用它微调 LLaMA-7B。

推理长什么样:模型被训练成多轮对话格式,每一轮问一个类型——「What describes person in the text?」,模型回一个 JSON 列表,loss 只算在回答上。他们还加了负采样(问段落里不存在的类型,期望回空列表)来抑制过度预测。

「一次一个类型」不是疏忽,是必需品:这一点很关键。作者自己试过把所有类型塞进一次问答(UniNER-7B-all-in-one),也就是最省钱的那种改法——结果平均 F1 从 41.7 掉到 29.9,编程领域直接从 27.7 崩到 6.1。他们归因于注意力被分散。换句话说,这个方法的昂贵是结构性的,不能通过合并请求消掉

卡在哪:于是成本随类型数线性膨胀。CrossNER-AI 有 17 个实体类型,就意味着同一个句子要被完整解码 17 遍。CrossNER-Politics 上实测 1970 秒、2.77 美元,而 JPT-4B 是 90 秒、0.13 美元。另一个结构性后果是:每个类型独立判断,没有任何机制保证互斥,所以同一个 mention 经常被同时打上 ORGANIZATION 和 POLITICAL PARTY 两个标签——论文 Figure 9 里的过度预测就是这么来的。

与 JPT 的关系:UniNER 是 JPT 速度对照的主角(22× 加速)。精度上 JPT-8B 74.1 vs UniNER-7B 61.8。有意思的是,GLiNER 和后来一批判别式模型用的训练数据(Pile-NER)正是 UniversalNER 造出来的——它的数据贡献比它的模型贡献影响更久。

SaM:Selecting and MergingACL'25
JPT 之前的 SOTA(CrossNER + MIT 平均 66.2 F1):离线训练一批领域专家 LoRA,推理时用两套策略各挑 3 个专家合并成两个模型,结果取并集。缺口是仍属生成式、需要预备专家权重库,而且解码成本是别人的 2 倍。
SaM 框架总览

SaM 原论文 Figure 2:目标领域进来后分叉成上下两条独立的选择流水线,各自合并出一个模型,最后把两边的预测取并集。

展开说明 ▸
  • 最左侧紫色文档堆「Target Domain」:目标领域的原始文本。一条灰箭头出来后立刻一分为二,进入上下两个面板——这个分叉是理解全图的钥匙。
  • 上面板 (a) Domain Similarity:左边那组彩色椭圆是各领域的文本嵌入聚类,每个椭圆中心的实心点是领域质心。细箭头从中心的紫色(目标)质心辐射到其余五个质心,量的是余弦相似度。这是一条不需要任何标注的纯分布先验。
  • 下面板 (b) Sampling Evaluation:流程被标了 ①②③④。① 从目标领域只抽 10 条样本;② 让全部六个专家各跑一遍,得到六份预测;③ 对这些预测投票合成伪标签——这一步是关键,它让整个评估不需要任何真实标注;④ 虚线箭头从伪标签反指回各份预测,用伪标签给每个专家打分。这是实测而非先验。
  • 两条支路各自的「Expert models」虚线框:都挑出 3 个专家(m=3),但注意图里两个框中的立方体颜色不同——两套策略选出的专家集合本来就不一样,论文观察到「最相似的领域往往不是表现最好的」。
  • Merge → Task model:各自用 Ties-Merging 把 3 个 LoRA 的任务向量融成一个模型,得到紫色的 MDS 和蓝色的 MSE
  • 最右侧两股预测汇成一份:两个模型各自把整个测试集解码一遍,结果取并集。这就是它比别人贵一倍的地方——图上那两条并行的推理路径是字面意义上的两次全量解码。

它做了什么:SaM 针对的是一个很现实的矛盾——单个统一 NER 模型吃不下所有领域,而给每个领域各训一个又不可扩展。它的方案是把「组合多领域知识」这一步从训练时挪到推理时:离线把 20 多个 NER 数据集归成六个领域(新闻 / 社交媒体 / 生物医学 / STEM / 法律 / 交通),各训一个 LoRA 专家;推理时再按目标领域临时挑选并合并。

两套选择策略,而不是一套:这是容易看漏的设计。Domain Similarity 用句嵌入质心的余弦距离排名,是理论先验;Sampling Evaluation 则从目标域抽 10 条样本,让六个专家全跑一遍、投票产生伪标签,再拿伪标签反过来给专家打分,是经验实测。两套各选 3 个专家、各合并出一个模型、各解码一遍,最后预测取并集。作者说明并集而非交集是刻意的:两个模型已经各自领域定制,捕捉的是互补视角,此处召回比精度过滤更值钱。

为什么它是最强基线:CrossNER + MIT 平均 66.2 F1(Llama3.1-8B),比自家统一训练的基线高 8.7% 相对值,Politics 域提升最大。

卡在哪:三处。其一,仍然是生成式——选择和合并只改善了「用哪套权重」,没碰「逐 token 解码出 JSON」这个昂贵且要解析的输出形式。其二,论文附录自己列了成本表:所有统一模型基线推理开销都是 SaM 是 ,因为两个模型要各解码一遍全集;砍成一个模型的 SaM_eco 能回到 1×,但精度掉到 65.49。其三,它离不开那个预先训练好、预先按领域切分好的专家库,等于把泛化问题推给了库的覆盖度——这也是作者自列的头号局限。

与 JPT 的关系:这是 +7.9 F1 那个数字的对照对象(74.1 vs 66.2)。两种思路的差异很能说明问题:SaM 靠准备更多专用权重覆盖领域,JPT 靠一句自然语言定义指定新类型——后者边际成本近乎为零,而且定义可以离线编码并缓存。

GLiNERNAACL'24
判别式路线的代表:DeBERTa 双向编码器同时编码「实体类型提示 + 文本」,在共享隐空间里做 span-类型匹配,一次并行抽全部类型。缺口是 backbone 只有几亿参数,世界知识撑不起专业领域。
GLiNER 架构图

GLiNER 原论文 Figure 2:类型提示与文本拼成一条序列送进双向编码器,左右两条支路分别产出类型向量和 span 向量,最后在右下角的相似度矩阵里做匹配。

展开说明 ▸
  • 顶部两个虚线框:左边橙色标题「Entity types prompt」里是 [ENT] Person [ENT] Organization [ENT] Location [SEP]——每个类型名前插一个可学习的 [ENT] 标记;右边绿色标题「Input sentence」是 “Alain Farley works at McGill University”,每个词下面标了 0–5 的位置编号。
  • 两个框汇入同一条蓝色横条「Bidirectional Transformer(BERT, DeBERTa…)」:这是全图的关键——类型提示和文本在同一次前向里互相注意,不是分开编码再比对。
  • 中间灰色向量条(Token/word representation):编码器对每个位置输出一个向量,提示侧和文本侧都有。
  • 分成左右两条粉色支路:左边 FFN layer 只吃提示侧的向量,产出「Entity type Embeddings」(3 个,每类一个);右边 Span representation layer 只吃文本侧的向量,把起止 token 拼接后过 FFN,产出「Span Embeddings」。
  • span 下方标注的 (0,0) (0,1) (1,1) …:GLiNER 穷举所有起止组合(长度上限 12),一次性全部并行算出,这是它不需要自回归的原因。
  • 右下角的相似度矩阵:行是类型、列是 span,格子里是 σ(Sij·qt)。红色高亮的两格是答案——0.9 落在(Person, span(0,1))即 “Alain Farley”,0.8 落在(Organization, span(4,5))即 “McGill University”。超过 0.5 即判为实体。
  • 一句话总结:整个 NER 被压成一张「类型 × span」的概率表,一次前向填满,阈值一卡就出结果。

它做了什么:GLiNER 把 NER 重新表述成共享隐空间里的语义匹配,全程一次前向。类型向量 qt 取自各个 [ENT] 位置的隐状态过 FFN;span 向量 Sij 由起止 token 表示拼接后过 FFN(长度上限 12,避免枚举爆炸)。最后 σ(Sijqt) 对每个「span × 类型」组合做二分类,训练用的是全体组合上的二元交叉熵。

关键优势:因为编码器是双向的,且所有类型都在同一条序列里,一次前向就能把全部类型的全部 span 并行抽完——没有自回归,没有逐类型轮询,也不可能幻觉出原文没有的文本(候选只能从输入的 span 里选)。模型只有几亿参数,跑起来非常便宜(CrossNER-Politics 上 33 秒、0.05 美元)。训练数据用的正是 UniversalNER 蒸馏出来的 Pile-NER,但有个必要的补丁:Pile-NER 只标了句子里存在的类型,所以 GLiNER 要从同批次其他样本里采样负类型——不采负样本只有 53.3 F1(假阳性泛滥),采 50% 才到 60.9。

卡在哪:backbone 容量。DeBERTa 只有约 0.3B 参数、上下文窗口有限,编码的世界知识远不如大模型。碰到 CrossNER 的音乐、AI、文学这类需要领域常识的场景就吃力——GLiNER-L 在 CrossNER+MIT 上平均 60.9,比 JPT-8B 低 13.2 分。JPT 在定性对比里指出的典型症状是细粒度类型分不开:把五个政治人物统统标成 PERSON 而非 POLITICIAN,把「Empire of Japan」标成 ORGANIZATION 而非 COUNTRY。

与 JPT 的关系:这是结构上最接近 JPT 的前作——同样判别式、同样把类型和 token 投到共享空间做匹配、同样一次前向。JPT 本质上是在问:能不能把 GLiNER 的范式搬到 7B+ 的 decoder 上?唯一挡路的就是因果掩码,而输入复制正是为了绕开它。

OpenBioNERFindings of NAACL'25
JPT「定义制导」的直接来源:证明了用实体类型描述代替标签名,能显著提升罕见概念上的零样本表现。缺口是仍绑在小编码器和生物医学单一领域上。
GLiNER 用标签名 vs OpenBioNER 用类型描述的对比

OpenBioNER 原论文 Figure 1:同一个句子,上半用 GLiNER 的裸标签名(三个预测错两个),下半用类型描述(全对)。这张图是「为什么定义比标签名强」最直观的证据。

展开说明 ▸
  • 上半部分(GLiNER):输入是 [ENT] Cell Line [ENT] RNA [SEP] 加上句子「Extracts of the B-cell line, BJA-B, contain high levels of NF-A2 and specifically transcribe lg promoters」。
  • 它的三个预测:✅ BJA-B → Cell Line 对;❌ NF-A2 → RNA 错;❌ lg promoters → RNA 错。三个里错两个
  • 错在哪:NF-A2 是转录因子(蛋白质),lg promoter 是启动子(DNA 序列),两者都不是 RNA。但「RNA」这三个字母作为标签名,根本没告诉模型 RNA 的边界在哪,模型只能靠「跟基因表达沾边」这种表层联想。
  • 下半部分(OpenBioNER):同一个句子被送两次,每次拼一段类型描述。第一次拼「A cell line is a population of cells derived from a single cell, cultured in vitro or in vivo…」,输出 ✅ B-cell 和 ✅ BJA-B——注意它还多抓到了 GLiNER 漏掉的 B-cell
  • 第二次拼 RNA 的描述「RNA is a type of nucleic acid that plays a crucial role in the transmission of genetic information from DNA to…」,输出是空的。定义里「核酸」二字直接排除了转录因子和启动子,两个假阳性被干净地压掉。
  • 一句话总结:标签名只提供一个词的语义,描述提供的是可执行的判定边界——而 NER 的错误恰恰大量发生在边界上。JPT 把这个观察从生物医学搬到了通用领域,并加了第二条 prompt 通道。

它做了什么:OpenBioNER 面对的是生物医学 NER 的一个具体痛点——标签名根本不足以确定标注口径。同一个 Chemical 在不同本体里覆盖范围可能差很多(「aspirin」在一个数据集里是 Drug、另一个里是 Chemical),而这个领域的实体类型又特别多、特别细、特别长尾。它的方案是不再编码标签名,改为把类型的一段自然语言描述和输入文本拼成一条序列 [CLS] 文本 [SEP] 描述 [SEP],送进一个 cross-encoder。

cross-encoder 是关键选择:因为描述和文本在同一条序列里双向互相注意,同一个词在不同类型描述下会得到不同的向量表示。这是 GLiNER 那种「类型和 span 分开编码再点积」的双编码器结构在原理上做不到的。分类头轻到极致——一个 768×1 的线性层把每个 token 压成一个标量,各类型的标量拼起来 softmax 出 BIO 标签。

关键结论:110M 参数的 OpenBioNER 在 8 个生物医学 benchmark 上零样本平均 52.9 F1,超过 459M 的 GLiNER-large(51.9)和 UniNER-7B(49.9)。而最能说明问题的是罕见类型——JNLPBA-Rare 上 63.9 vs GLiNER 的 51.9,高出 12 分。罕见类型正是标签名信息量最低的地方。

卡在哪:三处。其一,backbone 是 BioBERT-base,只有 110M,世界知识正是它放弃的东西——它靠描述换泛化,而不是靠预训练知识。其二,评测全是生物医学,不是通用零样本 NER 模型。其三,cross-encoder 的代价是每个类型要单独跑一次前向,成本随类型数线性增长(论文自己的表显示类型多时反而比 GLiNER 慢),它的辩护是生物医学任务通常类型不超过 10 个。

与 JPT 的关系:这是 JPT「定义制导」部件的直接思想来源,论文明确说明了这一点。JPT 在两个方向上把它推远了:其一,从几亿参数的编码器搬到 4B/8B 的 decoder-only LLM 上;其二,从单通道扩成双通道——定义不只被编码成向量供分类器匹配,还原样写进 LLM 的 prompt,让注意力在编码 token 时就能读到。消融显示这第二条通道确实带来了额外收益(单通道 63.3–65.2,双通道 70.9)。

Echo Embeddings:Repetition Improves LM EmbeddingsICLR'25
和 JPT 最像的前作——同样把输入重复一遍来绕开因果掩码,但它把第二遍池化成一个句向量做检索。JPT 的差别正在这里:不池化,保留每个 token 的隐状态做细粒度分类。
同样的输入复制,分岔点在「第二遍的隐状态拿来干什么」 第一遍 x₁…xₙ 第二遍 x₁…xₙ → 因果 LLM 一次前向 第二遍的隐状态 h₁ … hₙ Echo Embeddings:池化 mean-pool → 一个句向量 用于检索 / MTEB JPT:不池化 保留每个 token 的向量 逐 token 分类 → NER span 同一个 trick 两种粒度 Echo Embeddings 概念图

Echo Embeddings 原论文 Figure 1:上半是问题(token 看不到自己的未来,虚线箭头被打叉),下半是解法(同一句写两遍,信息从第一遍的尾部流进第二遍的头部)。

展开说明 ▸
  • 上半部分标题「Autoregressive embeddings do not encode context from later tokens」,下面是一行六个方块,颜色从绿渐变到品红——渐变不是装饰,它表示句子前三个和后三个 token 内容不同。
  • 方块上方的实线弧箭头(从左指向右):合法的因果注意力,靠后的 token 可以看靠前的。
  • 方块下方的虚线弧箭头 + ✗(从右指向左):被禁止的方向。token 看不到自己的未来,所以靠前 token 的表示天然是残缺的——无论怎么池化都补不回来。
  • 下半部分「Repetition enables embeddings to encode context from later tokens」:同样的六个方块出现了两组,左边是第一遍、右边是第二遍,渐变完全相同,复制关系一目了然。
  • 关键的那条箭头:从第一遍的靠后位置斜着指向第二遍的第一个 token。这就是全部机制——第二遍的早期 token 合法地读到了第一遍的晚期内容。下半部分没有任何 ✗,因为不需要做任何违规的事,掩码一动没动,只是输入变长了。
  • 右下角的 “echo embeddings” 标注在第二组下方:只有第二遍被读出。而 JPT 与它的分岔点正在这里——Springer 等人把这些状态平均池化成一个向量,JPT 保留它们逐 token 的形态。

它做了什么:Echo Embeddings 解决的是句向量问题。用自回归 LLM 做 sentence embedding 有个结构性缺陷——句子前半 token 的表示里不含后半信息。作者用一个很干净的合成实验证明了这有多致命:构造两类句子,一类判别信息在前半、一类在后半,结果 mean pooling 在前者上 0.94、后者上只有 0.51(等于瞎猜),last-token pooling 恰好反过来(0.25 / 1.0)。两种池化各自在相反的结构上失败,所以换池化方式救不了。

解法:在 prompt 里让模型扮演自编码器,把句子写两遍——「Rewrite the following paragraph: x. The rewritten paragraph: x」——然后只对第二遍的位置做平均池化。零样本 MTEB 从 42.38 涨到 48.64,同一个冻结模型、同样的池化、零训练。这个数字基本追平了需要额外 MNTP 微调的 LLM2Vec(49.43)。

和 JPT 的关系,以及分岔点在哪:两者用的是同一个底层观察——重复输入可以在不改掩码的前提下换来双向上下文。但下游用途完全不同:Echo Embeddings 最终把第二遍压成一个全局句向量,为的是检索;这个动作恰好把 JPT 需要的东西全丢掉了。JPT 要的是不池化的、逐 token 隐状态,因为 NER 要给每个 token 单独打标签。

有个细节反过来印证了 JPT 的选择:Echo 在零样本下必须用 mean pooling(48.64),换成 last-token 只有 31.55。也就是说有用的信息是分布在第二遍各个 token 的状态里的,不集中在某一个位置——而这正是 JPT 直接拿来用的那份资源。

另一处差别:Echo 依赖「Rewrite / Repeat / Rephrase」这类自然语言指令来给第二遍一个复述第一遍的理由;JPT 只用一个分隔符、不给指令,等于在主张——只要下游接了可训练的分类头,位置带来的可见性本身就够了。论文在 Related Work 里也就此划清了界限:JPT 的贡献不是「发现重复输入有用」,而是把它用在细粒度判别式 token 分类上。

其他让因果模型获得双向上下文的路子,为什么都不能直接用?

让 decoder 拿到双向上下文并不是新问题,论文列了三条已有路子,并说明为什么它们都不能拿来即用:

  • Prefix-LM 注意力(T5、UL2):把序列分成前缀和后缀,前缀内部允许双向注意,后缀保持因果。问题是这个注意力模式必须在预训练时就存在——一个现成的因果 LLM 从没见过这种模式,直接切过去表示会崩。
  • Fill-in-the-Middle(FIM):预训练时把文档重排成 (prefix, suffix, middle),让模型学会在给定后文的条件下补中间。它确实提供了对未来上下文的条件化能力,但同样需要专门的预训练,而且目标是生成,不是逐 token 打标签。
  • 推理时直接把因果掩码去掉:最省事的想法,但通常会把事情变得更糟。模型从未在完整(非下三角)掩码下计算过注意力,注意力分布直接跑到分布外。LLM2Vec 的说法很直白:decoder 「was not trained to attend to future tokens」,所以这种朴素做法「甚至可能导致更差的表示」。他们的实测印证了这点——在 token 级任务(chunking、NER、POS)上「naively applying bidirectional attention dramatically hurts performance」,而且逐层比较发现去掩码前后的隐状态余弦相似度很低,即表示不是被扰动了,而是变成了另一种东西。Echo Embeddings 那边也有独立数据:LLaMA-2-7B 上「去掩码」比老老实实用因果掩码更差(43.03 vs 47.27)。

一个需要如实说明的例外:Mistral 系列是个特例——对它去掩码几乎不掉分,NER 上甚至略有提升,逐层表示相似度也保持很高。两篇论文给出了同一个猜测:Mistral 的预训练里可能混入了某种双向或 prefix-LM 成分。所以准确的说法不是「去掩码一定失败」,而是它不可靠、且依赖你恰好挑中哪个模型——而一个只在特定 backbone 上成立的做法,很难称之为方法。

JPT 的做法绕过了以上全部前提:掩码是标准的下三角,权重是现成的,什么都不用重新预训练。它只是让同一句话在序列里出现两次,于是第二次出现时,模型在完全常规的因果计算下就自然拥有了完整上下文。这也是为什么论文反复强调「requires no architectural modifications」——这不是修辞,而是它相对上述三条路子的核心区别。

值得一提的是,FIM 在机制上其实是三者里离 JPT 最近的:它同样只靠重排 token 序列、不碰掩码。区别在于 FIM 的重排只有被它训练过的模型才看得懂(<PRE>/<SUF>/<MID> 这些标记不在普通模型的词表里),而「把话说两遍」对任何因果 LLM 都是合法的——第二遍不过是一段前面碰巧有相同内容的普通文本。

把这些放在一起,缺口就很清楚了:要么有双向上下文但模型太小(编码器),要么模型够大但只能生成(LLM),要么需要改预训练(Prefix-LM / FIM)。JPT 想同时拿到「大模型的知识」和「双向的上下文」,而且不改架构、不重新预训练——剩下唯一能动的地方,就是输入本身。

JPT 怎么做

JPT 由两个正交的部件拼成:输入复制解决「看不到后文」,定义制导实体类型解决「怎么零样本泛化到没见过的类型」。最后用一个双线性分类器把 token 和类型在共享空间里对齐。

JPT 架构图:实体类型编码通路与 token 通路

图 2(论文原图):上半部分是实体类型编码通路(可离线预计算并缓存),下半部分是token 通路。雪花 = 冻结,火焰 = 可训练。

展开说明 ▸
  • 左上「TYPES DEFINITIONS」:输入不是 PER / ORG / LOC 这些标签名,而是每个类型对应的一段自然语言定义(“A Person…”“An Org…”)。注意最后还有一个 O 类,定义是「not an Entity」——非实体也被当成一个有定义的正常类别参与匹配。
  • Text Encoder(蓝色,冻结):把每条定义编码成向量 ePER, eORG, …。论文用 Qwen3-Embedding-8B(denc=4096),也可换成 OpenAI text-embedding-3-small。因为定义在推理时固定不变,这一整条通路可以离线算完存下来,运行时零开销
  • Entity Projection MLP(火焰,可训练):把 denc 降到共享维度 dp=256,得到实体嵌入 PPER, PORG, PLOC, PO
  • 左下「INPUT」的两个括号:这是全图最关键的细节。序列被分成 Context(第一遍)「Paris / called / yesterday」、一个紫色 [SEP]、以及 Target(第二遍)同样的「Paris / called / yesterday」。两段内容完全相同,区别只在位置。
  • Causal LLM(蓝色冻结 + 橙色 LoRA Adapter):Qwen3-4B / 8B 主干全程冻结,只在注意力的 q/k/v/o 投影上挂 LoRA。整条 2N+1 长度的序列一次前向跑完。
  • 「RAW TOKEN EMBEDDINGS (h)」的灰白分色:h₁–h₄(第一遍三个 token + SEP)是灰色——算了但丢弃;h₅–h₇(第二遍)是白色——只有它们进入下游。原因是只有第二遍的隐状态携带了完整双向上下文。
  • Token Projection MLP(可训练):把 dllm(4B 是 2560,8B 是 4096)降到同一个 dp=256,得到 t₁, t₂, t₃。至此 token 向量和类型向量住进了同一个空间。
  • BILINEAR CLASSIFIER:接收下方的 t 和上方粗箭头送来的 P,对每个 (token, 类型) 组合算一个匹配分。右侧输出的小柱状图就是每个 token 在所有类型上的得分分布——第一个 token 判为 PER,后两个判为 O
  • 一句话总结:把 NER 变成「token 向量」和「类型定义向量」在 256 维空间里的匹配问题,而 token 向量的双向性靠输入复制白拿。

部件一:输入复制

给定输入 x = (x₁, …, xₙ),JPT 构造这样一条序列:

x′ = ( x₁, …, xₙ , [SEP] , x₁, …, xₙ )
└─── 第一遍 ───┘      └─── 第二遍 ───┘
注意力覆盖:第二遍位置 k 的 token 在 x′ 中处于第 n+1+k 位,因果掩码允许它看到所有 j ≤ n+1+k 的位置,其中包含了第一遍的全部 n 个 token

这就是全文的核心 trick,而且它没有修改任何注意力机制——掩码还是标准的下三角,模型还是那个模型。变的只是「同一句话在序列里出现了两次」,于是第二次出现时,它的每个 token 天然排在完整句子之后。

下面这个演示可以直接感受这件事。点击任意 token,看它在因果掩码下能看到哪些位置:

交互演示:谁能看到谁

点击一个 token 作为「查询」,高亮的是它在因果掩码下可以 attend 到的全部位置。

切到 JPT 模式点第二遍的 Paris 就能看到:它前面站着一整句完整的话。「未来上下文」被搬到了它的过去。

论文里还有一个容易忽略但很实在的实现细节:所谓的 [SEP] 在真实 prompt 里不是特殊 token,而是一句大白话。整条输入长这样:

完整 prompt 模板(论文 Figure 6)——注意那个「假的」assistant 轮次
<|im_start|>system
You are an information-extraction assistant.
Task: Perform Named Entity Recognition (NER) on the user-supplied text.
The user will give you the supported entity types and their definitions.
The user will give you the text twice in the format
  "The first time: 'actual text' The second time: 'actual text'".
Rules: (1) Keep multi-word entities together; (2) Only use provided types;
(3) Output once; (4) No bare-noun labelling; (5) Output types exactly as
listed; (6) Only label if clearly matches definition.
<|im_end|>
<|im_start|>user
Supported entity types (3): ["PERSON", "ORGANIZATION", "LOCATION"]
Entity type definitions:
- "PERSON": "A named individual, including fictional characters"
- "ORGANIZATION": "A company, institution, or group with a formal name"
- "LOCATION": "A geographical place such as a city, country, or landmark"
<|im_end|>
<|im_start|>assistant
I have read the definitions. Please provide the text in the format
'The first time: ... The second time: ...'
<|im_end|>
<|im_start|>user
The first time: '<Input_Sequence>'  The second time: '<Input_Sequence>'
<|im_end|>

几点值得注意:(1) 分隔符就是 ' The second time: ' 这句自然语言,不需要新增词表;(2) 中间那轮 assistant 回复是硬编码的,作用是让对话格式合法、并把定义和文本分成两个 user 轮;(3) system prompt 里写满了「输出格式」规则,但模型其实什么都不生成——这些规则只是为了让分布贴近 backbone 熟悉的指令模式;真正的输出来自分类头。

部件二:定义制导的实体类型

第二个设计针对零样本泛化。常规做法是把类型名(PERSON)编码成向量,这依赖标签名本身的表层语义;JPT 改成编码一整段定义

pj = MLPentity( Embed(defj) ) ∈ ℝ256

好处是解耦了模型和固定标签词表——新类型在推理时用一句话描述即可,无需重训。更进一步,定义给了用户对边界情况的直接控制权。论文举的例子:

LOCATION: “Any word indicating WHERE: explicit place names (Boston, downtown), relative indicators (nearby, around), directional words (east, south side).”

写不写「relative indicators」,直接决定了 nearby 会被标成地点还是当普通形容词忽略。这把 JPT 从「一个固定的 NER 模型」变成了可控的信息抽取器

定义走两条通道同时注入,这是论文强调的「dual-channel」:

通道一:embedding 向量

定义被文本编码器编码成 pj,供分类器与 token 向量做匹配。作用在分类阶段

因为定义推理时不变,这条通路可离线算好缓存,不增加任何运行时延迟

通道二:prompt 文本

定义列表原样写进 LLM 的输入 prompt。作用在编码阶段——LLM 的注意力在处理每个 token 时可以直接看这些定义。

消融显示两条通道互补:单用任一条只有有限提升,合起来比不用定义高 +12.6 F1

部件三:双线性匹配与输出

token 向量 ti 和类型向量 pj 都在 256 维共享空间里,打分方式是一个双线性形式:

sij = ti W pj + bj    →    ŷi = argmaxj ∈ {O, 1, …, N} sij
W ∈ ℝ256×256 可学习,bj 是类型专属偏置。加上显式的 O 类共 N+1 路分类。推理时把连续的同类 token 合并成 span,类型发生变化处即为边界。

注意 O 类不是「其他」兜底桶,而是一个有定义的正常类别(“A token that is not part of any named entity”),同样走 embedding 通路。

实现细节:其实是双分类头 ensemble,而非单个双线性打分器

论文正文为简洁只写了单个双线性打分器,实际实现用了两个共享同一套 t 和 p 的头,最终预测取两者概率的平均:

  • Softmax 头:把标签当互斥的多分类(含显式 O 类),交叉熵训练,O 类权重降到 wO=0.25 来对抗类别不平衡。特点是预测果断。
  • Sigmoid 头:每个实体类型当独立的二分类,focal loss(γ=2.5,正样本权重 5.0)训练,聚焦难例、上采样罕见实体。特点是更善于处理罕见类型。

这个 ensemble 比单 softmax 头提升 1–2 F1。论文明确说明核心方法(输入复制 + 定义制导)与这个选择无关。有意思的是,sigmoid 头的独立按类概率也是论文提到的未来支持嵌套实体的路径:让重叠 span 同时输出多个概率 > 0.5 的类型。

一次完整推理的数据流

步进演示:JPT 处理一个句子
离线:定义 → pj
拼接 x [SEP] x
一次 prefill
取第二遍 hi
投影 → ti
双线性打分
合并 span

训练:只动 2% 不到的参数

backbone 全程冻结,可训练的只有三样:注意力投影上的 LoRA 适配器、两个投影 MLP、以及双线性分类器。合计 JPT-4B 是 38.8M(0.95%),JPT-8B 是 142.8M(1.71%)。4×H100 上训 5 个 epoch 只要约 1.5 小时。

损失只在第二遍的 token 上计算——第一遍的隐状态从头到尾只是给第二遍当上下文用的,不参与监督。

训练数据:一个用 Claude 自动标注的 Wikipedia 语料,与所有评测集零重叠

训练数据的构造方式本身挺值得一看,而且直接支撑了「定义制导」的动机:

  • 来源:Wikipedia 正文,取自 TELEClass benchmark 里 DBpedia 语料的 test 分区。每段文字附带 DBpedia 本体的三级主题层级(如 agent → athlete → chess_player)。
  • 第一阶段·类型生成:把段落和它的主题层级交给 Claude Sonnet 4.5(开 extended thinking),让它提出该领域适用的实体类型及其定义(体育文章就提 Athlete / Team / Stadium)。
  • 第二阶段·实体检测:模型标出 span,再跑一轮 gap-detection 补漏标。
  • 质检:Claude Opus 4.5 抽样评估类型是否恰当、定义是否可执行、抽取是否准确。
  • 规模:17,489 句、3,391,899 token、374,705 个实体提及、5,009 个不同实体类型,实体/非实体比约 1:1.5。

为什么类型数这么夸张地多?因为类型分布是长尾的——中位数每个类型只有 4 次提及,71% 的类型出现不超过 10 次。这种分布下模型根本没法靠记忆频繁模式取胜,只能被迫去读定义的语义。长尾在这里是特意设计的训练信号,不是数据缺陷。

训练集里出现的类型包括 EARTHQUAKETIMEZONERACECATEGORY 这类相当细的类别。作者强调这份数据与任何评测 benchmark 都无重叠——即使 PERSONLOCATION 这类常见类型出现过,用的也是不同的定义,所以评测确实是零样本的。

输入翻倍了,为什么反而快 20 倍

这是最容易被直觉误导的一点:JPT 把序列长度从 N 变成了 2N,听起来应该更慢。但对熟悉推理栈的人来说,答案一句话就够——它把开销从 decode 阶段挪到了 prefill 阶段。

生成式方法:卡在 decode

延迟随输出长度增长。每生成一个 token 都要把整个模型权重从 HBM 读一遍,只为做一步计算——串行、memory-bound、算力大量闲置

UniNER 更极端:它一次只查一个实体类型,17 个类型的 CrossNER-AI 就是 17 轮完整解码。

JPT:全部在 prefill

2N 个 token 一次性并行算完,compute-bound、GPU 利用率高。之后没有任何自回归步骤,分类头一次出全部标签。

相当于拿便宜的并行输入 token换掉了昂贵的串行输出 token——这也正是 API 定价里输出比输入贵 3–5 倍的同一个原因。

实测(CrossNER-Politics,同一张 A100,batch size = 1):

方法类别成本 ($) ↓耗时 (秒) ↓F1 ↑
UniNER-7B生成式2.771970.261.8
GNER生成式8.215831.575.8
GPT-5生成式 API0.49579.667.1
GLiNER-L判别式编码器0.0533.360.9
JPT-4B判别式 LLM0.1389.776.4
JPT-8B判别式 LLM0.21146.277.0

最有说服力的对比是 GNER:它是生成式里精度最高的(75.8 F1),但要跑 5831 秒、花 8.21 美元。JPT-8B 精度略高(77.0),耗时是它的 1/40,成本是 1/39。而 GLiNER-L 虽然最便宜最快,精度落后 16 分——JPT 拿到的是「编码器量级的成本 + 超过最强生成式的精度」

注:论文 Table 3 的 F1 列口径不完全一致——表注写的是 CrossNER-Politics,而 UniNER-7B(61.8)与 GLiNER-L(60.9)对应的是 Table 1 里 CrossNER+MIT 的平均值。跨方法比较请以下一节 Table 1 的完整结果为准;这张表真正无歧义的是成本与耗时两列。

有意思的细节

注意力热力图:能直接看到「回头看」发生了

论文最漂亮的一张证据图。输入 “The Eiffel Tower is in Paris, France.”,纵轴是第二遍的 token,横轴是第一遍的 token,格子颜色是跨层平均的注意力权重。

第二遍 token 对第一遍 token 的注意力热力图

图 3(论文原图):亮点不在对角线,而在第 5 列——「The / E / iff / el」这几个 token 全都强烈地看向后面才出现的「Tower」。

展开说明 ▸
  • 坐标轴含义:每一行是第二遍里的某个 token(作为 query),每一列是第一遍里的某个 token(被 attend 到)。颜色越深红,注意力权重越大(0 到 1)。
  • 「Tower」那一列的深红竖条:这是全图最关键的现象。“The”“E”“iff”“el” 四个 token 在原句里都排在 “Tower” 之前;标准因果注意力下它们绝无可能看到 “Tower”。可现在它们的注意力集中打在了 “Tower” 上。
  • 为什么这件事重要:“E / iff / el” 是 “Eiffel” 被切碎的 subword,单独看毫无意义。要判断它们属于 LANDMARK 实体,必须先知道整个短语是 “The Eiffel Tower”。输入复制让这些残缺 token 得以提前把词补全
  • 右下角的对角线:“Paris → Paris”、“France → France”、“. → .” 这些格子也很深。说明每个 token 也在强烈关注自己在第一遍的对应位置——第一遍事实上充当了一份可随机访问的「完整句子备份」。
  • 右上角大片浅色:早期 token 对句末内容的注意力较弱,说明模型不是无差别地把注意力摊平,而是有选择地取语义相关的上下文
  • 一句话总结:这张图把「输入复制 = 免费的双向注意力」从一个论证变成了可观测的现象——不完整的 token 用第一遍来「向前看」,从而拿到准确的边界检测和类型判断。

定义写得好不好,比模型大小更重要

论文附录里有一张容易被略过但信息量极大的表:同一个模型,只改实体类型的定义文本,F1 能差出几十分。

类型笼统的定义精确的定义ΔF1
LOCATION “A geographical place” “Any word indicating WHERE: explicit places (NYC, downtown), relative indicators (nearby, around, close by), directional phrases (east, south side). Tag the location word itself.” +32.6
PRICE “A monetary value” “Explicit monetary amounts ($50, 100 dollars) AND qualitative price indicators (cheap, expensive, budget-friendly, overpriced, pricey).” +34.9
AMENITY “An available service” “A feature, facility, or service offered by a restaurant. Includes: physical features (bar, parking), services (delivery, reservations, takeout), atmosphere descriptors (romantic, casual, family-friendly).” +14.3

MIT-Restaurant 上把 LOCATION 的定义从「一个地理位置」改成明确包含 “nearby” 这类相对指示词,单类型 F1 涨了 32.6 分。这说明所谓的「模型识别不出来」,很多时候其实是标注口径没说清——benchmark 的 gold label 有自己的隐含约定,而定义正好是把这些约定显式写出来的接口。

论文因此给了三条定义撰写守则:说清包含什么和排除什么给具体例子直接点名边界情况(“nearby” 算不算地点?)。

消融:两个部件各值多少分

配置平均 micro-F1说明
单次输入(去掉复制)55.7专门用单输入重新训练的变体,不是推理时才关掉复制
不用定义(只用类型名)58.3退回传统的标签名编码
定义只进 prompt63.3单通道
定义只进 embedding65.2单通道
双通道定义 + 输入复制(完整 JPT-4B)70.9输入复制 +15.2,双通道定义 +12.6

值得注意的是单输入变体是重新训练的,而不是训练时用双份、推理时砍成单份。作者特意说明这一点,是为了排除「训练/推理不匹配」这个混淆因素——所以 −15.2 F1 确实来自缺少双向上下文本身。

单输入模型具体错在哪:四个典型例子

论文分析了单输入变体的失败模式,规律非常一致:实体出现在句首、而决定性线索在句尾时必错

句子(实体在句首)正确类型单输入预测为什么必须看后文
Autoencoders are trained to minimise…”algorithmproduct要看到 “trained to minimise” 才知道说的是算法
François Bayrou (…) is a French centrist politician…”politicianperson要看到后面的 “politician” 才能定到细粒度类型
The Color Purple is a 1982 epistolary novel…”book(漏标)要看到 “novel” 才能认出这是书名
Young’s work on squid giant axons…”scientist(漏标)要看到后面的科研语境才能判为科学家

两类错误各占一半:类型混淆(标出了 span 但类型错)和完全漏标(压根没识别出实体)。第二类更能说明问题——缺了后文,模型连「这里有个实体」都不敢确认。

和自己的 backbone 比:涨的是框架,不是模型

一个干净的对照实验:拿同一个 Qwen3-4B,一边按 JPT 框架用,一边纯生成式地 prompt 它做 NER。CrossNER 五个域平均,生成式 57.4 vs JPT-4B 74.4+17.0 F1。同一套权重、同样的世界知识,差距全部来自输入复制 + 定义制导 + 判别式分类这三件事。

实验结论

主结果在 CrossNER(AI / 文学 / 音乐 / 政治 / 科学五个专业域,每域 9–17 个领域特定类型)和 MIT Movie / Restaurant(对话式查询的槽位填充)上。这些数据集全部是零样本——训练语料与它们无重叠。

方法AI文学音乐政治科学MovieRest.平均
UniNER-7B 生成62.964.970.666.970.861.235.261.8
GoLLIE 生成59.162.767.857.255.563.043.458.4
InstructUIE 生成49.047.253.248.249.363.021.047.8
SaM (MoE) 生成60.966.973.574.462.672.152.966.2
GLiNER-L 判别57.264.469.672.662.664.442.960.9
JPT-4B68.373.784.176.469.560.763.470.9
JPT-8B71.972.285.377.071.376.564.474.1

JPT-8B 平均 74.1,比最强基线 SaM 高 7.9 分。提升最大的三个域是音乐(+11.8)、Restaurant(+11.5)、AI(+11.0)——都是术语高度专业化的场景,正好是 LLM 世界知识发挥作用的地方。连 JPT-4B 都超过了所有基线(+4.7)。

扩展到 20 个数据集(涵盖生物医学、社交媒体、多语言)后,JPT-4B 平均 55.5,对比 GLiNER-L 47.8 和 UniNER-7B 45.7,20 个里赢了 19 个。唯一输的是 GENIA(生物医学嵌套实体,50.8 vs GLiNER 55.5)——这与下文的「不支持嵌套实体」局限直接对应。

定性对比:JPT 对、GLiNER 和 UniNER 错的四个例子

论文挑了 CrossNER-Politics 上的几组对照,能看出定义制导在解决什么:

  • 荷兰大学(应为 ORGANIZATION):“…faculty members from the Vrije Universiteit, University of Amsterdam, Delft University of Technology, and Leiden University.” GLiNER 和 UniNER 四个全漏,JPT 全对。
  • 加拿大政党(应为 POLITICAL PARTY 而非 ORGANIZATION):GLiNER 两个都漏;UniNER 因为逐类型推理,同一个 mention 同时被打上 ORGANIZATION 和 POLITICAL PARTY,过度预测
  • 美国政治人物(应为 POLITICIAN 而非 PERSON):GLiNER 把 Lincoln、Buell 等五人全标成 PERSON——类型正确但粒度不够。
  • Empire of Japan(应为 COUNTRY 而非 ORGANIZATION):GLiNER 标成 ORGANIZATION。

共同点是语义相邻类型的区分:POLITICIAN vs PERSON、POLITICAL_PARTY vs ORGANIZATION、COUNTRY vs ORGANIZATION。这些区分靠标签名本身很难做,但靠一句定义可以说清。另外注意 UniNER 的过度预测是它「一次查一个类型」的结构性后果——每个类型独立判断,自然没人保证互斥。

JPT 自己错在哪:边界检测是头号失败模式

作者做了完整的错误分析,主要有三类:

1. 边界检测错误(最常见):类型判对了、核心 mention 也对,但 span 多切或少切一截,通常是被修饰语或同位语带偏:

数据集Gold span预测 span
CrossNER-ScienceVirgo interferometerVirgo interferometer collaboration
CrossNER-PoliticsSocial Credit Party of Canadanational Social Credit Party of
MIT-Moviethe reflecting skinreflecting skin movie
MIT-Restaurantwithin a milea mile of here

2. 类型混淆:集中在语义相邻的类别之间。例如 “NIST” 被判成 CONFERENCE 而非 METRICS(缩写歧义)、“Augustus” 判成 POLITICIAN 而非 PERSON(角色 vs 实体)、“London Calling” 判成 LOCATION 而非 ALBUM(专辑名恰好是地名)。

3. 过度预测:领域术语长得像实体但 gold 里没标,比如把 “frame language” 标成编程语言。

实体类型混淆矩阵

混淆矩阵(论文 Figure 8)印证了这一点:错误几乎全部落在语义相邻的类型对上——PER↔ORG、LOC↔COUNTRY、POL_PARTY↔POLITICIAN——而不是随机的标签乱翻。作者的结论是这些错误源于表层歧义和定义重叠,而非缺乏上下文理解;对应的解法也很直接:把定义写得更细,或补充针对性训练样本。

局限与适用边界

论文自己的收尾很克制,但落点很准:因果注意力的约束,并不意味着 LLM 只能走生成式路线。绕过它不一定需要改架构或改预训练,有时候改一下输入就够了。而且作者点出这个思路不限于 NER——任何 token 级任务(POS、chunking、span 抽取)都可以照搬。