8.11 Hugging Face Transformers API:从结构到调用

Author

jshn9515

Published

2026-05-09

Modified

2026-05-09

前面几节里,我们已经从概念上讲完了 Transformer 的主要结构:

这一节,我们把这些结构和真实代码接口对应起来。

在实际使用中,我们通常不会从零实现一个完整 Transformer,而是会用 Hugging Face Transformers 这样的库加载预训练模型。它帮我们封装好了 tokenizer、模型结构、权重加载、forward 输出、文本生成等流程。但如果只停留在复制代码能跑的层面,很容易不知道每个 API 背后对应的是前面讲过的哪个结构。

所以这一节的重点不是把 Hugging Face Transformers 的所有参数都列一遍,而是建立一个映射关系:

我们前面讲过的 Transformer 结构,在 Hugging Face Transformers 里分别对应哪些 API?

Note
  1. 这节内容主要介绍 Hugging Face Transformers 中常用的接口,以及这些接口和 Transformer 结构之间的对应关系。由于 Transformers 库仍在持续更新,部分 API 的行为或参数可能会随着版本变化而调整。如果你在使用时发现代码和本文不完全一致,建议优先参考最新的官方文档。
  2. 这一节不要求你已经熟悉每一个具体模型。你可以先把它当作一份模型速查表:看到 AutoModel、AutoModelForCausalLM、AutoModelForSeq2SeqLM 这类接口时,能够大致判断它们对应的是 encoder-only、decoder-only,还是 encoder-decoder 结构。至于具体模型的细节,我们会在后面的章节中再展开介绍。
from pprint import pprint

import IPython.display as ipy
import torch
import torch.nn as nn
import transformers
from torch import Tensor

print('PyTorch version:', torch.__version__)
print('Transformers version:', transformers.__version__)
PyTorch version: 2.12.0+cpu
Transformers version: 5.12.1

8.11.1 Transformers API 的基本思路

使用 Hugging Face Transformers 时,最常见的流程是:

from transformers import AutoTokenizer, AutoModel

model_id = 'google-bert/bert-base-uncased'

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModel.from_pretrained(model_id)
ipy.clear_output()

这里有两个核心对象:tokenizermodeltokenizer 负责把文本变成模型能处理的数字序列;model 负责把数字序列送进 Transformer,得到输出表示或预测结果。

整体流程可以写成:

图 8.11.1:Hugging Face Transformers 的基本调用流程

也就是:

inputs = tokenizer('Hello world', return_tensors='pt')
print('Tokenizer output keys:', list(inputs.keys()))

outputs = model(**inputs)
print('Model output keys:', list(outputs.keys()))
Tokenizer output keys: ['input_ids', 'token_type_ids', 'attention_mask']
Model output keys: ['last_hidden_state', 'pooler_output']

这里的 **inputs 会把 tokenizer 返回的字典展开成模型 forward 需要的参数。常见字段包括 input_idsattention_mask,有些模型还会返回 token_type_idsposition_ids。不同模型结构需要的输入不完全一样,但 AutoTokenizer 通常会根据模型自动返回合适的字段。

outputs 也是一个结构化对象,包含了模型前向传播的结果。常见字段包括 last_hidden_statelogitspooler_outputattentionspast_key_values 等。但这些字段不一定都会出现,具体取决于模型类型、任务头,以及你在调用时设置的参数。

8.11.1.1 AutoTokenizer:从文本到 Token IDs

Transformer 不能直接处理字符串,它需要的是 token id。比如:

text = 'I love deep learning.'
inputs = tokenizer(text, return_tensors='pt')
pprint(inputs)
{'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1]]),
 'input_ids': tensor([[ 101, 1045, 2293, 2784, 4083, 1012,  102]]),
 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0]])}

其中,input_ids 表示每个 token 在词表里的编号。例如,一个句子可能被分成:

['I', 'love', 'deep', 'learning', '.']

然后映射成:

[1045, 2293, 2784, 4083, 1012]

这组数字就是 input_ids。至于开头的 101 和结尾的 102,在 BERT 里分别是 [CLS][SEP],它们是一种特殊的 token id,分别表示句子开始和结束。

attention_mask 通常用来区分哪些位置是真实 token,哪些位置是 padding。比如 batch 里有两个句子:

['I love deep learning.', 'Good morning!']

