跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
PythonAI算法

大语言模型词表裁剪方法与实践

如何裁剪大语言模型(LLM)的词表以降低参数量。以 Bloom 模型为例,阐述了词表裁剪的核心原理,包括 Embedding 层和 LM Head 权重的映射与复制。提供了完整的 Python 代码实现,展示了如何验证新词表是否为原词表子集、如何更新模型配置以及如何进行一致性检查。实验结果显示,裁剪后模型参数量可显著减少,且生成结果保持一致。文章还补充了关于特殊 Token 保留、分词策略一致性及 OOV 处理等最佳实践建议。

奇形怪状发布于 2025/2/7更新于 2026/10/2591 浏览
大语言模型词表裁剪方法与实践

大语言模型词表裁剪方法与实践

前言

在多语言大语言模型(LLM)的应用场景中,原始模型的词表(Vocabulary)往往非常庞大,包含了全球多种语言的字符和子词。然而在实际的下游任务中,我们可能只需要支持特定的语言,例如仅中文和英文。此时,对词表进行裁剪是一个有效的优化手段。

通过裁剪词表,我们可以显著减少模型的参数量,降低显存占用,同时保留模型在目标语言上的性能表现。本文将基于 Bloom 模型为例,详细介绍如何进行词表裁剪的操作流程、代码实现及注意事项。

词表裁剪原理

大语言模型的词表大小直接决定了嵌入层(Embedding Layer)和语言模型头(LM Head)的维度。假设原词表大小为 $V_{old}$,隐藏层维度为 $H$,则嵌入层的参数量为 $V_{old} \times H$,LM Head 的参数量为 $H \times V_{new}$(通常 $V_{new} = V_{old}$)。

当我们将词表裁剪至 $V_{new}$ 时,我们需要从原模型中提取对应于新词表中 token 的权重向量,并构建新的模型结构。核心逻辑如下:

  1. Token 映射:建立新词表 Token ID 到原词表 Token ID 的映射关系。
  2. 权重提取:根据映射关系,从原 Embedding 矩阵和 LM Head 矩阵中复制对应的行向量。
  3. 配置更新:更新模型配置中的 vocab_size 字段。
  4. 一致性验证:确保裁剪后的模型在相同输入下生成的文本与原模型一致。

准备工作

首先,我们需要准备两个关键组件:

  1. 原模型:包含完整多语言词表的预训练模型(如 bigscience/bloom-560m)。
  2. 新词表:经过筛选的、仅包含目标语言 Token 的 tokenizer 文件(通常是 tokenizer.json 或 vocab.txt)。

注意:新词表必须是原词表的子集,即所有新词表中的 Token 都必须存在于原词表中,否则会导致索引越界错误。

代码实现

以下是一个完整的词表裁剪类实现,基于 Hugging Face Transformers 库。

1. 基础裁剪类

import os
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
from tqdm import tqdm

