为预训练LLM添加新Token:原理、步骤与云原生实践

为预训练LLM添加领域特定Token,可提升微调效果。文章介绍操作步骤,并提供云原生场景下的集成方案。

原文标题:如何为预训练 LLM 添加新 token?

原文作者:AI前线

冷月清谈:

本文深入探讨了为预训练LLM添加新token的原因和方法,指出在微调前增加领域特定token能够提升模型对数据集的理解能力和微调效果。文章详细阐述了添加新token的四个关键步骤:修改分词器、初始化嵌入权重、保存并重新加载模型、使用新token进行微调。此外,文章还结合云原生场景,针对Pod、Node和Cluster三种不同级别的模型微调,提出了相应的集成解决方案,包括利用Init Containers、Ray Job API和Kubernetes Operator等技术手段来实现token的添加,并强调了多集群任务发现的重要性。最后,作者展望了云原生环境下LLM的更广泛应用,并鼓励读者积极探索更多解决方案。

怜星夜思:

1、文章提到添加新token可以提高文本生成的效率和准确性,特别是对于像`ChatGPT`这样的词汇。除了这种直接将常见词汇添加为token的方法,还有没有其他更巧妙或更具创新性的token添加策略?
2、文章提到了在云原生环境下,针对不同级别的微调(Pod、Node、Cluster)有不同的token添加策略。 如果我需要在多个地理位置分散的集群上进行LLM微调,并且每个集群都需要添加略有不同的token(例如,基于当地语言习惯的特定缩写),应该如何设计token添加流程,以确保所有集群都能正确加载和使用各自的token?
3、文章中提到了初始化新token的嵌入权重时,可以使用当前模型嵌入层中所有token的平均嵌入权重。除了平均值,还有没有其他更有效或更合理的初始化策略? 例如,是否可以利用已有的词向量知识(如Word2Vec或GloVe)来初始化新token的嵌入权重?

原文内容

作者 | 张怀龙
策划 | 褚杏娟

为了使得通用的预训练大模型能够满足专业领域的需求,我们往往会对通用的大模型进行微调。实际上通过微调框架或技术对大模型进行微调之后可能会发现,微调之后的模型其实并没有达到预定的效果。当然这个原因是多方面的,有时候取决于微调的数据集,微调时对模型进行的一些列优化处理等等,但是另外一个不容忽视的问题则是需要在微调之前向预训练 LLM 的分词器增加一些专业领域的 token,以帮助 LLM 在微调过程中提高对数据集的理解能力。

本文的主要目的是介绍往 LLM 分词器中新增 token 的原因和基本方法,同时也会介绍在云原生的场景下如何将该操作集成到 LLM 微调的流程当中。

为什么需要在预训练 LLM 中
添加新 token

在对预训练 LLM 进行微调之前,通常会在预训练 LLM 分词器中添加额外的新 token。这样做有以下几个目的:

1. 引入领域特定词汇

如果需要在特定领域(例如医学、法律、金融)进行微调,则需要引入一些基础模型无法识别的新术语、缩写或技术术语。添加新 token 有助于模型更好地理解和生成与该领域相关的文本。

2. 处理词汇表之外的单词

预训练 LLM 使用分词算法(如 BPE 或 WordPiece),然而有些词可能会被分解为低效的子词。因此添加额外的标记可以提高文本生成的效率和准确性。例如,`ChatGPT` 可能会拆分为 Chat 和 GPT,从而导致语义碎片化。

3. 适应多语言扩展

如果针对原始训练数据中没有很好覆盖的新语言进行微调,则为唯一的单词或字符添加额外的标记可以提高 LLM 的微调性能。

4. 引入自定义格式标记、指令调整和提示词工程

如果需要在微调数据中引入特定结构(例如,markdown 格式、XML 标签或对话角色的占位符),新 token 可以帮助预训练 LLM 更有效地学习所需的结构。除此之外,一些预训练 LLM 使用特殊标记(如 、 或 )来指导推理响应。这些额外的 token 有助于根据结构化输入调节模型。在这些情况下,最好引入特殊 token,而不是通用 token。本文中的示例只包含了添加通用 token 的例子。

