大语言模型的输入:tokenize

对于非 NLP 专业的人来说,要向理解大语言模型的基础其实是非常不容易的。在有了一定的神经网络基础、数学基础之后,算是可以更进一步了,在了解LLM的系列中,大概可以分成几个部分:输入(即本文的tokenize)、计算(Attention)、输出(beam search/top-k)。在本篇中:(a) 通过代码实践观察大模型(GPT2)如何进行 tokenize;(b) 如何查看 token id 列表 (c) 观察模型中所有token的 Embedding Matrix。

这是我的大语言(LLM)学习系列中的一篇,完整的学习路径参考:我的大模型学习路线

1. 理解 tokenize

1.1 Token ID

这里我们使用如下的提示词,来看看大模型是如何处理的:It’s very hot in summer. Swimming is

大模型会使用预先设计好的“tokenize”实现,将上述的句子分解成独立的“Token”,并转换为对应的“Token ID”,而每个Token,都有自己的编码,也就是 Embedding ,这些 Embedding 就最终作为大语言模型的输入。

对于上述的句子,“openai-community/gpt2” 在进行 tokenize 之后对应的 Token 和 Token ID 如下:

itsveryhotinsummer . Swimmingis
Token ID1026447247 82 84530242873931 13 245127428318
TokenItâĢĻsĠveryĠhotĠinĠsummer.ĠSwimmingĠis

“openai-community/gpt2” 使用了较为常见的BPE(Byte Pair Encoding)对句子进行处理,把每个词语按照“subword”进行处理,例如:

  • “Swimming”拆分为“Sw”与“imming”
  • 这里两个 Token 447、247 组成特殊字符 ‘ (撇号)
  • Ġ(U+0120)的作用:表示这是一个新的词语(而不是一个拆分后子词),可以看到 “Swimming”在拆分后的“imming”前面并没有Ġ,表示这是一个拆分后的子词

1.2 词表大小

“openai-community/gpt2” 模型的词表大小为:50257。词表中的前三个 Token 为 ’emb’、 ‘ĠDraft’、 ‘Ġreinvent’,对应的 Token ID 为 24419、 13650、 36608。

1.3 根据 Token ID 打印字符

这里打印了 Token ID 为 0、 1、 2 和 50254、 50255、 50256 的几个字符如下:

Token IDChar
0!
1
2#
50254Ġinformants
50255Ġgazed
50256<|endoftext|>
--- Token ID 转换为 Token 字符 ---
Token ID: 0 | 对应 Token 字符: '!'
Token ID: 1 | 对应 Token 字符: '"'
Token ID: 2 | 对应 Token 字符: '#'
Token ID: 50254 | 对应 Token 字符: 'Ġinformants'
Token ID: 50255 | 对应 Token 字符: 'Ġgazed'
Token ID: 50256 | 对应 Token 字符: '<|endoftext|>'

2. Token Embedding

在大模型的通常是从 Embedding 开始的,即对于所有字符的处理,都是依赖字符对应的“向量”。所以,大致的处理逻辑是这样:一个句子,先切分为 Token,然后根据 Token ID 在“Embedding Matrix”中找到对应的“向量”,把该“向量”组作为输入。

这里,我们观察上述示例句子,根据对应的 Token ID 可以查询到对应的向量。因为这里的向量是768维的,这里仅显示前三个维度的分量,如下:

Token      | wte[:3]                         |
----------------------------------------------
It | [0.039, -0.0869, 0.0662 ,...] |
âĢ | [-0.075, 0.0948, -0.0034,...] |
Ļ | [-0.0223, 0.0182, 0.2631 ,...] |
s | [-0.064, -0.0469, 0.2061 ,...] |
Ġvery | [-0.0553, -0.0348, 0.0606 ,...] |
Ġhot | [0.0399, -0.0053, 0.0742 ,...] |
Ġin | [-0.0337, 0.0108, 0.0293 ,...] |
Ġsummer | [0.0422, 0.0138, -0.0213,...] |
. | [0.0466, -0.0113, 0.0283 ,...] |
ĠSw | [0.0617, 0.0373, 0.1018 ,...] |
imming | [-0.1385, -0.1774, -0.0181,...] |
Ġis | [-0.0097, 0.0101, 0.0556 ,...] |

更为完整的,上述的每个词语对应的向量是一个 1×768 的向量;整个句子 12 个向量,可以理解为一个 12×768 的输入矩阵。对于各 LLM 来说,这通常就是其将要处理的输入。

3. Positional encoding

