AiPaper
论文状态:已完成

TokDrift: When LLM Speaks in Subwords but Code Speaks in Grammar

发表:2025/10/17
原文链接PDF 下载
价格:0.10
价格:0.10
已有 8 人读过
本分析由 AI 生成,可能不完全准确,请以原文为准。

TL;DR 精炼摘要

本文提出TokDrift框架,通过语义等价重写生成仅分词不同的代码变体,揭示主流代码大模型中子词分词与语法词元边界错位问题。实验表明微小格式差异显著影响模型行为,强调未来代码模型需采用感知语法的分词策略。

摘要

Large language models (LLMs) for code rely on subword tokenizers, such as byte-pair encoding (BPE), learned from mixed natural language text and programming language code but driven by statistics rather than grammar. As a result, semantically identical code snippets can be tokenized differently depending on superficial factors such as whitespace or identifier naming. To measure the impact of this misalignment, we introduce TokDrift, a framework that applies semantic-preserving rewrite rules to create code variants differing only in tokenization. Across nine code LLMs, including large ones with over 30B parameters, even minor formatting changes can cause substantial shifts in model behavior. Layer-wise analysis shows that the issue originates in early embeddings, where subword segmentation fails to capture grammar token boundaries. Our findings identify misaligned tokenization as a hidden obstacle to reliable code understanding and generation, highlighting the need for grammar-aware tokenization for future code LLMs.

思维导图

论文精读

中文精读

1. 论文基本信息 (Bibliographic Information)

  • 标题 (Title): TokDrift: 当大语言模型讲子词,而代码讲语法 (TokDrift: When LLM Speaks in Subwords but Code Speaks in Grammar)
  • 作者 (Authors): Yinxi Li, Yuntian Deng, Pengyu Nie。他们均来自滑铁卢大学 (University of Waterloo)。
  • 发表期刊/会议 (Journal/Conference): 这篇论文目前是预印本 (Preprint),发布在 arXiv 上。arXiv 是一个广泛使用的学术论文预发布平台,让研究者可以快速分享他们的最新成果,但尚未经过同行评审 (Peer Review)。
  • 发表年份 (Publication Year): 2025 (根据 arXiv 编号推测,这是一个未来的占位符,实际提交日期为 2024 年 10 月)。
  • 摘要 (Abstract): 用于代码的大语言模型 (LLM) 依赖于从自然语言和编程语言混合文本中学到的子词分词器 (Subword Tokenizer),例如字节对编码 (BPE)。然而,这种分词器是基于统计而非语法驱动的。因此,语义上完全相同的代码片段,可能会因为空格或标识符命名等表面因素而被不同地分词。为了衡量这种“错位”的影响,我们引入了 TokDrift 框架。该框架通过应用保持语义的重写规则,来创建仅在分词上有所不同的代码变体。我们测试了九个代码 LLM(包括超过 300 亿参数的大模型),发现即使是微小的格式变化也会导致模型行为发生巨大转变。通过逐层分析,我们发现问题源于早期的嵌入层,在这些层中,子词分割未能捕捉到语法的词元边界。我们的发现揭示了,分词错位是实现可靠代码理解和生成的一个隐藏障碍,并强调了未来代码 LLM 需要采用感知语法的分词方法。
  • 原文链接 (Source Link):

2. 整体概括 (Executive Summary)

  • 研究背景与动机 (Background & Motivation - Why):

    • 核心问题: 现代代码大语言模型 (Code LLMs) 在处理代码时,第一步是使用子词分词器(如 BPE)将代码文本切分成一系列 tokens。然而,这些分词器是基于数据中字符序列出现的频率进行统计学习的,它们并不理解编程语言 (Programming Language, PL) 的语法规则。这就导致了一个根本性的**“错位” (Misalignment)**:LLM “看到”的 token 边界与代码语法定义的 token 边界(如关键字、标识符、操作符)不一致。
    • 问题重要性: 这种错位意味着,两个在功能上完全等价的代码片段,仅仅因为一些无关紧要的格式差异(比如多一个空格,或者变量名大小写风格不同),就可能被分词成完全不同的 token 序列。既然 LLM 的所有计算都始于这些 token,那么这种不一致性很可能会影响模型的理解和生成能力,导致其表现不稳定、不可靠。例如,math.factorial 可能被分为 ['math', '.', 'factorial'],而 math .factorial 可能被分为 ['math', ' .', '_factorial'],这会给模型带来困扰。
    • 创新切入点: 现有研究要么在设计新的分词器,要么在生成时强制施加语法约束,但很少有工作系统性地量化 (quantify) 现有主流模型受这个“错位”问题影响的严重程度。本文的切入点就是设计一个可复现的框架 TokDrift,通过**“语义保持的微扰” (semantic-preserving perturbations)** 来精确测量这种分词变化对模型最终输出的实际影响。
  • 核心贡献/主要发现 (Main Contribution/Findings - What):

    • 提出了 TokDrift 框架: 这是一个用于量化 LLM 对分词变化敏感度的系统性框架。它通过应用一系列保持代码语义不变的重写规则(如修改变量命名风格、增删空格),生成功能相同但分词不同的代码变体,并测量模型输出的稳定性。
    • 大规模实证研究: 论文对三大系列(Llama-3, Qwen2.5-Coder, DeepSeek-Coder)、九个不同尺寸的模型,在三类编程任务(代码修复、代码摘要、代码翻译)上进行了广泛实验。
    • 关键发现 1 (普遍存在且影响显著): 所有被测试的模型,无论大小,都表现出对分词变化的敏感性。即使是微小的格式改动,也能导致模型输出的正确性发生翻转,在某些规则下,模型的表现变化率可高达 60%
    • 关键发现 2 (定位问题根源): 通过对模型隐藏状态的逐层分析,论文发现这种不稳定性源于模型的早期嵌入层。在这些底层,子词分词的差异导致了截然不同的初始表示,这种差异在后续的层中持续存在,最终影响了输出。
    • 呼吁未来方向: 研究结果明确指出,分词错位是阻碍代码 LLM 可靠性的一个关键因素,并强调了开发**“语法感知分词” (grammar-aware tokenization)** 的必要性。