综上所述,添加额外的 token 有助于预训练的 LLM 在微调过程中更有效地适应特定领域的要求,从而减少对大量训练数据的需求。这也是平衡微调效果和计算成本的关键技术。

实施此技术的步骤有哪些?

基本上,通过 transformers 包实现额外 token 的添加有如下 4 个步骤:

1. 修改分词器

a) 加载分词器 (例如从 Hugging Face’s transformers 库中加载);

b) 添加新的 token 到分词器的词汇表中;

有关向分词器添加新 token 的更多详细信息,请参阅 new_tokens 和 special_tokens;

c) 调整模型中嵌入层的大小以适应新的 token。

需要调用 resize_token_embeddings 来调整模型的嵌入矩阵的大小以匹配其分词器。此外,请参阅此处了解更多详细信息。

2. 在模型的嵌入层中正确初始化新 token 的嵌入权重

新的 token 被添加到分词器中,其索引从当前词汇表的长度开始,并且在应用分词算法之前将被隔离,因此这些新的 token 和来自分词算法词汇表的 token 不会以相同的方式处理。除此以外,默认情况下,模型嵌入层中新 token 的嵌入权重是随机初始化的,这可能会导致最初的训练不稳定或性能不佳。因此,一般在每加入一个新的 token 之前,事先计算当前模型嵌入层中所有 token 的平均嵌入权重(average embedding weight),以此作为当前新加入 token 的嵌入权重。

3. 保存并重新加载更新后的分词器和模型

添加新 token 后,保存更新的分词器和模型以供将来使用。当然,后面需要加载分词器和模型的时候需要与保存时候的路径相对应地加载。

4. 使用添加的 token 对预训练 LLM 进行微调

如果新 token 代表专业领域的概念,则在相关数据集上微调模型至关重要。使用包含这些新 token 实例的数据集并训练模型以正确理解其用法。

根据以上描述,我完成在 GitHub 完成了一个示例程序 AddExtraTokens2LLM。读者克隆该例子后可直接在您的设备上启动运行,欢迎尝试并提出宝贵意见。

云原生场景下如何添加
新的 token 操作集成至 LLM 的微调流程

无论我们选择何总微调技术或者框架,预训练 LLM 微调的流程大致如下图所示。红色虚线框的内容包含的就是特定领域的 token 表,我们均需要在正式微调模型前将其加入到模型的分词器词库和模型的 embedding 层中,并且该操作应该在微调模型加载前,更靠前于导入 LLM 微调的数据集。

结合云原生的场景下,在预训练 LLM 微调前该如何为其模型添加新的 token?对于这个问题其答案取决于多个方面的考量,比如用什么技术框架对模型进行微调,微调的集群是单集群还是多集群等。笔者综合遇到的大部分场景,将这此集成过程抽象成如下三种情况:Pod Level 的模型微调,Node Level 的模型微调和 Cluster Level 的模型微调。

对于 Pod Level 的模型微调,这是所有情况中最简单的场景,一般情况下其微调的时间不会太长,技术也不会太复杂。笔者对此的解决方案是可以将微调的任务放到 Pod 的常规 containers 中去实现,然后将添加新的 token 的任务放入 Pod 的 Init Containers 中。熟悉 Kubernetes Init Containers 机制的读者应该明白,Init Containers 可以保证其任务在所有常规的 containers 启动之前完成。

对于 Node Level 的模型微调,这种场景通常情况下会涉及各种分布式微调框架技术的使用,比如基于 Ray 的大语言模型微调。除此以外,Node Level 也可以出现多集群微调的场景,为了简单起见,笔者只考虑单集群的 Node Level 的模型微调。而对此的解决方案基本上会定义微调模型的流程,此目的自然是为了实现对微调过程的控制干预。对于使用 Ray 等框架来说可能相对比较简单,因为用户只需要将给模型添加 token 的工作封装成 Ray script (Driver),然后再微调之前通过 Ray Job API 事先提交一个 Ray Job 即可。如果想要做得更通用灵活,笔者的建议则是为模型的微调实现一个 Kubernetes Operator,通过定义 Kubernetes CRD 来实现模型微调流程的扭转。这些流程节点笔者总结如下:1. 从模型下载。2. 为模型添加新 token 并保存模型。3. 重新加载处理后的模型。4. 导入微调数据集(可能包含微调数据集的处理,可以添加子流程)。5. 模型微调(需要包含调度,自动扩缩容和断点续训等处理机制)。6. 微调后模型保存。