为了组成一个 batch,较短的句子需要补齐到相同长度。例如,第二个句子 padding 后可能是:

['Good', 'morning', '!', '[PAD]', '[PAD]']

这时 attention_mask 大概是:

[1, 1, 1, 0, 0]

其中,1 表示真实 token,0 表示 padding token。

这其实就是前面讲的 key_padding_mask。它用来告诉模型哪些位置可以看,哪些位置不应该参与 attention。

8.11.1.2 AutoModel:Transformer 的主体

AutoModel 会加载模型的基础 Transformer 主体,不包含特定任务头。例如:

from transformers import AutoTokenizer, AutoModel

model_id = 'google-bert/bert-base-uncased'

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModel.from_pretrained(model_id)
ipy.clear_output()

inputs = tokenizer('I love deep learning.', return_tensors='pt')
outputs = model(**inputs)
print(list(outputs.keys()))
['last_hidden_state', 'pooler_output']

对于 BERT 这类 encoder-only 模型,AutoModel 返回的是每个 token 的上下文表示。其中最常见的字段是 last_hidden_state,它表示每个 token 在最后一层 Transformer block 的输出。它的形状通常是:

(batch_size, seq_len, hidden_size)

也就是:

\[ H = [h_1, h_2, \dots, h_n] \]

其中,每个 \(h_i\) 对应第 \(i\) 个 token 的上下文表示。这正好对应前面讲的 encoder-only:

\[ X \rightarrow \text{Transformer Encoder} \rightarrow H \]

所以,如果你只想拿到句子或 token 的表示,AutoModel 就很合适。

8.11.1.3 ModelOutput:为什么不是普通 Tuple

调用模型后,我们经常看到:

outputs = model(**inputs)

然后可以用:

outputs.last_hidden_state
outputs.logits
outputs.attentions
outputs.past_key_values

这是因为 Hugging Face Transformers 的模型输出通常是 ModelOutput 的子类。它有点像字典,也有点像 tuple,但更推荐使用属性名访问。比如,outputs.logitsoutputs[0] 更清楚地表达了我们想要的是什么。不同模型、不同任务头会返回不同字段。

  • AutoModel 常见字段是 last_hidden_state
  • AutoModelForSequenceClassification 常见字段是 logits
  • AutoModelForCausalLM 常见字段是 logitspast_key_values
  • 设置 output_attentions=True 后,会额外返回 attentions
  • 设置 output_hidden_states=True 后,会额外返回 hidden_states

所以,看到 outputs 时,不要把它想成一个神秘对象,它只是把模型前向传播的结果整理成了一个结构化输出。

8.11.2 Encoder-only 模型:理解输入序列

前面我们已经看到,AutoModel 可以加载一个 Transformer 主体,并返回每个 token 的上下文表示。对于 encoder-only 模型来说,这正是它最核心的用途:通过双向 self-attention,让序列中的每个位置都能同时看到左右上下文,从而得到更适合理解任务的表示。

不过,只有上下文表示还不够。真实任务通常还需要在这些表示之上做进一步预测:如果是文本分类,我们希望给整段文本预测一个类别;如果是命名实体识别,我们希望给每个 token 预测一个标签。因此,在 Transformers 库中,除了 AutoModel 之外,还提供了许多带任务头的模型。

8.11.2.1 SequenceClassification:Encoder + 分类头

如果我们要做文本分类,通常不会只用 AutoModel,而是使用带任务头的模型:

from transformers import AutoTokenizer, AutoModelForSequenceClassification

model_id = 'google-bert/bert-base-uncased'

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForSequenceClassification.from_pretrained(
    model_id,
    num_labels=2,
)
ipy.clear_output()

它的结构可以理解成:

\[ \operatorname{Encoder} + \operatorname{Classification Head} \]

也就是:

\[ X \rightarrow \text{BERT Encoder} \rightarrow h_{\mathrm{[CLS]}} \rightarrow \text{Linear} \rightarrow \text{logits} \]

调用方式是:

inputs = tokenizer('This movie is great!', return_tensors='pt')
outputs = model(**inputs)

logits = outputs.logits
print(logits.shape)
torch.Size([1, 2])

其中,logits 就是分类头输出的原始分数。它的形状通常是:

(batch_size, num_labels)

如果 num_labels=2,就表示每个样本输出两个类别分数。这里我们并没有说明这两个类别是什么。它们可能是正面和负面,也可能是其他任何两个类别。具体含义取决于我们训练模型时用的标签。