3. 预备知识与相关工作 (Prerequisite Knowledge & Related Work)

  • 基础概念 (Foundational Concepts):

    • LLM 分词 (LLM Tokenization): LLM 无法直接处理原始文本。分词是将其转换为模型能理解的离散单元(token)的过程。现代 LLM 普遍使用子词分词 (Subword Tokenization) 算法,如 字节对编码 (Byte-Pair Encoding, BPE)。BPE 的工作原理是:从最小的单元(如字符或字节)开始,反复合并语料库中出现频率最高的相邻单元对,直到达到预设的词汇表大小。这种方法的优点是能有效处理未登录词 (Out-of-Vocabulary),但缺点是其合并逻辑完全基于统计频率,不考虑语义或语法结构。例如,一个变量名 sortedList 可能会被分成 ['sorted', 'List'],也可能被分成 ['sort', 'ed', 'List'],这取决于训练数据。
    • 编程语言分词 (PL Tokenization): 在编译器或解释器中,这个过程被称为词法分析 (Lexing)。它根据编程语言的语法规则,将字符流转换成一系列有明确语法意义的 token,如 标识符 (identifier)关键字 (keyword)操作符 (operator)字面量 (literal) 等。这个过程是确定性的 (deterministic),只要代码语法有效,x+1x+1 永远会被解析成 xx (标识符), + (操作符), 1 (字面量) 三个 token,不受空格等格式影响。
    • 抽象语法树 (Abstract Syntax Tree, AST): 词法分析之后,编译器会进行语法分析 (Parsing),将 token 序列构建成一个树状结构,即 AST。AST 表达了代码的层次化句法结构。如果两个代码片段的 AST 相同,那么它们的语义就是等价的。本文的“语义保持”重写规则,其本质就是不改变代码 AST 的转换。
  • 前人工作 (Previous Works):

    • 分词器设计: 一些研究尝试改进分词器使其更适合代码。例如,CodeBPE (Chirkova and Troshin, 2023) 探索了不同的子词化选项以更好地对齐代码语法。另一些研究发现,允许 token 跨越空格边界可以产生更有意义的单元。这些工作表明分词器设计本身对模型性能有影响。
    • 表示变化的鲁棒性: 有研究表明,经过指令微调的模型对非标准的分词(如字符级)具有一定的鲁棒性,但性能仍会下降。而 Wang et al. (2025) 的研究发现,通过对抗性攻击改变 token 边界会显著降低模型性能。这说明 LLM 对表面表示仍然敏感。
    • 语法感知的代码建模: 为了解决分词与语法的错位问题,一些工作在模型生成 (decoding) 阶段引入语法约束。例如 SynchromeshPICARD 在生成每个 token 时,会实时检查语法合法性,过滤掉会导致语法错误的 tokenSynCode 则通过预先构建一个基于确定性有限自动机 (DFA) 的掩码来提高效率。
  • 技术演进 (Technological Evolution): 代码 LLM 的研究正在从单纯追求在基准测试上提高分数,转向关注模型的可靠性 (reliability)鲁棒性 (robustness)。早期模型主要关注如何从海量代码数据中学习,而现在,研究者们越来越意识到模型内部表示与代码内在结构(如语法、数据流)对齐的重要性。本文的工作正处于这一演进脉络中,它通过量化一个基础性问题(分词错位),推动社区关注更深层次的建模挑战。

  • 差异化分析 (Differentiation):

    • 分词器设计语法感知生成等**“解决方案” (solution-oriented)** 的工作不同,TokDrift 是一个**“诊断性框架” (diagnostic framework)**。它不提出新的模型或方法,而是提供了一套标准的测量工具和指标,用于评估现有模型在这个特定问题上的脆弱程度。
    • 相较于其他研究鲁棒性的工作,TokDrift 关注的是由分词器自身特性引起的、在日常编码中非常普遍的变化(如格式和命名),而不是集中的对抗性攻击。这使得它的发现更具普遍性和实践意义。