对于 Cluster Level 的模型微调,这种场景基本上属于很大规模的模型微调,需要多集群的协作是不可避免的,因此多集群的解决方案是必不可少的。事实上笔者到目前为止并没有遇到这种场景,所以也只能提出一些理论的解决方案。首先需要选择合适的多集群解决方案方。其次要实现多集群 Task Discovery(任务发现)的能力。此能力应该是实现各种关键任务的基础,包括本文主要提及的为模型添加 token 的任务。最后,需要把各种任务及其输出串联起来,形成完整的流程拓扑。鉴于笔者没有实践过,因此不再赘述。

虽然写本文的主要目的是为了介绍“如何为预训练 LLM 添加新 token?”,但是笔者抛砖引玉,也相信读者能从这一隅之地出发,为基于云原生场景下,解决大语言模型服务推理部署,微调,训练等场景提出更加通用优秀的解决方案,为业界分享更多的实践经验。

关于作者

张怀龙,曾就职于阿尔卡特朗讯、百度、IBM、英特尔等知名公司担任高级开发职位,拥有 16 年的技术研发经验。作为 Istio 社区的维护者,他专注于云原生微服务,并在云原生与 LLM 技术的交叉领域进行创新。他为 OpenVINO、Kserve 等社区做出贡献,致力于云原生场景下的 LLM 推理,是 OPEA(企业 AI 开放平台)社区的开发者和维护者。作者还曾在 KubeCon、ServiceMeshCon、IstioCon、GOTC、InfoQ/Qcon 和 GOSIM 等会议上发表演讲。

 会议推荐

AICon 2025 强势来袭,5 月上海站、6 月北京站,双城联动,全览 AI 技术前沿和行业落地。大会聚焦技术与应用深度融合,汇聚 AI Agent、多模态、场景应用、大模型架构创新、智能数据基建、AI 产品设计和出海策略等话题。即刻扫码购票,一同探索 AI 应用边界!


今日荐文




图片
你也「在看」吗?👇

关于添加新token的策略,我觉得可以考虑以下几个方向:

1. 基于Subword的动态Tokenization:与其直接添加ChatGPT这样的词,不如设计一种算法,能够动态地识别并组合高频出现的subword组合,将它们作为一个token处理。这样可以更灵活地适应新的词汇。

2. 情感/语义Token:在某些应用场景下,可以考虑添加代表情感或者语义的token。例如,用<正面><负面><疑问>等token来标记文本的情感倾向或语义类型。这样可以帮助模型更好地理解和生成带有特定情感或语义的文本。

3. 领域知识Token:针对特定领域,可以添加一些代表该领域关键概念的token。比如,在医学领域,可以添加<基因><疾病><药物>等token。这可以帮助模型更好地理解和处理该领域的文本。

总的来说,token添加策略应该根据具体的应用场景和需求来设计,目标是提高模型对文本的理解和生成能力。

我最近在研究Prompt Engineering,发现一个思路,我们可以不直接加token,而是通过Prompt让LLM自己“学会”新的token。 比如,我想让LLM理解“Orz”代表“膜拜”,我可以在Prompt里这样写:“以后你看到Orz,就理解为膜拜的意思”。 这样,LLM就可以在不修改tokenizer的情况下,理解新的“token”了。 当然,这种方法的效果取决于Prompt的设计,需要不断尝试和优化。

从工程角度有个比较简单的方案,就是如果你的新token是一个已知的英文单词,可以考虑直接用bert之类的embedding模型,把这个token转成embedding向量,直接作为初始化值,理论上效果会比随机平均好,但是要注意normalize

可以考虑用镜像分层的方式来管理。每个集群的基础镜像都包含通用的token,然后针对不同地域的集群,构建额外的镜像层,包含该地域特有的token。在部署时,将基础镜像和地域镜像层叠起来,形成最终的镜像。 这样做的好处是,通用token只需要存储一份,节省存储空间。而且,地域镜像层可以很小,方便快速分发。