大模型tokenizer重构流程

大模型tokenizer层再训练(选取Qwen7B试验,重构token层)

最近公司可能想训练一个蛋白质大模型,需要了解一下大模型tokenizer重构,之后可能要训练,这里做了一定的总结。

文章目录

  • [1. 首先查看Qwen2.5 7B基本信息](#1. 首先查看Qwen2.5 7B基本信息)
  • [2. 重构tokenizer整体流程](#2. 重构tokenizer整体流程)
    • [2.1 步骤一:准备语料](#2.1 步骤一:准备语料)
    • [2.2 步骤二:训练 tokenizer](#2.2 步骤二:训练 tokenizer)
    • [2.3 步骤三:替换原模型目录中的 3 个 tokenizer 文件](#2.3 步骤三:替换原模型目录中的 3 个 tokenizer 文件)
    • [2.4 步骤四:加载 tokenizer,验证](#2.4 步骤四:加载 tokenizer,验证)
    • [2.5 步骤五:加载、重构、保存模型](#2.5 步骤五:加载、重构、保存模型)
      • [加载模型 + 验证当前 embedding 尺寸](#加载模型 + 验证当前 embedding 尺寸)
      • [resize_token_embeddings(new_vocab_size) 调整维度](#resize_token_embeddings(new_vocab_size) 调整维度)
      • [保存新的模型权重 + tokenizer](#保存新的模型权重 + tokenizer)
    • [2.6 步骤六:检查](#2.6 步骤六:检查)
  • [3. 构建tokenizer核心:选择分词算法](#3. 构建tokenizer核心:选择分词算法)
    • [3.1 Word-level Tokenizer(词级分词器)](#3.1 Word-level Tokenizer(词级分词器))
    • [3.2 Character-level Tokenizer(字符级)](#3.2 Character-level Tokenizer(字符级))
    • [3.3 Byte-level Tokenizer(字节级)](#3.3 Byte-level Tokenizer(字节级))
    • [3.4 BPE (Byte Pair Encoding)](#3.4 BPE (Byte Pair Encoding))
    • [3.5 WordPiece](#3.5 WordPiece)
    • [3.6 Unigram Language Model(概率模型)](#3.6 Unigram Language Model(概率模型))
    • [3.7 SentencePiece(分词框架)](#3.7 SentencePiece(分词框架))
    • [3.8 Tiktoken(OpenAI 专用)](#3.8 Tiktoken(OpenAI 专用))

1. 首先查看Qwen2.5 7B基本信息

复制代码
✅ Tokenizer 加载成功!

📌 类型: <class 'transformers.models.qwen2.tokenization_qwen2_fast.Qwen2TokenizerFast'>
📦 所使用的 tokenizer 文件: /home/liuzhao/models/Qwen/Qwen2___5-7B
🧱 词表大小(vocab size): 151643
🔢 特殊 token id: 
  [PAD]: <|endoftext|> -> id: 151643
  [BOS]: None -> id: None
  [EOS]: <|endoftext|> -> id: 151643
  [UNK]: None -> id: None

🧪 示例 token 编码:
Decoded: 你好,蛋白质
  'ä½łå¥½' -> 108386
  'ï¼Į' -> 3837
  'èĽĭçĻ½è´¨' -> 107151
Sliding Window Attention is enabled but not implemented for `sdpa`; unexpected results may be encountered.
Loading checkpoint shards: 100%|█████████████████████████████████████████████████████████████████████████████| 4/4 [00:08<00:00,  2.00s/it]

✅ 模型加载成功!
📐 Embedding 层形状: torch.Size([152064, 3584])
🔢 Embedding 参数数量: 544997376

📊 模型结构摘要:
Qwen2ForCausalLM(
  (model): Qwen2Model(
    (embed_tokens): Embedding(152064, 3584)
    (layers): ModuleList(
      (0-27): 28 x Qwen2DecoderLayer(
        (self_attn): Qwen2Attention(
          (q_proj): Linear(in_features=3584, out_features=3584, bias=True)
          (k_proj): Linear(in_features=3584, out_features=512, bias=True)
          (v_proj): Linear(in_features=3584, out_features=512, bias=True)
          (o_proj): Linear(in_features=3584, out_features=3584, bias=False)
        )
        (mlp): Qwen2MLP(
          (gate_proj): Linear(in_features=3584, out_features=18944, bias=False)
          (up_proj): Linear(in_features=3584, out_features=18944, bias=False)
          (down_proj): Linear(in_features=18944, out_features=3584, bias=False)
          (act_fn): SiLU()
        )
        (input_layernorm): Qwen2RMSNorm((3584,), eps=1e-06)
        (post_attention_layernorm): Qwen2RMSNorm((3584,), eps=1e-06)
      )
    )
    (norm): Qwen2RMSNorm((3584,), eps=1e-06)
    (rotary_emb): Qwen2RotaryEmbedding()
  )
  (lm_head): Linear(in_features=3584, out_features=152064, bias=False)
)

📦 模型总参数量: 7,615,616,512

✅ 分析结果

🔹 Tokenizer 分析:

项目 说明
Tokenizer 类 Qwen2TokenizerFast 使用 transformers 中快速实现,基于 tokenizer.model(SentencePiece)
词表大小 151643 表示 tokenizer 可用的 token 数量
[PAD] token `< endoftext
[EOS] token `< endoftext
[BOS] token None 未定义,模型可能默认不需要
[UNK] token None 未定义,可能使用 fallback 机制
示例编码 token 输出乱码 因为使用了 byte-level BPE,按字节切分后再 BPE,token 看似乱码但是正确的 UTF-8 字节表示

🔹 模型结构分析:

项目 说明
Embedding 大小 [152064, 3584] vocab size(包括 special tokens)为 152064,hidden size 为 3584
层数 28 Transformer decoder 层数
隐藏维度(hidden size) 3584 词向量维度,也是注意力/FFN 输入输出维度
Attention 参数 KV projection 输出维度为 512 使用 多头注意力 + 低秩投影(multi-query or grouped attention)优化推理速度
FFN维度(MLP) 18944 表示采用大容量前馈网络,约为 hidden size 的 5.3 倍
LayerNorm 类型 Qwen2RMSNorm 自定义 RMSNorm,代替标准 LayerNorm
Rotary Embedding 支持 RoPE,用于 position encoding
输出层(lm_head)维度 [3584, 152064] 最终投影到 vocab 空间用于生成预测

2. 重构tokenizer整体流程

步骤 操作 工具/说明
1 准备生物医药语料(每行一个样本) protein.txt
2 使用 HuggingFace tokenizers 训练 tokenizer 输出 tokenizer.json / vocab.json / tokenizer_config.json
3 替换原模型目录中的 3 个 tokenizer 文件 cp *.json Qwen2.5-7B/
4 加载 tokenizer,验证 vocab size、分词逻辑 AutoTokenizer.from_pretrained(..., trust_remote_code=True)
5 加载模型,并执行 resize_token_embeddings(),重构embedding以及输出层,保存模型 自动调整 embedding + lm_head
6 检查 shape:model.get_input_embeddings().weight.shape 和 model.lm_head.weight.shape 应该都是 (padded_vocab_size, hidden_size)

2.1 步骤一:准备语料

由于现在没有语料数据,暂时构建伪数据,尝试跑通流程:

/home/liuzhao/tokenizer_construct/fack_data/protein_mock.txt

复制代码
MVHLTPEEKSAVTALWGKVNVDEVGGEALGRLLVVYPWTQ
MKVIFLGAVIGTILLISYGIR
MGDVEKGKKIFIMKCSQCHTVEKGGKHKTGPNLHGLFGRK
GAVAGVAGAAAKAAAKAAAKAA
MSTAVLGVLIFVGLVGMAAWTSRI
MAAAKAAAEAAAKAKAAAEAA
MEFVHLVYFGLVKGL
MNSFEMLRILGIIAASTLSLTI
MKAVLAGLVAVGLTVLAAAGA
MGGAVILFVLIGTFTALLAG

这 10 条伪序列涵盖了常见氨基酸字母(A、V、G、L、M、K、T、S、F、E...),不包含任何非法字符,适合 BPE/ByteLevel 分词训练。

2.2 步骤二:训练 tokenizer

分词算法

分词算法(Tokenization Algorithm)就是把原始文本(如"你好"或蛋白质序列)转换成模型能理解的离散符号(token)序列的过程。

它是大模型理解输入的第一步,决定了:

​ • 模型看到的"词"是啥?

​ • 每个 token 的 ID 是多少?

​ • 最终 embedding 层输入长什么样?

✅ 常见的分词算法一览(用于大语言模型 LLM)

分词算法 所属模型/特点 是否可训练 优点 缺点
Word-level 古早模型(Word2Vec) ❌ 预定义词表 简单直观 无法处理新词、词表爆炸
Character-level GPT-Char、汉字模型 ❌ 固定单字 不丢信息,适合小语种 序列过长,不压缩
Byte-level GPT2、Qwen2 ❌ 固定每个字节 跨语言统一处理 不压缩子词关系
BPE(Byte Pair Encoding) GPT2、Qwen2、LLama ✅ 可训练 平衡词频、可学习 初始复杂,需 merge 文件
Unigram LM(SentencePiece) T5、mT5 ✅ 可训练 支持多种粒度 训练慢、实现复杂
WordPiece BERT ✅ 可训练 词干拼接,较紧凑 不如 BPE 高效
Tiktoken GPT-3.5 / 4(OpenAI) ❌ 固定预定义 极度高效 封闭,无法训练

✅ 举例:

用蛋白质序列构建自己的 tokenizer,然后替换 Qwen2.5 的原生 tokenizer。

🧬 你的输入语料:

复制代码
MVHLTPEEKSAVTALWGK...

这是蛋白质序列,由一串大写字母组成,每个字母表示一种氨基酸。

🧩 如果用不同分词算法会变成什么?

分词算法 分词结果 特点
Character-level ['M', 'V', 'H', 'L', 'T', ...] 每个氨基酸是一个 token
Byte-level BPE(你现在用的) ['M', 'V', 'H', 'L', 'T', 'PE', 'EKS', ...] 高频组合被合并为 token,压缩效果更好
Word-level 整句作为一个 token(不适用) 无法处理蛋白质
Tiktoken ['MV', 'HL', 'TP', 'EE', ...](预定义) 可能会错误分割
SentencePiece(Unigram) ['MVHL', 'TP', 'EEKS'] 基于语言概率构建,合并子序列更灵活

✅ 那么,分词算法的作用就是:

把一条「原始蛋白质序列」分解成适合模型使用的 token 序列,再将每个 token 映射成一个 ID,再送入 embedding 层中。

✅ ByteLevel BPE 有什么特别的?

ByteLevel BPE = Byte-level + BPE 合体

特性 说明
ByteLevel 文本按字节处理,支持各种字符集(中英文、特殊字符、符号、换行、空格)
BPE(可训练) 高频子词自动 merge,词表更精简,更适合短词/序列建模
优势 对蛋白质这种「结构性、定长、重复性高」的字符序列特别友好

所以我们说:

ByteLevel BPE 是当前 LLM 中最通用、兼容性最强、训练效率高的分词方法之一。

✅ 选 ByteLevel BPE 的理由:

原因 说明
蛋白质本身是字符序列 天然适合 ByteLevel
子串重复率高(如 AVAVAV) BPE merge 后可以变成一个 token,提升压缩效率
不需要复杂语义模型 不像自然语言,要解析句法
想替换大模型(Qwen2.5)的 tokenizer Qwen2 就是用 ByteLevel BPE,所以兼容性满分 ✅

✅ 最终总结:

分词算法是模型输入的第一道大门,不同算法决定了模型"看到的单位"是词、子词、字节还是字符;而 ByteLevel BPE 是一种兼具高效、灵活、可训练的主流方法,适用于你构建蛋白质 tokenizer 的任务。

BPE流程

完整可视化 BPE 的合并(merge)过程,包括:

  • 初始化状态;
  • 每轮统计 + 合并;
  • 最终得到词表(vocab)和合并规则(merges)。

✅ 示例文本语料:

我们用这个简单但有重复的英文短语做语料(共 4 条):

复制代码
low lower lowest lowly

🧾 Step 1:初始化为字符级 token + <\w> 结尾标记

每个词都拆成字符 + 特殊符号 表示词结束:

原始词 初始 token 序列
low l o w
lower l o w e r
lowest l o w e s t
lowly l o w l y

🔁 Step 2:统计频率最高的相邻 token 对

我们数一下所有相邻字符对出现的次数:

Token Pair 出现次数
(l, o) 4 ✅
(o, w) 4 ✅
(w, ) 1
(w, e) 2
(e, r) 1
(e, s) 1
(s, t) 1
(w, l) 1
(l, y) 1

🔨 Step 3:合并频次最高的 pair

我们选中 ('l', 'o') → "lo",进行第一次 merge。

更新所有词:

更新后 token 合并
lo w l+o ✅
lo w e r l+o ✅
lo w e s t l+o ✅
lo w l y l+o ✅

🔁 Step 4:继续迭代统计 + 合并

第 2 次 merge:

pair: ('lo', 'w') → "low"

更新后:

更新后 token
low
low e r
low e s t
low l y

第 3 次 merge:

pair: ('low', 'e') → "lowe"

更新:

复制代码
low </w>
lowe r </w>
lowe s t </w>
low l y </w>

第 4 次 merge:

pair: ('lowe', 'r') → "lower"

更新:

复制代码
low </w>
lower </w>
lower s t </w>
low l y </w>

第 5 次 merge:

pair: ('lower', 's') → "lowers"

更新:

复制代码
low </w>
lower </w>
lowers t </w>
low l y </w>

第 6 次 merge:

pair: ('lowers', 't') → "lowest"

更新:

复制代码
low </w>
lower </w>
lowest </w>
low l y </w>

第 7 次 merge:

pair: ('low', 'l') → "lowl"

然后再合并 ('lowl', 'y') → "lowly"

最终分词表:

原词 最终 token 序列
low → low
lower → lower
lowest → lowest
lowly → lowly

🎉 每个词都被压缩成一个 token(理想情况),这就是 BPE 的"学习高频组合"的本事。

📦 最终 vocab(词表)示意:

复制代码
{
  "l": 0,
  "o": 1,
  "w": 2,
  "e": 3,
  "r": 4,
  "s": 5,
  "t": 6,
  "y": 7,
  "</w>": 8,
  "lo": 9,
  "low": 10,
  "lowe": 11,
  "lower": 12,
  "lowers": 13,
  "lowest": 14,
  "lowl": 15,
  "lowly": 16
}

🪄 最终 merges.txt:

复制代码
l o
lo w
low e
lowe r
lower s
lowers t
low l
lowl y

每一行就是一轮 merge 的规则,按顺序合并

🔎 总结一下流程:

步骤 说明
初始化 将词拆成字符,加词尾标记
多轮统计 + 合并 每轮合并出现频率最高的相邻 token
构建 merges.txt 合并记录即为 tokenizer 的"知识"
应用时分词 遇到相同子串时就能直接编码为 token,提高效率

构建tokenizer

python 复制代码
import os
import json
from tokenizers.implementations import ByteLevelBPETokenizer
from transformers import GPT2TokenizerFast

# === 配置路径 ===
input_file = "/home/liuzhao/tokenizer_construct/fack_data/protein_mock.txt"
output_dir = "/home/liuzhao/tokenizer_construct/new_tokenizer"
vocab_size = 512  # 小语料建议设置小一点

# === Step 1: 训练 ByteLevelBPE Tokenizer ===
tokenizer = ByteLevelBPETokenizer()

# 特殊 token(你可以根据自己需求添加)
special_tokens = ["<|endoftext|>", "<unk>", "<pad>"]

# 训练 tokenizer,生成 vocab.json + merges.txt
tokenizer.train(
    files=input_file,
    vocab_size=vocab_size,
    min_frequency=1,
    special_tokens=special_tokens,
)

# 创建输出目录
os.makedirs(output_dir, exist_ok=True)

# 保存 vocab.json / merges.txt
tokenizer.save_model(output_dir)

# === Step 2: 写入 tokenizer_config.json,指定 tokenizer 类型为 GPT2 ===
tokenizer_config = {
    "model_type": "gpt2",  # 关键!明示为 GPT2-style,避免 tiktoken 报错
    "unk_token": "<unk>",
    "eos_token": "<|endoftext|>",
    "pad_token": "<pad>"
}

with open(os.path.join(output_dir, "tokenizer_config.json"), "w") as f:
    json.dump(tokenizer_config, f)

# === Step 3: 使用 GPT2TokenizerFast 加载 + 保存 HuggingFace 格式 ===
wrapped_tokenizer = GPT2TokenizerFast.from_pretrained(output_dir)
wrapped_tokenizer.save_pretrained(output_dir)

# === Step 4: 打印验证信息 ===
print(f"✅ Tokenizer 构建成功,已保存至 {output_dir}")
print(f"🧱 词表大小: {wrapped_tokenizer.vocab_size}")
print(f"🔤 示例分词: {wrapped_tokenizer.tokenize('MVHLTPEEKS')}")

输出:

复制代码
(tokenizer) root@420GP-252:/home/liuzhao/tokenizer_construct# python build_tokenizer.py 
[00:00:00] Pre-processing files (0 Mo)    █████████████████████████████████████████████████████████████████████                100%
[00:00:00] Tokenize words                 █████████████████████████████████████████████████████████████████████ 14       /       14
[00:00:00] Count pairs                    █████████████████████████████████████████████████████████████████████ 14       /       14
[00:00:00] Compute merges                 █████████████████████████████████████████████████████████████████████ 183      /      183
✅ Tokenizer 构建成功,已保存至 /home/liuzhao/tokenizer_construct/new_tokenizer
🧱 词表大小: 259
🔤 示例分词: ['M', 'V', 'H', 'L', 'T', 'P', 'E', 'E', 'K', 'S']

2.3 步骤三:替换原模型目录中的 3 个 tokenizer 文件

将生成的文件替换原来Qwen的tokenizer文件。

2.4 步骤四:加载 tokenizer,验证

python 复制代码
# check_tokenizer_replacement.py

from transformers import AutoTokenizer
import os

# 替换后的模型目录
tokenizer_dir = "/home/liuzhao/models/Qwen/Qwen2.5-7B-change"

# 加载 tokenizer(关键:信任 remote_code)
tokenizer = AutoTokenizer.from_pretrained(
    tokenizer_dir,
    trust_remote_code=True
)

# 打印基本信息
print("✅ Tokenizer 加载成功!\n")
print(f"📌 类型: {type(tokenizer)}")
print(f"📦 路径: {tokenizer.name_or_path}")
print(f"🧱 词表大小: {tokenizer.vocab_size}")

# 分词验证(用你熟悉的蛋白质序列)
test_text = "MVHLTPEEKS"
tokens = tokenizer.tokenize(test_text)
token_ids = tokenizer.convert_tokens_to_ids(tokens)
decoded = tokenizer.decode(token_ids)

print(f"\n🔤 分词结果: {tokens}")
print(f"🔢 Token IDs: {token_ids}")
print(f"🔁 解码还原: {decoded}")

输出:

复制代码
(tokenizer) root@420GP-252:/home/liuzhao/tokenizer_construct# python check_tokenizer_replacement.py 
✅ Tokenizer 加载成功!

📌 类型: <class 'transformers.models.gpt2.tokenization_gpt2_fast.GPT2TokenizerFast'>
📦 路径: /home/liuzhao/models/Qwen/Qwen2.5-7B-change
🧱 词表大小: 259

🔤 分词结果: ['M', 'V', 'H', 'L', 'T', 'P', 'E', 'E', 'K', 'S']
🔢 Token IDs: [47, 56, 42, 46, 54, 50, 39, 39, 45, 53]
🔁 解码还原: MVHLTPEEKS

2.5 步骤五:加载、重构、保存模型

第 5 步:模型结构调整阶段

✅ 第五步任务总目标:

让模型结构(特别是 embedding 层和输出层 lm_head)的维度,匹配你新 tokenizer 的词表大小(vocab_size),确保 forward 时不会报错或 shape mismatch。

🎯 第五步重新拆解为 3 个子任务(建议逐步执行):

加载模型 + 验证当前 embedding 尺寸

内容
✅ 检查当前 embedding 层 Embedding(num_embeddings, hidden_size) 的维度
✅ 检查 lm_head 的输出维度是否一致
✅ 打印当前 vocab_size 与 tokenizer vocab 是否对齐

✅ 示例输出结构检查:

复制代码
print(model.model.embed_tokens.weight.shape)
print(model.lm_head.out_features)

resize_token_embeddings(new_vocab_size) 调整维度

复制代码
model.resize_token_embeddings(new_vocab_size)
调整的内容 说明
model.model.embed_tokens embedding 层的 token 数变成新 vocab size(如 259)
model.lm_head 输出维度也会同步调整(transformers 自动处理)

保存新的模型权重 + tokenizer

​ • 保存为 Qwen2.5-7B-with-protein-tokenizer/

​ • 之后可以重新加载、微调或推理

复制代码
model.save_pretrained(output_dir)
tokenizer.save_pretrained(output_dir)

resize_model_embedding.py

python 复制代码
import os
from transformers import AutoTokenizer, AutoModelForCausalLM

# === 路径配置 ===
model_path = "/home/liuzhao/models/Qwen/Qwen2.5-7B-change"
output_path = "/home/liuzhao/models/Qwen/Qwen2.5-7B-with-protein-tokenizer"

# === Step 1: 加载 tokenizer 和模型 ===
print("📦 加载 tokenizer 和模型...")
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(model_path, trust_remote_code=True)

print("✅ 加载完成!")
print(f"🔢 当前 tokenizer.vocab_size: {tokenizer.vocab_size}")
print(f"📐 原始 embedding 层形状: {model.model.embed_tokens.weight.shape}")
print(f"📐 原始 lm_head 输出形状: {model.lm_head.out_features}")

# === Step 2: 调整 embedding 和输出层大小 ===
print("\n🔧 执行 resize_token_embeddings ...")
model.resize_token_embeddings(tokenizer.vocab_size)

# 再次打印新形状
print(f"✅ 新 embedding 层形状: {model.model.embed_tokens.weight.shape}")
print(f"✅ 新 lm_head 输出形状: {model.lm_head.out_features}")

# === Step 3: 保存新模型和 tokenizer ===
print(f"\n💾 保存模型和 tokenizer 到: {output_path}")
os.makedirs(output_path, exist_ok=True)
model.save_pretrained(output_path)
tokenizer.save_pretrained(output_path)

print("\n🎉 完成!模型已保存并可用于推理或微调。")

输出:

复制代码
(tokenizer) root@420GP-252:/home/liuzhao/tokenizer_construct# python resize_model_embedding.py 
📦 加载 tokenizer 和模型...
Sliding Window Attention is enabled but not implemented for `sdpa`; unexpected results may be encountered.
Loading checkpoint shards: 100%|█████████████████████████████████████████████████████████████████████| 4/4 [00:05<00:00,  1.42s/it]
✅ 加载完成!
🔢 当前 tokenizer.vocab_size: 259
📐 原始 embedding 层形状: torch.Size([152064, 3584])
📐 原始 lm_head 输出形状: 152064

🔧 执行 resize_token_embeddings ...
✅ 新 embedding 层形状: torch.Size([259, 3584])
✅ 新 lm_head 输出形状: 259

💾 保存模型和 tokenizer 到: /home/liuzhao/models/Qwen/Qwen2.5-7B-with-protein-tokenizer

🎉 完成!模型已保存并可用于推理或微调。

2.6 步骤六:检查

首先检查tokenizer以及模型:

复制代码
✅ Tokenizer 加载成功!

📌 类型: <class 'transformers.models.gpt2.tokenization_gpt2_fast.GPT2TokenizerFast'>
📦 所使用的 tokenizer 文件: /home/liuzhao/models/Qwen/Qwen2.5-7B-with-protein-tokenizer
🧱 词表大小(vocab size): 259
🔢 特殊 token id: 
  [PAD]: <pad> -> id: 2
  [BOS]: <|endoftext|> -> id: 0
  [EOS]: <|endoftext|> -> id: 0
  [UNK]: <unk> -> id: 1

🧪 示例 token 编码:
Decoded: 你好,蛋白质
  'ä' -> 163
  '½' -> 124
  'ł' -> 257
  'å' -> 164
  '¥' -> 101
  '½' -> 124
  'ï' -> 174
  '¼' -> 123
  'Į' -> 237
  'è' -> 167
  'Ľ' -> 252
  'ĭ' -> 236
  'ç' -> 166
  'Ļ' -> 250
  '½' -> 124
  'è' -> 167
  '´' -> 115
  '¨' -> 104
Sliding Window Attention is enabled but not implemented for `sdpa`; unexpected results may be encountered.
Loading checkpoint shards: 100%|█████████████████████████████████████████████████████████████████████| 6/6 [00:05<00:00,  1.13it/s]

✅ 模型加载成功!
📐 Embedding 层形状: torch.Size([259, 3584])
🔢 Embedding 参数数量: 928256

📊 模型结构摘要:
Qwen2ForCausalLM(
  (model): Qwen2Model(
    (embed_tokens): Embedding(259, 3584)
    (layers): ModuleList(
      (0-27): 28 x Qwen2DecoderLayer(
        (self_attn): Qwen2Attention(
          (q_proj): Linear(in_features=3584, out_features=3584, bias=True)
          (k_proj): Linear(in_features=3584, out_features=512, bias=True)
          (v_proj): Linear(in_features=3584, out_features=512, bias=True)
          (o_proj): Linear(in_features=3584, out_features=3584, bias=False)
        )
        (mlp): Qwen2MLP(
          (gate_proj): Linear(in_features=3584, out_features=18944, bias=False)
          (up_proj): Linear(in_features=3584, out_features=18944, bias=False)
          (down_proj): Linear(in_features=18944, out_features=3584, bias=False)
          (act_fn): SiLU()
        )
        (input_layernorm): Qwen2RMSNorm((3584,), eps=1e-06)
        (post_attention_layernorm): Qwen2RMSNorm((3584,), eps=1e-06)
      )
    )
    (norm): Qwen2RMSNorm((3584,), eps=1e-06)
    (rotary_emb): Qwen2RotaryEmbedding()
  )
  (lm_head): Linear(in_features=3584, out_features=259, bias=False)
)

📦 模型总参数量: 6,527,478,272

test_model_with_protein_tokenizer.py

python 复制代码
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch

# === 路径配置 ===
model_path = "/home/liuzhao/models/Qwen/Qwen2.5-7B-with-protein-tokenizer"

# === Step 6.1: 加载 tokenizer 和模型 ===
print("📦 加载 tokenizer 和模型...")
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(model_path, trust_remote_code=True)
model.eval()  # 推理用

print("✅ 加载完成!\n")

# === Step 6.2: 打印模型结构参数 ===
print(f"📌 Tokenizer 类型: {type(tokenizer)}")
print(f"🧱 词表大小: {tokenizer.vocab_size}")
print(f"📐 Embedding 层形状: {model.model.embed_tokens.weight.shape}")
print(f"📊 Transformer 层数: {len(model.model.layers)}")
print(f"📐 输出层形状 (lm_head): {model.lm_head.weight.shape}\n")

# === Step 6.3: 简单推理测试 ===
test_input = "MVHLTPEEKSAVTALWGKVNV"  # 一段蛋白质序列
inputs = tokenizer(test_input, return_tensors="pt")

print("🔍 输入 token IDs:", inputs["input_ids"][0].tolist())

# 推理(限制最大长度防止 OOM)
with torch.no_grad():
    outputs = model.generate(
        input_ids=inputs["input_ids"],
        max_new_tokens=20,
        do_sample=False
    )

# 解码输出
decoded = tokenizer.decode(outputs[0])
print("\n🧪 推理结果:")
print(decoded)

输出:

复制代码
(tokenizer) root@420GP-252:/home/liuzhao/tokenizer_construct# CUDA_VISIBLE_DEVICES=5 python test_model_with_protein_tokenizer.py 
📦 加载 tokenizer 和模型...
Sliding Window Attention is enabled but not implemented for `sdpa`; unexpected results may be encountered.
Loading checkpoint shards: 100%|█████████████████████████████████████████████████████████████████████| 6/6 [00:05<00:00,  1.03it/s]
✅ 加载完成!

📌 Tokenizer 类型: <class 'transformers.models.gpt2.tokenization_gpt2_fast.GPT2TokenizerFast'>
🧱 词表大小: 259
📐 Embedding 层形状: torch.Size([259, 3584])
📊 Transformer 层数: 28
📐 输出层形状 (lm_head): torch.Size([259, 3584])

🔍 输入 token IDs: [47, 56, 42, 46, 54, 50, 39, 39, 45, 53, 35, 56, 54, 35, 46, 57, 41, 45, 56, 48, 56]
The attention mask and the pad token id were not set. As a consequence, you may observe unexpected behavior. Please pass your input's `attention_mask` to obtain reliable results.
Setting `pad_token_id` to `eos_token_id`:151643 for open-end generation.
The attention mask is not set and cannot be inferred from input because pad token is same as eos token. As a consequence, you may observe unexpected behavior. Please pass your input's `attention_mask` to obtain reliable results.

🧪 推理结果:
MVHLTPEEKSAVTALWGKVNVGVGVGVGVGVGVGVGVGVGV

3. 构建tokenizer核心:选择分词算法

通过构建tokenizer的整体流程来看,核心其实是第二步:训练tokenizer。这一步设计到分词算法的选择。

✅ 常用分词算法总览表(用途 × 原理 × 特性 × 模型支持)

分词算法 是否可训练 是否支持 OOV 压缩能力 粒度 原理简介 常用模型/场景
Word-level ❌ 否 ❌ 差 ❌ 差 按空格分词,每个词是一个 token Word2Vec(早期)、GloVe
Character-level ❌ 否 ✅ 好 ❌ 差 字/字符 每个字符是一个 token,无需词表 拼音、蛋白质、语音转写
Byte-level ❌ 否 ✅ 很好 ❌ 差 字节(UTF-8) 每个字符拆成 1-4 字节,再用 Byte 作为 token OpenAI GPT 系列(GPT-3/4)、tiktoken
BPE (Byte Pair Encoding) ✅ 是 ✅ 良好 ✅ 高 子词 高频字符对合并,构造合成词单元 GPT2、LLama、Qwen、CodeGen
WordPiece ✅ 是 ✅ 良好 ✅ 一般 子词 最大匹配 + 子词合并 BERT、ALBERT
Unigram Language Model ✅ 是 ✅ 最好 ✅ 很高 子词 训练一个概率模型,保留最优子词集 T5、mT5、XLM-R、SentencePiece
SentencePiece (框架) ✅ 是 ✅ 强 ✅ 强 子词/字节 支持 Unigram/BPE/Char/Byte 等算法 Google 系列、跨语种任务
Tiktoken ❌ 否 ✅ 固定词表 ✅ 非常高 字节 OpenAI 内部算法,压缩+速度双优 GPT-3.5、GPT-4(不可训练)

3.1 Word-level Tokenizer(词级分词器)

原理:按空格或分隔符将文本切分为完整词语。

优点

直观易理解

缺点

词表庞大,难以覆盖 OOV(未登录词)

无法泛化:如"play"和"played"是两个不同词

常见模型:Word2Vec、GloVe(早期传统方法)

3.2 Character-level Tokenizer(字符级)

原理:将每个字符(如汉字、英文字母、氨基酸等)当作一个 token。

优点

完全无 OOV 问题

极细粒度,适合结构化序列(如蛋白质)

缺点

序列太长,建模成本高

应用:蛋白质序列、拼音建模、小语种处理

3.3 Byte-level Tokenizer(字节级)

原理:将文本按 UTF-8 字节编码,每个字节为一个 token,共 256 种。

优点

无语言依赖,支持所有字符

与 GPT2、OpenAI 模型兼容性好

缺点

不会做词级压缩(每个 token 都很短)

代表实现:GPT2、Qwen2、OpenAI 的 Tiktoken

3.4 BPE (Byte Pair Encoding)

原理

从字符出发,按频率合并最常出现的 token 对,逐步构造子词

优点

可训练

高压缩比,子词有语义组合能力

高效通用,适用于各种语种和符号结构

缺点

只考虑合并频率,缺少语言建模概率

应用模型:GPT2、LLama、Qwen、CodeGen、RoBERTa(BPE with space)

3.5 WordPiece

原理

按最大匹配方式将词拆解成子词,子词带 ## 前缀表示后缀部分

优点

控制好拆分与组合平衡,稳定性强

缺点

构建过程比 BPE 更复杂

常见模型:BERT、ALBERT、DistilBERT、Electra

3.6 Unigram Language Model(概率模型)

原理

从一大批子词候选集中,选择能最优压缩文本概率的子词集合

每个词有多种拆分路径,选择最优的

优点

最灵活可控、最适配多语种场景

缺点

构建耗时较长,训练复杂

常见模型:T5、mT5、XLM-R、Google 系列

3.7 SentencePiece(分词框架)

本质是框架,支持以下算法:

✅ Unigram(默认)

✅ BPE

✅ Character

✅ Byte

支持无空格语言(如中文、日文)

常见于 Google / multilingual 模型

3.8 Tiktoken(OpenAI 专用)

​ • 原理:压缩优化过的 UTF-8 字节映射 + 快速查表

​ • 特点

​ • 封闭系统,无法训练

​ • 提供极高推理效率和兼容性

​ • 常见模型:GPT-3.5、GPT-4(不可替换)

支持自训练的分词算法有哪些?

可自训练? 算法
✅ 可以 BPE, Unigram, WordPiece, SentencePiece
❌ 固定 Char-level, Byte-level, Tiktoken
相关推荐
阿杰学AI3 分钟前
AI核心知识32——大语言模型之多模态语音(简洁且通俗易懂版)
人工智能·ai·语言模型·自然语言处理·aigc·语音识别·多模态语音
极客BIM工作室9 分钟前
AI导读AI论文: DeepSeek-V3.2: Pushing the Frontier of Open Large Language Models
人工智能·语言模型·自然语言处理
Baihai_IDP17 分钟前
用户体验与商业化的两难:Chatbots 的广告承载困境分析
人工智能·面试·llm
带刺的坐椅1 小时前
Solon AI 开发学习13 - chat - Tool的输入输出架构及生成类
ai·chatgpt·llm·solon·mcp
莫叫石榴姐1 小时前
如何让大模型更好地理解和处理 JSON 数据?
语言模型·json
EdisonZhou1 小时前
MAF快速入门(5)开发自定义Executor
llm·aigc·agent·.net core
zhangfeng11332 小时前
suppr.wilddata.cn 文献检索,用中文搜 PubMed 一种基于大语言模型的智能搜索引擎构建方法
人工智能·搜索引擎·语言模型
啊吧怪不啊吧3 小时前
从数据到智能体大模型——cozeAI大模型开发(第一篇)
人工智能·ai·语言模型·ai编程
智泊AI13 小时前
震惊!Open AI把Transformer训练成了“几乎全部归零”!
llm
大模型教程16 小时前
开源大模型不求人!一文带你全面入门《开源大模型食用指南》
程序员·llm·agent