所以,总结起来就是:

AutoModel 给你的是基础表示;AutoModelForSequenceClassification 给你的是基础表示加分类头。

这就是 Hugging Face API 命名里 For... 的含义:它表示这个模型是为某个具体任务准备的。

8.11.2.2 TokenClassification:每个 Token 一个预测

如果任务是命名实体识别,或者其他 token-level 分类,就可以使用:

from transformers import AutoModelForTokenClassification

model_id = 'google-bert/bert-base-uncased'

model = AutoModelForTokenClassification.from_pretrained(
    model_id,
    num_labels=9,
)
ipy.clear_output()

它和 sequence classification 的区别是:分类不是只对整个句子做一次,而是对每个 token 都做一次。

它的结构可以理解成:

\[ h_i \rightarrow \operatorname{Linear} \rightarrow \text{logits}_i \]

调用方式和前面类似:

inputs = tokenizer('Hugging Face is based in New York.', return_tensors='pt')
outputs = model(**inputs)

logits = outputs.logits
print(logits.shape)
torch.Size([1, 10, 9])

输出形状通常是:

(batch_size, seq_len, num_labels)

也就是说,每个 token 都有一个类别分布。这对应前面讲 encoder-only 输出怎么用时的 token-level 任务:

\[ \hat{y}_i = \mathrm{Classifier}(h_i) \]

8.11.2.3 一个完整的 Encoder-only 示例

下面是一个 BERT 风格文本表示例子:

import torch
from transformers import AutoTokenizer, AutoModel

model_id = 'google-bert/bert-base-uncased'

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModel.from_pretrained(model_id)
ipy.clear_output()

texts = [
    'I love deep learning.',
    'Transformers are powerful models.',
]

inputs = tokenizer(
    texts,
    return_tensors='pt',
    padding=True,
    truncation=True,
)

with torch.inference_mode():
    outputs = model(**inputs)

last_hidden_state = outputs.last_hidden_state
sentence_embedding = last_hidden_state.mean(dim=1)

print('Last hidden state shape:', last_hidden_state.shape)
print('Sentence embedding shape:', sentence_embedding.shape)
Last hidden state shape: torch.Size([2, 7, 768])
Sentence embedding shape: torch.Size([2, 768])

这里的 last_hidden_state 是每个 token 的上下文表示。

如果输入 batch 里有 padding,更严谨的平均池化应该结合 attention_mask,只对真实 token 求平均:

mask = inputs['attention_mask'].unsqueeze(-1)
masked_hidden = last_hidden_state * mask

sentence_embedding = masked_hidden.sum(dim=1) / mask.sum(dim=1)

这对应 encoder-only 的核心用途:

把输入文本编码成上下文表示。

8.11.3 Decoder-only 模型:自回归生成

如果说 encoder-only 模型更擅长理解已经给定的输入,那么 decoder-only 模型更擅长根据已有内容继续生成。它使用 masked self-attention,让当前位置只能看到自己之前的 token,然后一步一步预测下一个 token。

不过,模型每次前向传播并不会直接生成完整文本,而是输出每个位置的 logits,用来预测下一个 token。完整的自回归生成需要反复执行预测下一个 token + 拼回输入序列的过程。Transformers 库既允许我们直接查看 logits,也提供了 generate 接口来封装这个生成循环。

8.11.3.1 CausalLM:Decoder-only 语言模型

如果我们要加载 GPT 风格的 decoder-only 模型,通常使用:

from transformers import AutoTokenizer, AutoModelForCausalLM

model_id = 'openai-community/gpt2'

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)
ipy.clear_output()

CausalLM 的意思是 causal language modeling,也就是从左到右预测下一个 token。它对应的训练目标是:

\[ p(x_1, x_2, \dots, x_n) = \prod_{t=1}^{n} p(x_t \mid x_{<t}) \]

调用 forward 函数:

inputs = tokenizer('I love deep', return_tensors='pt')
outputs = model(**inputs)

logits = outputs.logits
print(logits.shape)
torch.Size([1, 3, 50257])

这里的 logits 形状通常是:

(batch_size, seq_len, vocab_size)

它表示每个位置对词表中所有 token 的预测分数。

比如输入是:

I love deep

最后一个位置的 logits 可以用来预测下一个 token:

next_token_logits = logits[:, -1, :]

这对应自回归生成里的:

\[ p(x_{t+1} \mid x_{\le t}) \]

8.11.3.2 generate:自回归生成的封装

虽然我们可以手动取 logits[:, -1, :],然后一步一步采样,但实际使用中通常直接调用:

inputs = tokenizer(
    'The last human on Earth heard a knock at the door and',
    return_tensors='pt',
)
output_ids = model.generate(**inputs, max_new_tokens=100)

text = tokenizer.decode(output_ids[0], skip_special_tokens=True)
print(text)
[transformers] Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.
The last human on Earth heard a knock at the door and the doorbell rang.

"Hello, my name is John. I'm a student at the University of California, Berkeley. I'm a student at the University of California, Berkeley. I'm a student at the University of California, Berkeley. I'm a student at the University of California, Berkeley. I'm a student at the University of California, Berkeley. I'm a student at the University of California, Berkeley. I'm a student at the University of California, Berkeley. I

generate() 会帮我们完成自回归生成过程。它内部大致做的是:

for step in range(max_new_tokens):
    outputs = model(input_ids)
    next_token = select_next_token(outputs.logits[:, -1, :])
    input_ids = torch.concat([input_ids, next_token], dim=-1)

这里的流程正好对应前面讲的自回归生成:

\[ x_1 \rightarrow x_2 \rightarrow x_3 \rightarrow \cdots \]

模型每次生成一个新 token,再把它接回输入,继续生成下一个 token。

上面的 generate() 默认采用贪心解码(greedy decoding),也就是每次选概率最高的 token。但它也支持其他采样策略,比如温度采样、top-k 采样、top-p 采样等。比如下面这个例子:

inputs = tokenizer(
    'The last human on Earth heard a knock at the door and',
    return_tensors='pt',
)
output_ids = model.generate(
    **inputs,
    max_new_tokens=100,
    do_sample=True,
    temperature=0.8,
    top_p=0.9,
)

text = tokenizer.decode(output_ids[0], skip_special_tokens=True)
print(text)
[transformers] Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.
The last human on Earth heard a knock at the door and called 911.

He said he saw the man in black with a black hoodie, a black hat and sunglasses. He said the man was wearing a white suit and pants.

He said he saw a man in a black T-shirt and jeans with a white shirt and jeans.

He said he saw the man in a black hat and a black hat.

The man told the dispatcher he had been shot. He said he saw a bullet in his left arm.

你看,调整了采样参数后,生成的文本就会更有多样性,起码不会当一个复读机。generate() 的参数非常多,我们后面会专门讲一讲它的使用技巧。

8.11.3.3 use_cache:KV Cache 的接口

前面我们讲过 KV cache。它的核心思想是:推理时缓存过去 token 的 key 和 value,避免每一步都重新计算完整前缀。在 Hugging Face Transformers 里,这通常和 use_cache=True 这个参数相关。

对于 decoder-only 或 decoder 部分,模型 forward 时可以返回 past_key_values,它包含了每一层的 key 和 value 缓存。一个简化调用如下:

outputs = model(
    **inputs,
    use_cache=True,
)

past_key_values = outputs.past_key_values

下一步推理时,可以把 past_key_values 传回模型:

next_token_logits = outputs.logits[:, -1, :]
next_input_ids = next_token_logits.argmax(dim=-1, keepdim=True)

next_outputs = model(
    input_ids=next_input_ids,
    past_key_values=past_key_values,
    use_cache=True,
)

这样模型就不需要重新计算所有历史 token 的 key/value,而是只处理新增 token,并复用缓存。

在实际调用 generate() 时,KV cache 通常由 generate() 内部管理,我们不一定需要手动维护 past_key_values。所以可以这样理解:

手写 decoding loop 时,我们可能会直接接触 past_key_values;调用 generate() 时,KV cache 通常被封装在内部。

这正好对应前面 8.9 节讲的内容。

8.11.3.4 一个完整的 Decoder-only 示例

下面是一个最小的 GPT 风格生成例子:

from transformers import AutoTokenizer, AutoModelForCausalLM

model_id = 'openai-community/gpt2'

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)
ipy.clear_output()

text = 'I love deep learning because'
inputs = tokenizer(text, return_tensors='pt')

with torch.inference_mode():
    output_ids = model.generate(
        **inputs,
        max_new_tokens=100,
        do_sample=True,
        temperature=0.8,
        top_p=0.9,
    )