在“GPT-2”使用了“Learned Positional Embeddings”(注:与 Transformer 论文中固定的Sinusoidal 实现不同)。这是一个 \(L \times d \) 的矩阵,其中,\(L \) 是最大接收字符数,\(d \) 是 Token Embedding 的维度。该矩阵通常随机初始化,最终通过训练确定。在“GPT-2”中,最终训练后的矩阵有如下形式:

pos_emb_layer = model.transformer.wpe

# .weight.data : Embedding Matrix(PyTorch Tensor)
pos_emb_matrix = pos_emb_layer.weight.data

print(pos_emb_matrix[:12,:3])

输入如下:

tensor([[-0.0188, -0.1974,  0.0040],
        [ 0.0240, -0.0538, -0.0949],
        [ 0.0042, -0.0848,  0.0545],
        [-0.0003, -0.0738,  0.1055],
        [ 0.0076, -0.0251,  0.1270],
        [ 0.0096, -0.0339,  0.1312],
        [ 0.0027, -0.0205,  0.1196],
        [ 0.0025, -0.0032,  0.1174],
        [-0.0012, -0.0018,  0.1110],
        [ 0.0049,  0.0021,  0.1178],
        [ 0.0016,  0.0062,  0.1004],
        [-0.0036,  0.0175,  0.1068]])

示例与代码

环境准备

!pip install transformers torch

from transformers import AutoTokenizer, AutoModelForCausalLM

MODEL_NAME = "openai-community/gpt2"
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
model = AutoModelForCausalLM.from_pretrained(MODEL_NAME)

对句子进行 tokenize

观察句子的tokenize:

text = "It’s very hot in summer. Swimming is"

# 1. 对句子进行 Tokenization
# return_tensors='pt' 表示返回 PyTorch Tensor 格式 (虽然这里我们主要看 IDs)
inputs = tokenizer(text, return_tensors='pt')

# 2. 打印 Tokenization 结果
print(f"--- 原始句子:{text} ---")

# a. 打印 Token ID Tensor
print("Token IDs (Tensor):")
print(inputs['input_ids'])

# b. 将 Token ID 转换回可读的 Token (Word Pieces)
# .squeeze() 是为了去除 batch 维度
tokens = tokenizer.convert_ids_to_tokens(inputs['input_ids'].squeeze().tolist())
print("\nToken List (可读文本 Tokens):")
print(tokens)

# c. 打印 Attention Mask (1 表示是有效 Token,0 表示是 Padding Token)
print("\nAttention Mask:")
print(inputs['attention_mask'])

--------------
--- output ---
--------------

--- 原始句子:It’s very hot in summer. Swimming is ---
Token IDs (Tensor):
tensor([[ 1026,   447,   247,    82,   845,  3024,   287,  3931,    13,  2451,
         27428,   318]])

Token List (可读文本 Tokens):
['It', 'âĢ', 'Ļ', 's', 'Ġvery', 'Ġhot', 'Ġin', 'Ġsummer', '.', 'ĠSw', 'imming', 'Ġis']

Attention Mask:
tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])

词表大小查看

查看词表大小,并打印词表中的前20个词:

# 获取词汇表大小
vocab_size = tokenizer.vocab_size
print(f"--- GPT-2 模型支持的 Tokenize 总数 (词汇表大小): {vocab_size} ---")

# 打印所有 Tokenize
# tokenizer.get_vocab() 返回的是一个字典 {token: id}
print("\n--- 打印前 20 个 Token (用于示例): ---")
vocab = tokenizer.get_vocab()
count = 0
for token, id in vocab.items():
    if count < 20:
        # 使用 repr() 确保特殊字符(如空格 'Ġ')能被清晰展示
        print(f"ID: {id:<5} | Token: {repr(token)}")
        count += 1
    else:
        break

--------------
--- output ---
--------------

--- GPT-2 模型支持的 Tokenize 总数 (词汇表大小): 50257 ---

--- 打印前 20 个 Token (用于示例): ---
ID: 24419 | Token: 'emb'
ID: 13650 | Token: 'ĠDraft'
ID: 36608 | Token: 'Ġreinvent'
ID: 36171 | Token: 'Recommended'
ID: 20706 | Token: 'aunting'
ID: 39558 | Token: 'Ġprotagonists'
ID: 49309 | Token: 'raised'
ID: 20589 | Token: 'Ġwicked'
ID: 43074 | Token: 'ĠâĿ'
ID: 22792 | Token: 'ĠTut'
ID: 21620 | Token: 'erate'
...

根据 Token ID 打印字符

# 待查询的 Token ID 列表
target_ids = [0, 1, 2, 50254 ,50255, 50256]

tokens = tokenizer.convert_ids_to_tokens(target_ids)