class VocabularyPruner(object):
    def check(self, old_model_name_or_path, new_model_name_or_path, text):
        """
        检查模型裁剪后,生成结果是否一致
        """
        max_length = 20

        # 使用老模型对文本编码
        old_model = AutoModelForCausalLM.from_pretrained(old_model_name_or_path)
        old_tokenizer = AutoTokenizer.from_pretrained(old_model_name_or_path)
        old_input_ids = old_tokenizer(text, return_tensors=).input_ids
        old_output = old_model.generate(old_input_ids, max_length=max_length)
        old_output_text = old_tokenizer.batch_decode(old_output)
        (.(old_output_text))

        
        new_model = AutoModelForCausalLM.from_pretrained(new_model_name_or_path)
        new_tokenizer = AutoTokenizer.from_pretrained(new_model_name_or_path)
        new_input_ids = new_tokenizer(text, return_tensors=).input_ids
        new_output = new_model.generate(new_input_ids, max_length=max_length)
        new_output_text = new_tokenizer.batch_decode(new_output)
        (.(new_output_text))

         old_output_text == new_output_text:
            ()
        :
            ()

     ():
         NotImplementedError

     ():
        
          os.path.exists(save_path):
            os.makedirs(save_path)
        
        
        new_tokenizer = AutoTokenizer.from_pretrained(new_tokenizer_name_or_path)
        
        old_tokenizer = AutoTokenizer.from_pretrained(model_name_or_path)

        
        old_vocab = old_tokenizer.vocab
        new_vocab = new_tokenizer.vocab
         token  tqdm(new_vocab.keys()):
             token   old_vocab:
                 Exception(.(token))
        ()

        
        new2old_token_id = {}
         token, token_id  tqdm(new_vocab.items()):
            old_token_id = old_vocab[token]
            new2old_token_id[token_id] = old_token_id

        
        model = AutoModelForCausalLM.from_pretrained(model_name_or_path, torch_dtype=)
        
        old_params = (p.numel()  p  model.parameters())
        ( % (old_params / ))

        
        vocab_size = (new_tokenizer)
        hidden_size = model.config.hidden_size

        new_embeds = torch.nn.Embedding(vocab_size, hidden_size, dtype=model.dtype)
        new_lm_head = torch.nn.Linear(in_features=hidden_size, out_features=vocab_size, bias=, dtype=model.dtype)
        
        
        .update_embeddings(model, new2old_token_id, new_embeds, new_lm_head)

        model.config.__dict__[] = vocab_size
         new_name_or_path   :
            model.config.__dict__[] = new_name_or_path

        
        new_params = (p.numel()  p  model.parameters())
        ( % (new_params / ))

        (.(((new_tokenizer) / (old_tokenizer), )*))
        (.((new_params / old_params, )*))
        
        model.save_pretrained(save_path)
        new_tokenizer.save_pretrained(save_path)
'pt'
print
'old_output:{}'
format
# 使用新模型对文本编码
'pt'
print
'new_output:{}'
format
if
print
'output is same, succeed to prune.'
else
print
'output is not same, fail to prune.'
def
update_embeddings
self, model, new2old_token_id, new_embeds, new_lm_head
raise
def
prune
self, model_name_or_path, new_tokenizer_name_or_path, save_path, new_name_or_path=None
# 创建输出目录
if
not
# 加载新词表。如果是中文,就是中文的词表
# 加载原词表。一般为多语言模型的词表
# 检查新词表是否为原词表的子集
for
in
if
not
in
raise
'{} not exist'
format
print
'new_tokenizer is subset of old_tokenizer'
# 获得新词表中每个 token_id 到原词表的 token_id 的映射
for
in
# 加载多语言模型
'auto'
# 计算原模型的参数量
sum
for
in
print
"Total params of original model: %.2fM"
1e6
# 对于新词表中的每个 token,取出其对应的权重,复制到新模型中
len
False
# 更新词表权重
self
'vocab_size'
if
is
not
None
'_name_or_path'
# 计算新模型的参数量
sum
for
in
print
"Total params of new model : %.2fM"
1e6
print
'词表缩小为原来的:{}%'
format
round
len
len
4
100
print
'模型参数量缩小为原来的:{}%'
format
round
4
100

2. Bloom 模型专用裁剪器

针对 Bloom 模型架构,需要特殊处理 Embedding 层的位置。

class BloomVocabularyPruner(VocabularyPruner):
    def update_embeddings(self, model, new2old_token_id, new_embeds, new_lm_head):
        for token_id, old_token_id in tqdm(new2old_token_id.items()):
            # 复制 Embedding 权重
            new_embeds.weight.data[token_id] = model.transformer.word_embeddings.weight.data[old_token_id]
            # 复制 LM Head 权重
            new_lm_head.weight.data[token_id] = model.lm_head.weight.data[old_token_id]
        
        # 替换模型中的权重对象
        model.transformer.word_embeddings.weight = new_embeds.weight
        model.lm_head.weight = new_lm_head.weight

3. 执行裁剪与验证

# 需要进行裁剪的模型路径
model_name_or_path = 'bigscience/bloom-560m'
# 自己制作的词表的路径
new_tokenizer_name_or_path = 'YeungNLP/bloom-396m-zh'
save_path = './pruned_bloom_zh'

pruner = BloomVocabularyPruner()
# 裁剪
pruner.prune(model_name_or_path, new_tokenizer_name_or_path, save_path)
# 检查裁剪的模型与原模型是否一致
pruner.check(model_name_or_path, save_path, text='长风破浪会有时')

运行结果示例

执行上述脚本后,控制台将输出参数变化及一致性检查结果:

100%|██████████| 46145/46145 [00:00<00:00, 1309531.65it/s]
new_tokenizer is subset of old_tokenizer
100%|██████████| 46145/46145 [00:00<00:00, 1120687.88it/s]
Total params of original model: 559.21M
100%|██████████| 46145/46145 [00:01<00:00, 41641.55it/s]
Total params of new model : 396.82M
词表缩小为原来的:18.41%
模型参数量缩小为原来的:70.96%
old_output:['长风破浪会有时,直挂云帆济沧海。愿你,在人生的旅途中,能遇见最美的风景,遇见最美的自己。</s>']
new_output:['长风破浪会有时,直挂云帆济沧海。愿你,在人生的旅途中,能遇见最美的风景,遇见最美的自己。</s>']
output is same, succeed to prune.

可以看到,模型参数量从 559.21M 降至 396.82M,缩减了约 29%,而生成结果完全一致。

补充说明与最佳实践

在进行词表裁剪时,除了代码实现外,还需注意以下几点以确保模型效果稳定:

  1. 特殊 Token 保留:务必保证 <pad>, <eos>, <unk>, <bos> 等特殊符号的索引在原模型和新模型中保持一致。如果这些符号被移除或索引变更,可能导致推理时的解码错误或序列截断异常。
  2. 分词策略一致性:确保新词表使用的分词算法(如 BPE 或 WordPiece)与原模型兼容。如果仅仅是过滤了部分 Token,需确认剩余 Token 的组合方式不会改变原有的语义分割逻辑。
  3. 未登录词处理:检查新词表中是否保留了足够的 <unk> 或类似兜底机制,以防遇到新词表之外的罕见词汇导致 OOV(Out Of Vocabulary)问题。
  4. 量化兼容性:如果后续计划对模型进行量化(如 INT8/INT4),裁剪后的词表结构必须重新适配量化算子的输入要求。
  5. 测试覆盖:建议在多个典型场景(如翻译、摘要、问答)下测试裁剪后的模型,确保特定领域的性能没有明显下降。

通过上述步骤,您可以有效地针对特定语言环境优化大语言模型,在保持性能的同时提升部署效率。

目录

  1. 大语言模型词表裁剪方法与实践
  2. 前言
  3. 词表裁剪原理
  4. 准备工作
  5. 代码实现
  6. 1. 基础裁剪类
  7. 2. Bloom 模型专用裁剪器
  8. 3. 执行裁剪与验证
  9. 需要进行裁剪的模型路径
  10. 自己制作的词表的路径
  11. 裁剪
  12. 检查裁剪的模型与原模型是否一致
  13. 运行结果示例
  14. 补充说明与最佳实践

更多推荐文章

查看全部
  • 本地部署 Flux.1 绘画工具实测:中低显存设备的高质量生成方案
  • Mac M3 安装 Antigravity Agent 显示已损坏问题解决方案
  • Java 字符处理:char、String 与 StringBuilder 详解
  • Microsoft Edge WebView2 Runtime 部署与调试指南
  • Visual C++ 运行库安装故障诊断与修复指南
  • 数论基础:最大公约数与最小公倍数的原理及实现
  • OpenClaw 接入飞书机器人与 Kimi2.5 配置指南
  • C++从零实现Json-Rpc框架:项目介绍、技术选型与环境搭建
  • Kali Linux 2025.4 在 VMware 中鼠标无法显示问题解决方案
  • Java Web 开发基础:Spring Web MVC 核心注解详解
  • 理解 RAG 真实边界:Spring AI 三种架构落地实践
  • 人工智能、机器学习与深度学习的关系详解
  • DeepSeek-R1-Distill-Llama-8B 模型安全与对抗攻击防护
  • Whisper OpenAI 开源语音识别工具安装与使用指南
  • 从零开始创建 cli-progress 自定义预设:打造独一无二进度条样式
  • 人像卡通化 AI 工具使用指南:基于 DCT-Net 模型的快速转换
  • C++ 内存管理进阶:从裸指针到智能指针的实战指南
  • Java 中的原码、反码与补码深入解析
  • C++11 新特性详解:Lambda、移动语义与可变参数模板
  • HarmonyOS6 RcButton 组件样式系统深度剖析

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online