output_text = tokenizer.decode(output_ids[0], skip_special_tokens=True)
print(output_text)
[transformers] Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation.
I love deep learning because it allows you to understand the world and then solve problems. Deep learning is an open science. It's a discipline where you can experiment and learn from it. In my opinion, this is the best way to teach deep learning.

The problem that I have with Deep Learning is that it's hard to tell the difference between a "good" deep learning method and a "bad" one. In reality, I know a lot of good Deep Learning methods, but I'm not sure that I

这段代码背后发生的是:

  1. Tokenizer 把文本变成 input_ids
  2. Decoder-only Transformer 对前缀做 causal self-attention;
  3. LM head 输出下一个 token 的 logits;
  4. generate() 根据采样策略选出下一个 token;
  5. 新 token 接回输入;
  6. 重复直到生成结束。

这就是 8.8 和 8.9 讲过的自回归生成和 KV cache 在实际 API 中的体现。

8.11.4 Encoder-Decoder 模型:输入输出序列转换

和 decoder-only 模型不同,encoder-decoder 模型不是单纯根据前文继续生成,而是先理解一个输入序列,再根据这个输入生成另一个输出序列。典型任务包括机器翻译、文本摘要和文本改写。这类模型通常通过 Seq2SeqLM 接口调用。

8.11.4.1 Seq2SeqLM:Encoder-Decoder 生成模型

如果我们要加载 T5、BART 这类 encoder-decoder 模型,通常使用:

from transformers import AutoTokenizer, AutoModelForSeq2SeqLM

model_id = 'google-t5/t5-small'

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForSeq2SeqLM.from_pretrained(model_id)
ipy.clear_output()

这类模型结构可以理解成:

\[ X \rightarrow \operatorname{Encoder} \rightarrow H \rightarrow \operatorname{Decoder} \rightarrow Y \]

例如 T5 的输入通常是 text-to-text 形式:

text = 'Translate English to German: I love deep learning.'
inputs = tokenizer(text, return_tensors='pt')

output_ids = model.generate(
    **inputs,
    max_new_tokens=50,
)

output = tokenizer.decode(output_ids[0], skip_special_tokens=True)
print(output)
Ich liebe das tiefe Lernen.

这里 generate() 仍然是自回归生成,但和 decoder-only 不同的是:

  • Encoder 会先处理完整输入;
  • Decoder 每一步生成时,会通过 cross-attention 读取 encoder 输出;
  • Decoder 内部仍然使用 masked self-attention,不能看到未来输出 token。

这对应前面讲过的 encoder-decoder:

\[ p(y_t \mid y_{<t}, X) = \operatorname{Decoder}(y_{<t}, \operatorname{Encoder}(X)) \]

8.11.4.2 一个完整的 Encoder-Decoder 示例

下面是一个 T5 风格的 text-to-text 示例:

from transformers import AutoTokenizer, AutoModelForSeq2SeqLM

model_id = 'google-t5/t5-small'

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForSeq2SeqLM.from_pretrained(model_id)
ipy.clear_output()

text = 'Translate English to German: I love deep learning.'
inputs = tokenizer(text, return_tensors='pt')

with torch.inference_mode():
    output_ids = model.generate(
        **inputs,
        max_new_tokens=50,
    )

output_text = tokenizer.decode(output_ids[0], skip_special_tokens=True)
print(output_text)
Ich liebe das tiefe Lernen.

这段代码背后发生的是:

  1. Tokenizer 处理输入文本;
  2. Encoder 双向编码完整输入;
  3. Decoder 从起始 token 开始自回归生成;
  4. Decoder self-attention 使用 causal mask;
  5. Decoder cross-attention 读取 encoder 输出;
  6. generate() 返回生成结果。

这正好对应 encoder-decoder 的结构:

\[ X \rightarrow \operatorname{Encoder} \rightarrow H \rightarrow \operatorname{Decoder} \rightarrow Y \]

8.11.5 模型内部信息:Attention 与 Hidden States

前面我们讲过,Transformer 内部有很多重要的结构信息,比如 attention weights、hidden states、KV cache 等等。这些信息对于理解模型行为、调试模型、可视化分析都非常有用。对于 Transformers 库,我们可以通过设置一些参数来取出这些内部信息。

8.11.5.1 output_attentions:取出 Attention Weights