4. 方法论 (Methodology - Core Technology & Implementation Details)

本论文的核心方法论是其提出的 TokDrift 框架,用于量化 LLM 对分词变化的敏感度。

  • 方法原理 (Methodology Principles):

    • 核心思想: 如果一个 LLM 真正理解了代码的语义,那么对于两个功能完全相同、仅有无关紧要的表面差异(如空格、命名风格)的代码输入,它应该给出功能相同的输出。
    • 直觉: TokDrift 框架通过创建这样的代码对(一个原始版本,一个重写后的变体版本),来检验 LLM 是否满足上述的“语义一致性”假设。当 LLM 对这两个版本的输入给出了功能不同的输出时(例如,一个正确,一个错误),就暴露了模型对表面分词变化的敏感性。
  • 方法步骤与流程 (Steps & Procedures): TokDrift 的工作流程如下图所示,可以分为以下几个步骤:

    该图像是论文中的示意图,展示了通过语义保持的重写规则(rewrite rule)生成代码变体后输入至大语言模型(LLM),其输出正确性发生变化的过程。 该图像是论文中的示意图,展示了通过语义保持的重写规则(rewrite rule)生成代码变体后输入至大语言模型(LLM),其输出正确性发生变化的过程。

    • 步骤 1: 获取基准输入 (Baseline Input): 从一个编码任务的数据集中取一个原始代码样本。
    • 步骤 2: 应用重写规则 (Apply Rewrite Rule): 选择一个预定义的、保持语义不变的重写规则 (semantic-preserving rewrite rule),并将其应用于原始代码,生成一个变体代码 (variant code)。这个变体在功能上与原始代码完全等价,但其文本表示发生了改变,从而导致 LLM 分词器产生不同的 token 序列。
    • 步骤 3: 模型推理 (LLM Inference): 将原始代码和变体代码分别输入到同一个 LLM 中,并让模型生成输出(例如,修复后的代码、代码摘要或翻译后的代码)。
    • 步骤 4: 评估输出正确性 (Evaluate Outputs): 使用任务自带的评估脚本(通常是单元测试)来判断两个输出的功能正确性 (functional correctness),即输出是“正确”还是“错误”。
    • 步骤 5: 测量行为变化 (Measure Behavior Change): 比较原始输入的输出结果和变体输入的输出结果。如果一个结果是正确的而另一个是错误的,就认为模型在该样本上的行为发生了**“翻转” (flip)**。通过统计翻转的频率来量化模型的敏感性。
  • 数学公式与关键细节 (Mathematical Formulas & Key Details): 本部分的核心在于重写规则 (Rewrite Rules) 的设计。这些规则被精心设计以确保它们在 Java 和 Python 的语法下是语义保持的。论文将规则分为两大类:

    • 命名约定规则 (Naming Convention Rules):NN 开头,共 6 条。这些规则改变标识符的大小写风格,例如从驼峰命名法 (camelCase) 改为蛇形命名法 (snake_case)。

    • 空格约定规则 (Spacing Convention Rules):SS 开头,共 18 条。这些规则在操作符、括号、标识符等语法单元之间插入空格。

      以下是论文 Table 3 中定义的重写规则的转录,其中 JJ 表示适用于 Java,PP 表示适用于 Python。

      编号 适用 PL 重写规则 描述 示例
      N1 J camelCasesnake_case 将标识符从最常见的风格转换 sortedListsorted_list
      N2 J camelCasePascalCase closestPairClosestPair
      N3 J camelCaseSCREAMING_CASE possibleSolutionsPOSSIBLE_SOLUTIONS
      N4 P snake_casecamelCase input_clipboardinputClipboard
      N5 P snake_casePascalCase string_xorStringXor
      N6 P snake_caseSCREAMING_CASE triangle_areaTRIANGLE_AREA
      S1 P OP-OPOP - 在操作符和减号间加空格 [::-1][:1][: -1]
      S2 P OP[OP [ 在操作符和左方括号间加空格 *[* [
      S3 J ).) . 在右括号和点号间加空格 replace(...).replace(...) .
      S4 JP ])] ) 在右方括号和右括号间加空格 arr[:]]arr[:] )
      S5 P OP]OP ] 在操作符和右方括号间加空格 *]* ]
      S6 J OP(OP ( 在操作符和左括号间加空格 !(isTrue)! (isTrue)
      S7 P [ID[ ID 在左方括号和标识符间加空格 [vowels[ vowels
      S8 J ++)++ ) 在自增符和右括号间加空格 i++)i++)i++)i++ )
      S9 J .*. * 在点号和星号间加空格 import .*import . *
      S10 P ):) : 在右括号和冒号间加空格 def main():def main() :
      S11 J );) ; 在右括号和分号间加空格 func();func() ;
      S12 J OP;OP ; 在操作符和分号间加空格 ++ ;++ ;
      S13 JP ))) ) 在两个右括号间加空格 func()))func()) )
      S14 JP ((( ( 在两个左括号间加空格 func((alpha))func(( alpha))
      S15 JP .ID. ID 在点号和标识符间加空格 .factorial. factorial
      S16 JP (ID( ID 在左括号和标识符间加空格 (String( String
      S17 JP OP IDOP ID 在操作符和标识符间加空格 i+leni+leni+leni + len
      S18 JP OP ALLOP ALL 在操作符和标识符/操作符间加空格 for(1 in list):for (1 in list) :

5. 实验设置 (Experimental Setup)

  • 数据集 (Datasets): 实验选用了覆盖 Java 和 Python 两种主流编程语言的 8 个基准测试集,涵盖 3 种典型代码任务。选择这些数据集是因为它们都包含代码片段作为输入,并且有自动化的功能正确性评测方法。

    以下是论文 Table 1 的转录:

    Benchmark Source Task Input PL Output PL # Samples
    HumanEval-Fix-py HumanEvalPack bug fixing Python Python 164
    HumanEval-Fix-java HumanEvalPack bug fixing Java Java 164
    HumanEval-Explain-py HumanEvalPack code summarization Python Python 164
    HumanEval-Explain-java HumanEvalPack code summarization Java Java 164
    Avatar-py2java Avatar code translation Python Java 244
    Avatar-java2py Avatar code translation Java Python 246
    CodeNet-py2java CodeNet code translation Python Java 200
    CodeNet-java2py CodeNet code translation Java Python 200
  • 评估指标 (Evaluation Metrics):

    • Accuracy (准确率):

      1. 概念定义: 这是最直观的指标,衡量模型生成的代码能够通过所有单元测试的样本所占的百分比。一个输出只有在功能上完全正确时才被认为是正确的。
      2. 数学公式: Accuracy=Number of Correct OutputsTotal Number of Samples \mathrm{Accuracy} = \frac{\text{Number of Correct Outputs}}{\text{Total Number of Samples}}
      3. 符号解释:
        • Number of Correct Outputs\text{Number of Correct Outputs}:模型输出通过所有测试用例的样本数量。
        • Total Number of Samples\text{Total Number of Samples}:评估集中的总样本数量。
    • Δ\Delta Accuracy (准确率变化量):

      1. 概念定义: 该指标直接衡量应用重写规则后,模型准确率的变化情况。它可以直观地显示某个重写规则对模型性能是产生了正面还是负面影响。然而,它有局限性:(1) 在一些样本上性能提升,在另一些样本上性能下降,正负效应会相互抵消;(2) 它没有考虑那些因不包含重写模式而未被修改的样本。
      2. 数学公式: ΔAccuracy=AccuracyvariantAccuracybaseline \Delta \mathrm{Accuracy} = \mathrm{Accuracy}_{\text{variant}} - \mathrm{Accuracy}_{\text{baseline}}
      3. 符号解释:
        • Accuracyvariant\mathrm{Accuracy}_{\text{variant}}:模型在经过重写规则处理后的变体数据集上的准确率。
        • Accuracybaseline\mathrm{Accuracy}_{\text{baseline}}:模型在原始基准数据集上的准确率。
    • Sensitivity (敏感度):

      1. 概念定义: 这是本文提出的核心无偏指标,旨在更精确地衡量模型对分词变化的敏感程度。它计算的是,在所有真正被重写规则修改了输入的样本中,模型输出的正确性发生翻转(即从“正确”变为“错误”,或从“错误”变为“正确”)的样本所占的百分比。这个指标排除了未受影响的样本,并同时捕捉了性能退化和提升两种情况,因此能更公允地反映模型的稳定性。
      2. 数学公式: Sensitivity=Number of Samples with Flipped CorrectnessNumber of Samples with Changed Input \mathrm{Sensitivity} = \frac{\text{Number of Samples with Flipped Correctness}}{\text{Number of Samples with Changed Input}}
      3. 符号解释:
        • Number of Samples with Flipped Correctness\text{Number of Samples with Flipped Correctness}:模型对原始输入和变体输入的输出结果不一致(一正一误)的样本数量。
        • Number of Samples with Changed Input\text{Number of Samples with Changed Input}:输入代码中存在匹配重写规则模式,并被成功修改的样本数量。
  • 对比基线 (Baselines): 严格来说,本研究的对比不是在不同模型之间,而是在同一个模型面对原始输入 (baseline input)变体输入 (variant input) 时的行为差异。实验评估了 3 个系列、共 9 个具有代表性的开源代码 LLM。

    以下是论文 Table 2 的转录:

    Series S (Small) M (Medium) L (Large)
    Llama-3 3B 8B 70B
    Qwen2.5-Coder 1.5B 7B 32B
    DeepSeek-Coder 1.3B 6.7B 33B

6. 实验结果与分析

  • 核心结果分析 (Core Results Analysis):

    • ΔΔ准确率分析: Table 4 展示了详细的准确率变化。

      以下是论文 Table 4 的转录,由于其结构复杂,使用 HTML <divclass="tablewrapper"><table><div class="table-wrapper"><table> 格式呈現:

      Input PL = Java Variant Llama-3B Llama-8B Llama-70B Qwen-1.5B Qwen-7B Qwen-32B DS-1.3B DS-6.7B DS-33B Average
      baseline 32.04 43.15 57.24 33.59 57.36 70.41 38.50 58.01 57.36 49.74
      N1 32.69 (+0.65) 43.54 (+0.39) 57.49 (+0.25) 35.27 (+1.68) 57.62 (+0.26) 70.28 (-0.13) 37.98 (-0.52) 57.36 (-0.65) 57.11 (-0.25) 49.93 (+0.19)
      N2 32.17 (+0.13) 43.54 (+0.39) 56.85 (-0.39) 35.27 (+1.68) 57.75 (+0.39) 70.41 (+0.00) 39.02 (+0.52) 58.14 (+0.13) 57.36 (+0.00) 50.06 (+0.32)
      N3 32.56 (+0.52) 44.19 (+1.04) 56.20 (-1.04) 35.53 (+1.94) 58.01 (+0.65) 69.12 (-1.29) 38.37 (-0.13) 56.33 (-1.68) 56.46 (-0.90) 49.64 (-0.10)
      S3 31.65 (-0.39) 43.02 (-0.13) 56.20 (-1.04) 34.37 (+0.78) 56.72 (-0.64) 70.41 (+0.00) 37.34 (-1.16) 58.66 (+0.65) 57.88 (+0.52) 49.58 (-0.16)
      S6 31.52 (-0.52) 43.02 (-0.13) 57.62 (+0.38) 33.20 (-0.39) 57.49 (+0.13) 70.28 (-0.13) 37.98 (-0.52) 58.53 (+0.52) 57.49 (+0.13) 49.68 (-0.06)
      S8 31.91 (-0.13) 43.28 (+0.13) 57.24 (+0.00) 34.11 (+0.52) 56.72 (-0.64) 71.45 (+1.04) 38.63 (+0.13) 57.49 (-0.52) 58.27 (+0.91) 49.90 (+0.16)
      S9 32.30 (+0.26) 40.96 (-2.19) 58.66 (+1.42) 33.46 (-0.13) 58.14 (+0.78) 69.51 (-0.90) 36.95 (-1.55) 56.59 (-1.42) 57.35 (+0.19) 49.37 (-0.37)
      S11 32.69 (+0.65) 44.57 (+1.42) 55.17 (-2.07) 35.14 (+1.55) 56.33 (-1.03) 71.58 (+1.17) 37.34 (-1.16) 57.11 (-0.90) 57.11 (-0.25) 49.67 (-0.07)
      S12 30.49 (-1.55) 43.02 (-0.13) 56.07 (-1.17) 34.75 (+1.16) 55.81 (-1.55) 67.05 (-3.36) 38.63 (+0.13) 55.94 (-2.07) 58.53 (+1.17) 48.92 (-0.82)
      S13 32.43 (+0.39) 42.64 (-0.51) 56.59 (-0.65) 33.46 (-0.13) 57.36 (+0.00) 69.77 (-0.64) 37.47 (-1.03) 58.27 (+0.26) 56.98 (-0.38) 49.44 (-0.30)
      S14 29.84 (-2.20) 41.09 (-2.06) 54.13 (-3.11) 32.17 (-1.42) 56.85 (-0.51) 71.19 (+0.78) 37.86 (-0.64) 57.11 (-0.90) 57.62 (+0.26) 48.65 (-1.09)
      S15 30.62 (-1.42) 36.82 (-6.33) 57.24 (+0.00) 33.46 (-0.13) 56.72 (-0.64) 70.28 (-0.13) 37.34 (-1.16) 55.43 (-2.58) 59.43 (+2.07) 48.59 (-1.15)
      S16 30.88 (-1.16) 40.83 (-2.32) 55.94 (-1.30) 34.88 (+1.29) 57.36 (+0.00) 71.96 (+1.55) 36.43 (-2.07) 57.49 (-0.52) 58.66 (+1.30) 49.38 (-0.36)
      S17 28.68 (-3.36) 37.34 (-5.81) 56.07 (-1.17) 35.66 (+2.07) 55.43 (-1.93) 70.03 (-0.38) 35.40 (-3.10) 55.04 (-2.97) 58.91 (+1.55) 48.06 (-1.68)
      Input PL = Python S18 25.97 (-6.07) 34.88 (-8.27) 56.85 (-0.39) 34.11 (+0.52) 56.07 (-1.29) 70.28 (-0.13) 33.98 (-4.52) 53.10 (-4.91) 56.33 (-1.03) 46.84 (-2.90)
      baseline 39.12 49.87 69.04 40.67 64.51 76.17 44.82 61.92 68.13 57.14
      N4 40.03 (+0.91) 51.04 (+1.17) 68.91 (-0.13) 39.77 (-0.90) 65.03 (+0.52) 77.85 (+1.68) 44.30 (-0.52) 61.53 (-0.39) 68.39 (+0.26) 57.43 (+0.29)
      N5 37.56 (-1.56) 50.91 (+1.04) 68.65 (-0.39) 39.25 (-1.42) 64.77 (+0.26) 77.72 (+1.55) 42.88 (-1.94) 61.53 (-0.39) 68.39 (+0.26) 56.85 (-0.29)
      N6 38.08 (-1.04) 50.65 (+0.78) 66.19 (-2.85) 39.38 (-1.29) 64.51 (+0.00) 76.81 (+0.64) 42.23 (-2.59) 61.14 (-0.78) 67.62 (-0.51) 56.29 (-0.85)
      S1 39.38 (+0.26) 50.39 (+0.52) 68.65 (-0.39) 40.54 (-0.13) 64.51 (+0.00) 76.68 (+0.51) 44.69 (-0.13) 62.56 (+0.64) 67.62 (-0.51) 57.22 (+0.08)
      S2 39.64 (+0.52) 50.65 (+0.78) 68.78 (-0.26) 40.41 (-0.26) 64.77 (+0.26) 75.91 (-0.26) 43.65 (-1.17) 62.44 (+0.52) 67.75 (-0.38) 57.11 (-0.03)
      S4 39.77 (+0.65) 50.65 (+0.78) 69.30 (+0.26) 40.54 (-0.13) 64.51 (+0.00) 73.19 (-2.98) 44.82 (+0.00) 61.92 (+0.00) 67.36 (-0.77) 56.90 (-0.24)
      S5 38.60 (-0.52) 50.78 (+0.91) 68.91 (-0.13) 40.80 (+0.13) 64.12 (-0.39) 76.94 (+0.77) 44.43 (-0.39) 62.69 (+0.77) 66.71 (-1.42) 57.11 (-0.03)
      S7 40.03 (+0.91) 49.35 (-0.52) 68.26 (-0.78) 40.67 (+0.00) 63.34 (-1.17) 76.42 (+0.25) 44.30 (-0.52) 62.69 (+0.77) 67.23 (-0.90) 56.92 (-0.22)
      S10 38.47 (-0.65) 50.65 (+0.78) 69.17 (+0.13) 40.67 (+0.00) 63.99 (-0.52) 77.46 (+1.29) 44.56 (-0.26) 62.05 (+0.13) 67.10 (-1.03) 57.12 (-0.02)
      S13 37.95 (-1.17) 50.13 (+0.26) 69.30 (+0.26) 40.54 (-0.13) 64.90 (+0.39) 76.55 (+0.38) 44.30 (-0.52) 62.05 (+0.13) 67.10 (-1.03) 56.98 (-0.16)
      S14 38.73 (-0.39) 49.22 (-0.65) 68.39 (-0.65) 39.38 (-1.29) 63.73 (-0.78) 74.09 (-2.08) 45.08 (+0.26) 61.66 (-0.26) 67.49 (-0.64) 56.42 (-0.72)
      S15 39.12 (+0.00) 50.26 (+0.39) 67.49 (-1.55) 39.77 (-0.90) 62.69 (-1.82) 76.30 (+0.13) 44.17 (-0.65) 61.66 (-0.26) 67.23 (-0.90) 56.52 (-0.62)
      S16 40.16 (+1.04) 49.87 (+0.00) 69.04 (+0.00) 39.64 (-1.03) 63.08 (-1.43) 76.68 (+0.51) 43.65 (-1.17) 61.27 (-0.65) 67.23 (-0.90) 56.74 (-0.40)
      S17 40.41 (+1.29) 50.39 (+0.52) 67.62 (-1.42) 39.38 (-1.29) 61.92 (-2.59) 76.55 (+0.38) 42.62 (-2.20) 60.49 (-1.43) 66.32 (-1.81) 56.19 (-0.95)
      S18 37.44 (-1.68) 49.87 (+0.00) 67.62 (-1.42) 38.34 (-2.33) 63.08 (-1.43) 75.13 (-1.04) 42.49 (-2.33) 62.05 (+0.13) 67.36 (-0.77) 55.93 (-1.21)

      从表中可以观察到,大多数重写规则都会导致模型准确率发生不可忽略的变化,平均变化范围在-2.90到+0.32个百分点之间。最极端的情况发生在 Llama-8B 模型上,应用 S18 规则(在操作符后添加空格)后,其在 Java 任务上的准确率从 43.15% 暴跌至 34.88%,下降了 8.27%。考虑到 LLM 领域的性能提升常常以一两个百分点来衡量,这种由简单格式改动引起的性能波动是相当惊人的。

    • 敏感度分析: Figure 3 提供了更直观的敏感度分布视图。

      Figure 3: Violin plots of sensitivity distributions. 该图像是图3,展示了三组小提琴图,分别按命名重写规则、空格重写规则和模型分组,展示了不同代码变体对九个模型敏感度的分布情况,突显格式微调对模型行为的显著影响。

      • 总体敏感度: 命名重写规则的平均敏感度为 9.26%,空格重写规则的平均敏感度为 8.29%。这意味着,每当代码格式或命名风格发生微小变化时,平均有近十分之一的样本,模型的输出正确性会发生翻转。
      • 规则影响: 在命名规则中,camelCasesnake_case 之间的转换 (N1, N4) 敏感度相对较低。在空格规则中,通配符规则 S17S18 影响最大(平均敏感度超过 10%),此外 S15(在 . 和标识符间加空格)、S14(在 (( 间加空格)和 S12(在操作符和 ; 间加空格)也造成了较高的敏感度。
      • 模型影响: Llama-3 系列的模型表现出比 QwenDeepSeek 系列更高的敏感度。但即使是表现最稳健的 Qwen-32B,其平均敏感度也达到了 5.71%,说明这个问题在所有模型中普遍存在。
  • 消融实验/参数分析 (Ablation Studies / Parameter Analysis):

    • 模型尺寸的影响: Table 5 探究了模型规模是否会影响其对分词变化的鲁棒性。

      Rewrite Rule Model Series S M L
      Naming Llama-3 11.48 10.68 9.43
      Qwen2.5-Coder 7.73 7.95 8.27
      DeepSeek-Coder 9.88 8.95 8.95
      Spacing Llama-3 10.22 10.99 8.51
      Qwen2.5-Coder 7.07 8.87 5.71
      DeepSeek-Coder 8.36 8.71 6.26

      发现: 通常情况下,更大的模型更鲁棒(敏感度更低)。例如,Llama-70B 的敏感度低于 Llama-3BLlama-8BDeepSeek-33B 也比其小尺寸版本更稳健。这符合“规模法则”(Scaling Law) 的普遍预期。但也有例外,如 Qwen-32B 在命名规则上的敏感度略高于中等尺寸模型,说明模型规模的扩大并不能完全解决这个问题。

    • 标识符分片变化的影响: 论文引入了标识符分片变化 (identifier fragment change) 的概念,指重写前后标识符被分成的子词列表是否发生变化(例如 sortedLst -> ['sorted', 'L', 'st'] vs. sorted_lst -> ['sorted', '_lst'])。Table 6 对比了有无这种变化的样本的敏感度。

      Rewrite Rule Model Unchanged Changed
      Naming Llama-70B 8.13 11.21
      Qwen-32B 6.58 10.57
      DS-33B 6.61 10.82
      Spacing Llama-70B 7.24 11.89
      Qwen-32B 5.09 7.37
      DS-33B 5.80 7.12

      发现: 存在标识符分片变化的样本组 (Changed) 表现出持续且显著更高的敏感度。例如,对于 DS-33B,命名规则下有变化的样本敏感度为 10.82%,而无变化的仅为 6.61%。这强有力地证明了,LLM 如何将标识符拆分成子词,对其代码理解能力至关重要。当分词结果不符合语义时(如 sortedLst 被拆成三个不相关的部分),模型的性能会变得更不稳定。

7. 根源分析 (Root Cause Analyses)

  • 词频分析 (Word Frequency Analysis):

    • 假设: 如果一个重写规则将一个常见的代码模式(LHS, 左侧)转换成一个在预训练语料中非常罕见的代码模式(RHS, 右侧),那么模型在处理后者时会表现得更差,从而导致更高的敏感度。

    • 验证: 论文在 GitHub(代码 LLM 的主要训练数据来源)上统计了部分空格重写规则左右两侧模式的出现频率。

      Table 7 的转录结果显示:

      Rewrite Rule LHS RHS Ratio [%]
      Java
      S3: ).) . 78.9M 45.7K 0.06
      S14: ((( ( 144M 195K 0.14
      Python
      S14: ((( ( 78.1M 71.7K 0.09
    • 结论: 结果证实了假设。所有被重写的模式(RHS)的出现频率都远低于原始模式(LHS),频率比率(Ratio)通常非常低。例如,对于规则 S14,在 Java 代码中,(( 出现了 1.44 亿次,而 ( ( 仅出现了 19.5 万次,比率仅为 0.14%。这种巨大的频率差异解释了为什么模型对这些看似微不足道的改动如此敏感——它们在模型的“世界观”里是极其罕见的现象。

  • 隐藏状态分析 (Hidden State Analysis): 为了探究模型内部发生了什么,论文分析了重写前后模型各层隐藏状态 (hidden states) 的变化。

    • 层级相似度分析 (Figure 4):

      Figure 4: The similarity of each layer's hidden states before and after applying rewrite rules. 该图像是论文中图4的两部分折线图,展示了通过语义等价的重写规则(命名和空格修改)前后不同层隐藏状态相似度的变化趋势。横轴为模型层深度百分比,纵轴为相似度,显示早期层相似度较低,后期层趋于高相似。

      通过计算重写前后对应 token 序列最后一个 token 隐藏状态的余弦相似度 (cosine similarity),论文发现:

      1. 第一层(输入层),相似度几乎为 0。这符合预期,因为不同的分词导致了完全不同的初始嵌入向量。
      2. 中间层,相似度迅速上升并趋于稳定。这符合信息瓶颈理论 (information bottleneck theory),即中间层提取并压缩了输入的语义信息。
      3. 然而,对于那些高敏感度的规则(如图 b 中的 S14S3),即使在中间层,相似度也维持在较低水平。这表明,对于这些变化,模型从始至终都将它们视为语义上不同的东西,差异未能被中间层“修复”。
      4. 最后一层(输出层),相似度再次下降,因为模型需要根据不同的内部表示做出具体的预测。
    • 隐藏状态差异可视化 (Figure 5):

      Figure 5: Visualizations of the hidden state diffs using t-SNE (Maaten and Hinton, 2008). 该图像是图表,展示了图5中使用t-SNE可视化的隐藏状态差异。(a)对比命名与空格两种变量的聚类情况;(b)展示了基于命名重写规则的语义聚类;(c)展示了基于空格重写规则的语义聚类,三者颜色区分不同类别。

      论文计算了中间层隐藏状态的差异向量(hidden_state_after - hidden_state_before),并使用 t-SNE 进行降维可视化。

      1. 图 (a) 显示,命名规则和空格规则引起的隐藏状态差异形成了两个清晰可分的簇。

      2. 图 (b)图 (c) 进一步显示,不同的命名规则(N1-N6)和不同的空格规则(S1-S16)也各自形成了独特的簇。

        结论: 这个发现证实了模型的隐藏状态有效捕捉了由不同重写规则引入的语义变化。这不仅解释了模型的敏感性,也暗示了可以利用这些隐藏状态的差异来诊断甚至缓解这个问题。

8. 总结与思考 (Conclusion & Personal Thoughts)

  • 结论总结 (Conclusion Summary): 这篇论文系统地揭示并量化了一个长期存在但常被忽视的问题:代码 LLM 使用的统计性子词分词器与编程语言的确定性语法之间存在根本性的错位。通过 TokDrift 框架,论文证明了这种错位导致了模型对代码中语义无关的表面变化(如命名和空格)高度敏感,从而损害了其可靠性。这种敏感性在各种规模和系列的先进代码 LLM 中普遍存在,其根源在于分词差异导致了模型早期嵌入层的巨大偏差。这项工作为社区敲响了警钟,强调了开发语法感知的分词器 (grammar-aware tokenizers) 对于构建下一代更可靠、更稳健的代码 LLM 的重要性。

  • 局限性与未来工作 (Limitations & Future Work):

    • 局限性:
      1. 重写规则有限: 论文的重写规则集虽然具有代表性,但并未覆盖所有可能引起分词漂移的情况。
      2. 模型架构有限: 研究主要集中在基于 Transformer 的自回归模型,结论是否适用于其他架构(如状态空间模型)尚不确定。
      3. 诊断而非解决: 本文侧重于问题的测量和诊断,并未提出具体的解决方案。
    • 未来工作:
      1. 改进分词器: 重新训练或设计能够更好对齐编程语言语法的分词器。
      2. 集成解码策略: 在生成时,可以考虑对多种可能的分词结果进行集成解码,以提高鲁棒性。
      3. 改进模型架构: 对模型架构进行修改,使其能够更好地处理和对齐词元边界与语法结构。
  • 个人启发与批判 (Personal Insights & Critique):

    • 启发:
      1. 问题的量化价值: 这篇论文最大的启发在于它将一个“感觉上存在”的问题,通过一个严谨、可复现的框架进行了量化。这种“诊断优先”的研究思路,对于推动领域向更深层次发展至关重要。
      2. “大力出奇迹”的局限性: 实验结果表明,即使是 30B+ 的大模型也未能完全克服这个问题,这提醒我们,仅仅依靠增加模型参数和数据量(即 Scale up)可能无法解决一些根本性的表示问题。底层表示(如分词)的设计同样关键。
      3. 可迁移的应用: TokDrift 的思想可以被迁移到其他结构化数据领域,如化学分子式 (SMILES)、数学公式等。在这些领域,同样存在多种等价表示,模型的表示一致性同样重要。
    • 批判性思考:
      1. 指标的潜在偏见: Sensitivity 指标虽然比 ΔAccuracyΔ Accuracy 更优,但可能仍受模型基线准确率的影响。例如,一个基线准确率在 50% 左右的模型,其输出结果有更多“翻转”的空间;而一个准确率高达 95% 的模型,即使受影响,也可能只是从一个“正确”的输出变成了另一个“正确”的输出(如果任务允许多个正确解),这在当前的功能正确性评估下无法被捕捉。
      2. 实际影响的权衡: 论文展示了显著的敏感度,但在实际开发场景中,程序员通常会遵循固定的代码风格指南 (style guide),因此某些“不规范”的写法(如 ( ()出现的频率可能远低于其在 GitHub 上的统计。尽管如此,这项研究对于评估模型在处理未见过或风格不一的代码时的鲁棒性仍然极具价值。
      3. 对齐的真正含义: 论文呼吁“语法感知”分词,这是一个正确的方向。但终极目标或许是“语义感知”分词。例如,一个理想的分词器应该将 get_user_by_id 作为一个整体或者 ['get', 'user', 'by', 'id'] 这样的有意义组合,而不是 ['get_user', '_by', '_id'] 这样无意义的切分。这需要分词器本身具备更深层次的语言理解能力。

相似论文推荐

基于向量语义检索推荐的相关论文。

暂时没有找到相似论文。