print(f"--- Token ID 转换为 Token 字符 ---")
for id, token in zip(target_ids, tokens):
    # 使用 repr() 确保任何特殊的不可见字符(如空格或控制字符)能被清晰地展示
    print(f"Token ID: {id:<5} | 对应 Token 字符: {repr(token)}")

# 额外打印这几个 Token 在词汇表中的 ID,以确认其对应关系
# 这里的 token.id 并不是直接从 1, 2, 3 来的,而是从 tokenizer.get_vocab() 中查到的
# 这是一个辅助验证步骤,确保 Tokenizer 的行为符合预期。
print("\n--- 辅助验证 ---")
for token in tokens:
    token_id_check = tokenizer.convert_tokens_to_ids(token)
    print(f"Token 字符: {repr(token):<10} | 查验 ID: {token_id_check}")

--------------
--- output ---
--------------

--- Token ID 转换为 Token 字符 ---
Token ID: 0     | 对应 Token 字符: '!'
Token ID: 1     | 对应 Token 字符: '"'
Token ID: 2     | 对应 Token 字符: '#'
Token ID: 50254 | 对应 Token 字符: 'Ġinformants'
Token ID: 50255 | 对应 Token 字符: 'Ġgazed'
Token ID: 50256 | 对应 Token 字符: '<|endoftext|>'

根据 Token ID 查询对应向量

在模型中,“词向量”(准确的应该是Token向量)存储在一个Embedding Matrix 中,可以使用如下的代码获取每个 token 对应 Embedding 后的向量:

target_token = "ĠSw"  # 注意前面的特殊符号,确保它是模型词汇表中的 Token
target_id = tokenizer.convert_tokens_to_ids(target_token)
print(target_id)

# embedding_matrix[target_id]
target_embedding = embedding_matrix[target_id]
print(target_embedding[:5].numpy())

--------------
--- output ---
--------------

2451
[ 0.06167513  0.03733223  0.10182938  0.04881619 -0.09597597]
Embedding 层的 dtype: torch.float32
整个模型的默认 dtype: torch.float32

句子到 Embedding 向量

text = "It’s very hot in summer. Swimming is"
inputs = tokenizer(text, return_tensors='pt')
print("Token IDs (Tensor):")
input_ids_tensor = inputs['input_ids']
input_ids_list = input_ids_tensor.squeeze().tolist()

for index, token_int in enumerate(input_ids_list):
    token_char = tokenizer.convert_ids_to_tokens(token_int)
    print(f"Token ID {token_int:<5} | Token: {repr(token_char):<9} | {embedding_matrix[token_int][:3]}")

--------------
--- output ---
--------------

Token ID 1026  | Token: 'It'      | tensor([ 0.0390, -0.0869,  0.0662])
Token ID 447   | Token: 'âĢ'      | tensor([-0.0750,  0.0948, -0.0034])
Token ID 247   | Token: 'Ļ'       | tensor([-0.0223,  0.0182,  0.2631])
Token ID 82    | Token: 's'       | tensor([-0.0640, -0.0469,  0.2061])
Token ID 845   | Token: 'Ġvery'   | tensor([-0.0553, -0.0348,  0.0606])
Token ID 3024  | Token: 'Ġhot'    | tensor([ 0.0399, -0.0053,  0.0742])
Token ID 287   | Token: 'Ġin'     | tensor([-0.0337,  0.0108,  0.0293])
Token ID 3931  | Token: 'Ġsummer' | tensor([ 0.0422,  0.0138, -0.0213])
Token ID 13    | Token: '.'       | tensor([ 0.0466, -0.0113,  0.0283])
Token ID 2451  | Token: 'ĠSw'     | tensor([0.0617, 0.0373, 0.1018])
Token ID 27428 | Token: 'imming'  | tensor([-0.1385, -0.1774, -0.0181])
Token ID 318   | Token: 'Ġis'     | tensor([-0.0097,  0.0101,  0.0556])

Embedding Matrix

查看 Embedding Matrix

# model.transformer.wte (Word Token Embeddings)
embedding_layer = model.transformer.wte

# .weight.data : Embedding Matrix(PyTorch Tensor)
embedding_matrix = embedding_layer.weight.data

print(embedding_matrix.shape)

--------------
--- output ---
--------------

torch.Size([50257, 768])

最后

大模型训练的第一步就是对于语料库(corpus)的处理,即将所有的语料转换为大模型训练能够接受的输入,即:tokenize。该过程会将语料库切分为独立的句子,多个句子可以作为一个批次(batch)作为输入进行训练。

Leave a Reply

Your email address will not be published. Required fields are marked *