如果我们要可视化 attention map,首先需要拿到模型内部的 attention weights。在 Hugging Face Transformers 中,可以在调用模型时设置 output_attentions=True

Warning

在较新的 Transformers 版本中,部分模型可能默认使用 SDPA 或 FlashAttention 等高效 attention 实现,这些实现有时不会返回 attention weights。如果需要可视化 attention,可以在加载模型时设置 attn_implementation='eager',但这通常会牺牲一部分性能。

from transformers import AutoTokenizer, AutoModelForSequenceClassification

model_id = 'google-bert/bert-base-uncased'

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForSequenceClassification.from_pretrained(
    model_id,
    num_labels=2,
    attn_implementation='eager',
)
ipy.clear_output()

text = 'I love deep learning.'
inputs = tokenizer(text, return_tensors='pt')

outputs = model(
    **inputs,
    output_attentions=True,
)

然后读取:

attentions = outputs.attentions
print('Number of layers:', len(attentions))
print('Attention shape per layer:', attentions[0].shape)
Number of layers: 12
Attention shape per layer: torch.Size([1, 12, 7, 7])

对于 encoder-only 或 decoder-only 模型,attentions 通常是一个 tuple,长度等于层数。每一层的 attention shape 通常类似:

(batch_size, num_heads, seq_len, seq_len)

也就是:

\[ A^{(l)} \in \mathbb{R}^{B \times H \times n \times n} \]

我们可以取某一层某个 head:

layer_idx = 0
head_idx = 0

attn = attentions[layer_idx][0, head_idx]
print(attn.shape)
torch.Size([7, 7])

这时 attn 的形状就是 (seq_len, seq_len),它表示这个 head 在这一层对输入序列的 attention 权重。这样,我们就可以拿去画 heatmap。

对于 encoder-decoder 模型,情况会多一些。模型可能返回:

encoder_attentions
decoder_attentions
cross_attentions

它们分别对应:

  • Encoder 内部的 self-attention;
  • Decoder 内部的 masked self-attention;
  • Decoder 到 encoder 的 cross-attention。

其中,cross-attention 最适合用来观察输入和输出之间的对齐关系。

8.11.5.2 hidden_states:取出每一层表示

除了 attention weights,有时我们还想看每一层的 hidden states。这时候,我们可以这样设置:

outputs = model(
    **inputs,
    output_hidden_states=True,
)

然后读取:

hidden_states = outputs.hidden_states
print('Number of layers:', len(hidden_states))
Number of layers: 13

通常它也是一个 tuple,包含 embedding 输出和每一层 Transformer block 的输出。例如,对于一个 12 层的模型,hidden_states 可能包含 13 个元素。其中第 0 个通常是 embedding 层输出,后面才是每一层 Transformer 的输出。

某一层的 hidden state 形状通常是:

(batch_size, seq_len, hidden_size)

这对应我们前面一直写的:

\[ H^{(l)} = [h_1^{(l)}, h_2^{(l)}, \dots, h_n^{(l)}] \]

如果我们想研究模型在不同层学到了什么,hidden_states 很有用。

8.11.5.3 一个 Attention Visualization 的接口示例

如果想取出 attention weights,可以这样写:

from transformers import AutoTokenizer, AutoModel

model_id = 'google-bert/bert-base-uncased'

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModel.from_pretrained(
    model_id,
    attn_implementation='eager',
)
ipy.clear_output()

text = 'The animal did not cross the street because it was tired.'
inputs = tokenizer(text, return_tensors='pt')

with torch.inference_mode():
    outputs = model(
        **inputs,
        output_attentions=True,
    )

attentions = outputs.attentions
tokens = tokenizer.convert_ids_to_tokens(inputs['input_ids'][0])

layer_idx = 0
head_idx = 0
attn = attentions[layer_idx][0, head_idx]

print('Attention weights shape:', attn.shape)
print('Tokens:', tokens)
Attention weights shape: torch.Size([14, 14])
Tokens: ['[CLS]', 'the', 'animal', 'did', 'not', 'cross', 'the', 'street', 'because', 'it', 'was', 'tired', '.', '[SEP]']

此时 attn 的形状是 (seq_len, seq_len),它就可以用来画 heatmap,观察这个 head 在这一层对输入 token 的 attention 权重分布。不过要注意:

这张图展示的是某一层某个 head 的 attention 权重,不是模型最终预测的完整解释。

在后面我们会讲这是为什么。

8.11.6 labels:训练时的损失怎么来

很多 For... 模型在 forward 时可以传入 labels=...。如果传入 labels,模型会自动计算 loss。

例如文本分类:

from transformers import AutoTokenizer, AutoModelForSequenceClassification

model_id = 'google-bert/bert-base-uncased'

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForSequenceClassification.from_pretrained(
    model_id,
    num_labels=2,
    attn_implementation='eager',
)
ipy.clear_output()

inputs = tokenizer(
    ['This movie is great!', 'This movie is terrible!'],
    return_tensors='pt',
    padding=True,
)
labels = torch.tensor([1, 0])
outputs = model(**inputs, labels=labels)

loss = outputs.loss
print('Loss:', loss.item())
Loss: 0.7770081162452698

这里的模型内部大致做的是:

\[ \text{logits} \rightarrow \text{loss function} \rightarrow \text{loss} \]

对于 sequence classification,常见的是交叉熵损失。对于 causal language modeling,也可以传入 labels:

from transformers import AutoTokenizer, AutoModelForCausalLM

model_id = 'openai-community/gpt2'

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)
ipy.clear_output()

inputs = tokenizer('I love deep learning.', return_tensors='pt')
outputs = model(
    **inputs,
    labels=inputs['input_ids'],
)

loss = outputs.loss
print('Loss:', loss.item())
[transformers] `loss_type=None` was set in the config but it is unrecognized. Using the default loss: `ForCausalLMLoss`.
Loss: 5.549721717834473

这时模型会计算 next-token prediction 的 loss。也就是说,用前面的 token 预测后面的 token。这个接口和前面讲 teacher forcing 的地方也能对应起来:

训练时完整目标序列已知,模型可以并行计算每个位置的预测,再和 labels 对齐计算 loss。

8.11.7 一张结构到 API 的对应表

可以把前面讲过的概念和 Hugging Face API 对应起来:

表 1:Transformer 结构和 Hugging Face API 的对应关系
前面讲的概念 Hugging Face 里常见接口
Tokenization AutoTokenizer.from_pretrained(...)
Token ids inputs['input_ids']
Padding mask inputs['attention_mask']
Encoder-only backbone AutoModel / AutoModelForSequenceClassification
Decoder-only LM AutoModelForCausalLM
Encoder-decoder LM AutoModelForSeq2SeqLM
Token 表示 outputs.last_hidden_state
分类分数 outputs.logits
每层 hidden states output_hidden_states=True / outputs.hidden_states
Attention weights output_attentions=True / outputs.attentions
Cross-attention weights outputs.cross_attentions
自回归生成 model.generate(...)
KV cache use_cache=True / outputs.past_key_values
训练 loss labels=... / outputs.loss

这张表的作用是把结构理解和代码调用连起来。以后你看到一个 Hugging Face 代码片段时,可以先问自己:

这个 API 对应的是 Transformer 的哪一部分?

这样就不容易把接口当成黑箱。

8.11.8 本章小结

这一节里,我们把 Transformer 的结构和 Hugging Face Transformers 的常用 API 对应了起来。

AutoTokenizer 负责把文本转成 input_idsattention_maskAutoModel 负责加载基础 Transformer 主体;AutoModelForSequenceClassificationAutoModelForTokenClassificationAutoModelForCausalLMAutoModelForSeq2SeqLM 则是在基础模型上加了不同任务头或生成接口。

对于 encoder-only 模型,最常见的输出是 last_hidden_state,它表示每个 token 的上下文表示。对于 decoder-only 和 encoder-decoder 生成模型,最核心的是 logitsgenerate():前者给出下一个 token 的预测分数,后者封装了自回归生成过程。

output_attentions=True 可以让模型返回 attention weights,用于可视化;output_hidden_states=True 可以返回每一层 hidden states,用于分析表示变化;use_cache=Truepast_key_values 则对应 KV Cache,用来加速自回归推理。

到这里,第 8 章就从 attention 的直觉、数学形式、Transformer 结构,一路走到了真实模型调用。理解这些接口背后的结构以后,Hugging Face Transformers 就不再只是一个能跑模型的库,而是可以帮助我们把理论、实现和真实预训练模型连接起来的工具。祝大家在使用 Transformers 的过程中,一切顺利!