那在这节课中,我们将通过大模型底层调用代码来一起深入的了解一下大模型是如何实现调用的。
前排提示,文末有大模型AGI-CSDN独家资料包哦!
那首先我们需要参考上一节课的配置方法来完成环境的配置,由于这里并不需要下载额外的库和模型,所以这里就不过去赘述了。
本节课我们将重点关注的是以下代码,只要大家把这段代码放到本地能跑就没有问题了,下面我们就来一步步的解析这部分代码具体的含义:
import torch from transformers import AutoTokenizer, AutoModelForCausalLM def main(): model_path = r"qwen" tokenizer = AutoTokenizer.from_pretrained(pretrained_model_name_or_path=model_path, use_fast=True,local_files_only=True) model = AutoModelForCausalLM.from_pretrained( pretrained_model_name_or_path=model_path, trust_remote_code=True, device_map="auto", dtype=torch.float16, ) user_prompt = "你是谁?" messages = [{"role": "user", "content": user_prompt}] # 关键:用 Qwen3 的 chat template 生成“模型真正想要的输入文本” text = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True, enable_thinking=False, # 先关闭 thinking,输出更直接 ) inputs = tokenizer([text], return_tensors="pt").to(model.device) generated = model.generate( inputs, max_new_tokens=60, do_sample=True, temperature=0.6, top_k=20, top_p=0.95, ) # 关键:只取新生成的部分(不把 prompt 原样打印出来) new_tokens = generated[0][inputs["input_ids"].shape[1]:] answer = tokenizer.decode(new_tokens, skip_special_tokens=True).strip() print("/n=== 模型回答 ===") print(answer) if __name__ == "__main__": main()
假如想要真正了解清楚这部分内容,有一定对大模型的背景知识是必不可少的,所以建议大家前往 B 站或者 Youtube 上查看 3Blue1Brown 上关于大模型、深度学习相关的视频,其所制作的视频通过动画的方式清晰的将这部分大模型的底层原理进行拆解,是我见过讲得最好最清楚的讲解视频了。
其中最推荐的视频是短短7分钟的大语言模型的简要解释:
https://www.bilibili.com/video/BV1xmA2eMEFF/?spm/_id/_from=333.337.search-card.all.click&vd/_source=99818a3df596c69ba400b78354d691da
另外深度学习部分的第五章和第六章也深入讲解了 Transformer 算法的具体内容,相信能够对大家有所帮助。
除此之外,在文中有任何不清楚的概念,也建议大家直接向大模型进行询问了解,从而不至于缺乏某些背景知识而看不懂。那我在讲解过程中也会尽量穿插上一些原理分析,那事不宜迟,我们马上开始吧!
这段代码主要分成了五个部分,包括:
- 加载资源
- 指定本地模型目录(权重、配置、tokenizer、chat template 等)
- 加载
tokenizer(负责:文本 ↔ token ↔ id) - 加载
model(负责:张量 → logits(下一个 token 概率))
- 构造输入
- 用户输入先整理成
messages(role/content) - 使用 chat template 把 messages 变成模型“真正吃的 prompt 文本”(包含 system/user/assistant 的特殊标记)
- 数字化(tokenize)
- 将文本 prompt 编码为 token id 序列
- 打包成 PyTorch 张量(
input_ids,attention_mask等)
- 生成(generate,自回归)
- 模型输出下一 token 的概率分布(logits → softmax)
- 按策略(贪心 / 采样 / beam 等)选出 token
- 追加到序列末尾,进入下一轮预测,直到停止条件触发
- 还原输出
- 截取“新增 token”(排除输入 prompt 的 token)
decode回可读文本
接下来我们将一步步对这些内容结合上节课下载的文件一起来进行讲解:

GPT plus 代充 只需 145model_path = r"qwen"
在代码最开始,我们设置的 model_path 用来指向本地模型目录。它的作用很直观:告诉 Transformers 不要去联网下载,而是直接从这个目录里读取模型所需的文件(如权重、配置、tokenizer 等)。
这里我们写的是 r"qwen",看起来不像常见的完整路径,其实它是一个相对路径。举个例子:如果你当前终端(或运行脚本时的工作目录)是 D:/微调与部署,那么程序最终会去 D:/微调与部署/qwen 下面查找模型相关文件。
当然,你也可以直接写成绝对路径,比如 r"D:/微调与部署/qwen",这样更直观、也更不容易出错;但缺点是迁移到另一台电脑时路径往往需要改动。相比之下,相对路径在项目目录结构固定的情况下会更方便复用。
另外,model_path 也可以填 Hugging Face 上的模型仓库名,比如 "Qwen/Qwen3-0.6B"。这种写法会在首次运行时自动下载模型并缓存到本地,之后再调用时通常会直接走缓存;而如果你直接填写本地路径,则会跳过下载步骤,完全使用你已准备好的本地模型文件。
使用方法
from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained(pretrained_model_name_or_path=model_path)
在确定了文件所在路径后,接下来我们就可以将模型和分词器进行加载了。这里我们使用的是 transformers 中最常规的 AutoTokenizer.from_pretrained() 方法进行加载。在这个方法中,我们只需要填入一个关键参数 pretrained_model_name_or_path ,这里就是写入前面的本地路径(model_path)或者 Huggingface 上的路径进行载入。这样我们就完成了 tokenizer 的加载了。
除了这个参数外,其实还有一些别的参数可以调整,包括:
use_fast:是否使用 Rust 实现的 Fast Tokenizer,默认是 True 进行使用的,此时就会使用 tokenizer.json 文件,假如是 False 的话才会使用 vocab + merges 文件进行实现。trust_remote_code:允许执行模型仓库中自定义的 tokenizer / model 代码,那对于国内模型而言,一般会选择进行开启。local_files_only:是否禁止联网下载。假如我们完全是在本地进行使用的话,建议开启该功能。
那一般情况下我们使用默认的就好了,系统会读取 tokenizer_config.json 文件读取一些必要的配置信息。
原理解析
那到底这个所谓的 tokenizer 是什么呢?
简单来说,tokenizer 就是把人类的文本语言,转换成模型能够处理的数字序列的工具(以及把数字序列再还原回文本)。因为计算机本质上只能处理数字,模型更不可能“直接读懂文字”,所以我们必须先把文本变成一串整数,再交给模型去计算。
更准确地说,tokenizer 做的事情通常包含两步:
- 分词(tokenization):
所谓分词,其实本质上是把输入文本切分成若干个 token(文本片段)。那在实际的分词中,主要分为四步:

- Normalization(规范化):
这一步的主要目的是把“表面不同但语义相同”的写法统一,否则词表会被同义写法撑爆,模型也更难学。常见规范化包括:
- 大小写统一(Hello → hello)
- Unicode 归一化(全角/半角、奇怪的空格符等)
- 去掉/替换一些不可见字符
- Pre-tokenization(预切分):
第二步把一句话先按“明显的边界”切成粗粒度片段,比如把空格边界、标点、数字串/特殊符号先分开,避免后面的子词算法把不该粘在一起的东西粘住。
- Model(核心分词模型):
第三步才是真正的“子词级切分”。其主要解决的问题是给定一个字符串片段,把它分解成若干个 token,使得这些 token 都在 vocab 里,并且分解方式尽量“合理”(通常是 token 数尽量少 / 概率尽量高)。
这里会使用到一些分词模型来实现,如 BPE/WordPiece/Unigram 等(qwen3-0.6B 模型使用的是 BPE 模型),根据模型里词表(Vocabulary)里的内容对输入的内容进行进一步的划分。这里的词表可以想象为是中英文词典,其作用是建立 token 与整数 ID 之间的一一映射关系,比如在我们下载的文件中,我们可以在
tokenizer.json文件中搜索vocab找到相关的内容:GPT plus 代充 只需 145
"vocab": { "!": 0, "/"": 1, "#": 2, "$": 3, "%": 4, "&": 5, "'": 6, "(": 7, ")": 8, "*": 9, "+": 10, ",": 11, "-": 12, }假如能够在词表中直接找到就可以使用,比如例子中的
[hello, how, are, u, ?]。但是对于tday这种没有能够在词表中找到的内容,那就会根据规则进行找到更合适的切分手段。比如在例子中我们就把其切成了td+ay。我们也可以在tokenizer.json文件中搜索merges找到相关合并的规则内容(越靠前的规则,优先级越高):# 这里的 Ġ 指的是空格的意思 "merges": [ ["Ġ", "Ġ"], ["ĠĠ", "ĠĠ"], ["i", "n"], ["Ġ", "t"], ["ĠĠĠĠ", "ĠĠĠĠ"] ]那其实这个
tday变td+ay的切分方式并不是绝对的,也有可能是和前面的片段进行合并形成一个更大的 token,比如变成utd+ay,因此具体的划分方式主要看合并的规则以及对应的优先级。需要强调的是,这些合并规则并非人工设计,而是在预训练阶段由模型根据语料中 token 共现频率自动学习得到的结果。所以可以把这些规则理解为是模型对语言中高频子结构的一种压缩与抽象方式。那在下载的文件中,我们不仅看到了
tokenizer.json文件,其实还有看到merges.txt文件和vocab.json文件。其实这两个文件和tokenizer.json文件里的词表和合并规则是一样的,只不过是为了兼容“slow tokenizer” 和旧生态。我们可以看到前面
AutoTokenizer.from_pretrained()里有一个参数use_fast,假如我将其设置为 False 的话,那么就会使用merges.txt文件和vocab.json文件进行分词。假如开启了的话就会使用tokenizer.json文件来进行分词。另外在很多老环境中,可能仅支持旧版本,所以这也算是提供了一个兜底兼容的分词方法。另外,我们会发现,这里切分完后的 token 并不都是一个个单词,之所以不直接使用字或词,主要原因是:
- 词表规模可控:如果按“所有单词”建词表,会爆炸;而 token 用“子词/片段”可以覆盖更多新词(比如没见过的专有名词也能拆开表示)。
- 泛化更强:遇到新词、新名字、新拼写,模型可以用已有 token 组合出来,而不是彻底“没法表示”。
- Postprocessor(后处理):
在将输入的内容拆分完成以后,最后一步就是要补上一些特殊的标记,比如在例子中的
CLS和SEP。这些特殊 token 的作用是告诉模型“结构信息”。我们在tokenizer.json文件中的"added_tokens"部分也能够找到,比如:GPT plus 代充 只需 145
"added_tokens": [ { "id": , "content": "<|endoftext|>", "special": true }, { "id": , "content": "<|im_start|>", "special": true }, { "id": , "content": "<|im_end|>", "special": true } ]或者我们可以在
tokenizer_config.json文件看到更多关于特殊 token 的行为配置信息:"added_tokens_decoder": { "": { "content": "<|endoftext|>", "lstrip": false, "normalized": false, "rstrip": false, "single_word": false, "special": true }, "": { "content": "<|im_start|>", "lstrip": false, "normalized": false, "rstrip": false, "single_word": false, "special": true }, "": { "content": "<|im_end|>", "lstrip": false, "normalized": false, "rstrip": false, "single_word": false, "special": true } }这些特殊 token 并不对应自然语言中的具体词语,而是用于显式标记文本结构与对话边界。
例如,在对话场景中:
通过这些特殊符号,模型可以在生成和理解文本时,明确区分不同消息、不同轮次以及文本边界,从而保证对话结构的正确性。
假如我们要使用 qwen3-0.6B 的分词器完成分词操作,方法也非常简单,只需要输入以下代码:
GPT plus 代充 只需 145
from transformers import AutoTokenizer model_path = r"D:/微调与部署/qwen" tok = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) text = "Hello how are U today?" tokens = tok.tokenize(text) print(tokens)此时就会返回分词后的结果:
['Hello', 'Ġhow', 'Ġare', 'ĠU', 'Ġtoday', '?']具体 encode 和 decode 时是怎么做的需要查询
tokenizer_config.json文件。该文件不负责真正的 BPE 切分规则,但它会决定 Tokenizer 在 encode/decode 时到底怎么做、哪些符号算特殊 token、以及 apply/_chat/_template 怎么拼 prompt。
<|im_start|>用于指示一段消息或语句的开始;<|im_end|>用于标记该段内容的结束;<|endoftext|>通常用于标识整体文本的终止位置。
- 映射(encoding):
在完成 token 的准备后,映射就是一个非常简单的事情,我们只需要在词表中找到对应的 toekn 然后将其转变成数字即可。
比如经过 qwen3-0.6B 模型的分词后,我们再为其进行映射:
GPT plus 代充 只需 145
ids = tok.encode(text) print(ids)此时返回的结果为:
[9707, 1246, 525, 547, 3351, 30]当然有 encode 就肯定有反向映射的 decode,假如此时我们把 ids 反向映射的话:
GPT plus 代充 只需 145
recon = tok.decode(ids) print(repr(recon))此时返回的内容就是原始的文本内容了:
'Hello how are U today?'
使用方法
GPT plus 代充 只需 145from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained( pretrained_model_name_or_path=model_path, trust_remote_code=True, device_map="auto", dtype=torch.float16, )
那在加载完 tokenizer 后,本质上就是获取了一个转换器,能够将人类发出的语言转换为机器能够读懂的语言,同时也能够将机器返回的语言转换为人类可读的内容。
在这一步的话我们其实就是将整个机器给构建起来,这里所使用的方法就是 transformers 中的 AutoModelForCausalLM.from_pretrained() 进行实现。
这里所谓的 CausalLM 其实表示的就是所有像 ChatGPT 一样的,基于 Transformer 架构的大模型。其是 Causal Language Model(因果语言模型) 的缩写。
所谓“因果”,指的是语言建模时遵循“从左到右”的时间因果顺序:
- 在生成第 t 个 token 时,只能看见前面
1..t-1的 token - 不允许“偷看未来”(这和 BERT 那种双向 Masked LM 不同)
因此,CausalLM 的核心任务非常统一:
给定一段上下文,预测下一个 token 的概率分布。
也正因为如此,很多人会把基于 Transformer 算法的大模型形象地称作“超级提词器”,因为它每一步都在做 next-token prediction,只是因为训练数据足够大、结构足够强、对齐足够好,所以“预测出来的下一步”在我们看来像是在回答问题、推理、写作、写代码。

那这个方法里也是只有一个必须输入的参数 pretrained_model_name_or_path,这里写入的也是模型的路径或者 Huggingface 上的名称来进行加载。这里我们还是使用的是 model/_path 来进行载入我们下载好的 qwen3-0.6B 模型。
那除了这个必要参数以外,我们还可以设置一下其他参数,包括:
dtype:这个参数表示的是模型的精度,通常包括float32,float16,bfloat16以及自动选择的'auto’等。那精度越大,所需要的显存就会越大,当然对应的模型回复的效果就可能会越好,后续在量化部分会更深入的进行讲解。device_map:这个参数主要是用于决定模型放在哪里进行推理,主要方式有两种,一种是使用 GPU 来进行推理,此时写入的参数是“cuda”。假如没有显卡的电脑也可以使用 CPU 来进行推理,此时写入的参数就是“cpu”,当然我们还可以传入“auto”来让系统自动判定使用 GPU 还是 CPU 进行推理。GPU 推理的速度会比 CPU 快非常多,所以一般我们都会选择使用 GPU 进行模型的推理。trust_remote_code:这个和 tokenizer 的其实是一样的,主要是决定是否信任自定义代码,一般情况下都要进行开启。
这三个就是主要会使用到的参数,其他的话会在 config.json 里自动进行设置,我们只需要按照默认的就好。当然随着 transformers 库的更新也会有一些参数名称有问题,比如现在用 dtype 而不是 torch_dtype 了,所以假如使用默认参数的话可能会引发一些 warning,但是这个不影响大家进行使用。
原理解析
在载入后其实内部主要做了两件事情,第一件事就是读取 config.json 文件,构建模型的结构蓝图(architecture blueprint)。如果没有它,你甚至无法实例化网络,因为你不知道:
- 有多少层(
num_hidden_layers) - 每层多宽(
hidden_size) - 注意力头数(
num_attention_heads) - MLP 隐层维度(
intermediate_size) - RoPE 相关参数(
rope_theta等) - 词表大小(
vocab_size) - 以及是否使用 GQA(
num_key_value_heads)、是否共享词嵌入(tie_word_embeddings)等等
{ "architectures": ["Qwen3ForCausalLM"], "hidden_size": 1024, "num_hidden_layers": 28, "num_attention_heads": 16, "num_key_value_heads": 8, "intermediate_size": 3072, "vocab_size": , "max_position_embeddings": 40960, "rope_theta": , "tie_word_embeddings": true, ... }
这里我们就相当于把整个模型的架构搭建起来了,比如最经典的 Transformer 算法架构如下所示:

然后 AutoModelForCausalLM 会根据配置里的字段(比如 architectures / model_type)选择对应的模型类,例如 Qwen3ForCausalLM,并创建一个“空壳模型”(参数尚未填充或是随机初始化)。
那前面的 trust_remote_code=True 参数其意义是允许 Transformers 加载模型作者提供的自定义实现(比如 Qwen 的特殊结构、rope、attention 实现等),否则可能只能走通用实现,甚至无法正确加载。
接下来会读取权重文件(可能是 model.safetensors,也可能是多个分片 model-00001-of-000xx.safetensors),并把里面的每个张量按名字对齐写入模型参数中。此时内部的参数就不再是空或者是随机值,而是完整的经过模型训练团队训练好的参数值了。
假如我们希望看到 qwen3-0.6B 的完整模型结构信息,我们可以前往其 huggingface 的主页上(Qwen/Qwen3-0.6B)找到 model.safetensors 文件并点击按钮,然后就会展开看到该模型的架构信息。

那这里我们就可以看出,对于模型而言最关键的就是两个文件 config.json 和 model.safetensors。config.json 决定形状(shape),model.safetensors 决定数值(values),两者必须匹配,否则会出现 shape 对不上、缺 key、多 key 等错误。
模型训练
那这里我们再向外拓展一些内容,也是给后面讲模型微调的时候一些铺垫:模型到底是如何训练出来的?为什么随机参数的时候说出来的内容是乱七八糟的,而在训练过后就能够正常的和人类进行对话呢?
要回答这个问题,我们得先抓住一个核心事实:CausalLM 的学习目标从头到尾几乎没变——永远都是“预测下一个 token”。在此基础上,所谓的“训练”,就是不断让模型在大量文本上重复做这件事,并用数学方法逼着它“越猜越准”。
如果我们把模型参数随机初始化,那它对任何输入都没有“语言经验”,预测下一个 token 纯靠随机偏好——所以输出自然是乱的、散的、没有语法和语义的。
为了能够让模型变得条理清晰,一般而言开发团队要完成至少三轮的训练,包括:
- 预训练(Pretraining)
- 指令微调(Supervised Fine-Tuning)
- 基于人类反馈的强化学习(RLHF)

- 预训练:
在预训练里,其实做的事情非常朴素,因为做的事情既不需要人类标注,也不需要特别复杂的训练技巧,只需要给模型喂海量文本,让它在真实语言分布里反复做“预测下一个 token”,用梯度下降把“猜错”变少,把“猜对”变多。这样就结束了。
所在可以看出,在预训练阶段里,最关键的重点在于喂海量的文本给模型进行训练,包括书本、代码、文章、维基百科以及各种网页内容。然后就开始一句句的给大模型预测下一个 token 的可能情况,接着根据标准答案使用梯度下降的方法对模型进行更新。在这个训练的过程中,由于文本量非常巨大且全部参数都要进行调整,这就需要花费时间和算力资源去完成,因此预训练阶段对于个人开发者而言是很难介入的。

在经过大量的文本训练后,模型会逐渐演变出一些能力,包括:
可以看出预训练让模型从“不会说话的随机函数”,变成“会续写的大型语言统计器”。但是此时由于没有刻意让他了解人类说话的习惯,所以这是虽然它会写,但不一定会听话。它更擅长“把文本续得像”,而不是“按你的意图做”。
比如在这个阶段我们问“中国的首都是哪里?”,模型的回答不会是我们想象的“北京”,更有可能的答案是“美国的首都在哪里?英国的首都在哪里?“这样的内容。所以可以看出,在对话场景里,用户输入往往不是要你“续写”,而是要:
这些能力的关键不在于目标函数变了,而在于训练数据从“自然文本”变成“指令—回答”这种交互结构,并且引入“偏好”与“约束”的信号。因此为了能够让有一定语言能力的预训练模型能学会像正常人类一样对话,我们需要使用指令微调,即添加人类标注好的数据给到大模型让其进行进一步的训练。
假如我们想直观地看到预训练模型的效果,可以下载 qwen3-0.6B-base 模型来尝试,我们只需要执行以下代码便可进行下载:
GPT plus 代充 只需 145
import os from huggingface_hub import snapshot_download os.environ["HF_ENDPOINT"] = "https://hf-mirror.com" snapshot_download( repo_id="Qwen/Qwen3-0.6B-base", local_dir="qwen-base", local_dir_use_symlinks=False, # Windows 推荐 False resume_download=True ) print("Download finished: qwen")假如网络不支持的可以前往 ModelScope 下载。
下载完以后可以通过前面的 pipeline 方法进行快速调用:
from transformers import pipeline pipe = pipeline( "text-generation", model=r"qwen-base" ) messages = [{"role": "user", "content": "你是谁?"}] result = pipe(messages) print(result)当我们输入”你是谁“的时候,其并不会说自己是 qwen 或者说一个有帮助的助手,而是一直在重复”你是谁“:
GPT plus 代充 只需 145
[{'generated_text': [{'role': 'user', 'content': '你是谁?'}, {'role': 'assistant', 'content': '你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是谁?.MaximizeBox/n你是'}]}]所以看出此时的模型其实并没有真正学习到对话,只知道成语接龙而已。
- 解释、总结、翻译、推理、写代码
- 按格式输出(表格/要点/JSON)
- 遵守身份设定与安全边界
- 遇到不确定要说不确定,而不是硬编
- 词与词的共现规律:哪些词常一起出现,哪些搭配更自然。
- 句法结构:主谓宾、从句、转折、指代等模式。
- 语义与世界知识的统计近似:不是“背百科”,而是在大量文本里学到“什么更可能是真的/常见的说法”。
- 跨段落的连贯性:写作结构、因果关系、论证套路。
- 多语言/代码等混合分布(如果数据里包含):会逐步形成对应风格的预测能力。
- 指令微调:
在指令微调(SFT)阶段,模型最核心的目标之一,就是学会“对话结构”本身:当输入看起来像人类对话时,它要能稳定地产出“像助理回复”的文本。
但这里会遇到一个非常现实的问题:对模型而言,输入本质上只是一串连续的 token 序列。如果不额外提供结构信号,模型并不知道哪些 token 属于“用户的问题”,哪些 token 属于“助理的回答”,更不清楚应该从哪里开始生成、以及什么时候该停止。这就是所谓的“角色边界不清”。
为了解决这个问题,我们首先需要为对话中的不同内容显式标注“角色”(role),让模型在输入层面就能分辨每一段话的身份与作用。常见的角色包括:
比如可能的对话对长下面这样:
{"messages": [{"role": "user", "content": "你是谁?"}, {"role": "assistant", "content": "我是剑锋大佬,一个专注于大模型微调与部署的AI老师。"}]}在完成角色划分后,下一步就是将这些结构化的消息(messages)进一步编码为模型可直接接收的输入文本,这就需要引入 Chat Template(对话模板):通过一组特殊标记(special tokens)将每一轮对话“包装”起来,让模型能够在输入层面清晰地识别轮次边界、角色边界以及生成起点,从而真正“读懂对话结构”。
以 Qwen3 为例,这套对话模板会被保存在
tokenizer_config.json中,并采用 Jinja2 模板语法进行编写。也就是说,当我们调用tokenizer.apply_chat_template(...)时,本质上就是用这份 Jinja2 模板把system / user / assistant / tool等消息按规则拼接成一段最终的模型输入文本。GPT plus 代充 只需 145
{{- '<|im_start|>system/n' }} {{- messages[0].content + '/n/n' }} ...在通过 Chat Template 将多轮对话转化为模型可直接接收的输入文本之后,这些“模板化的对话样本”就可以用于 SFT 训练了。此时训练的核心流程可以理解为:把用户的问题(以及系统提示词)作为条件输入,让模型去生成对应的助理回复。从训练目标上看,它依然是在做“预测下一个 token”的任务,但与预训练不同的是——数据分布发生了变化:模型看到的不是任意自然文本,而是大量“
user + system → assistant”形式的高质量示范对话。因此,SFT 本质上是在让模型学习这样一种条件概率分布:
给定一段带角色与轮次边界的对话上下文,下一段最合理的输出应当是“助理回复”。
在训练过程中,我们通常会把完整的对话拼接成一条序列,但在计算 loss 时会更关注(甚至只关注)assistant 回复部分,从而避免模型“学习复述用户问题”,而是把学习重点放在“如何生成合适的回答”上。这样训练下来,模型逐渐就能形成稳定的对话行为:知道何时该解释、何时该分点、何时该给步骤,也更不容易把用户提问当成一段文章开头去自由续写。
当我们在推理(inference)阶段输入新问题时,流程其实是训练流程的“镜像”。我们同样会先把
system/user/assistant等消息加载进 同一套提示词模板,再将模板渲染后的文本送入模型。由于模型在 SFT 中反复见过这种结构化输入,它就会非常清楚:也正因如此,SFT 能够显著提升模型的“可对话性”和“指令遵循能力”。
从数据规模来看,SFT 对数据量的需求远小于预训练。预训练往往需要海量语料来学习语言规律与知识分布;而 SFT 更像是“在已有语言能力的基础上做行为校准”,通常几千到几万条高质量指令数据就能带来明显改善(当然,效果高度依赖数据质量、覆盖面与标注一致性)。与此同时,近年来也出现了许多高效参数微调方法(例如 LoRA / QLoRA 等),使得我们即便在显存较有限的条件下,也能以较低成本训练出“表现相当不错”的指令模型。

后续的教学中,我们就会基于这种“高效 SFT 微调”的思路,带大家从数据组织、模板构造、训练策略到效果评估,逐步完成一次可复现、可迁移的小模型指令微调实践。
- 哪些内容是用户的提问(
user) - 当前轮次应该由谁说话(
assistant) - 应当从哪里开始生成(generation prompt / assistant 起始标记)
- 应该以怎样的风格与格式输出(由
system与示范数据共同塑形) user:人类用户输入的问题或指令,通常用于提出任务、需求与约束条件。assistant:模型在历史轮次中的回答,主要用于补充上下文,让模型“记得自己刚刚说过什么”,从而保持多轮对话的一致性。system:系统级指令或行为约束,通常位于对话最开头,用于设定模型的身份、人设、回答风格以及安全边界。tool:工具调用相关的上下文(函数返回/工具输出等),常见于 Agent 智能体场景;在普通对话任务中不一定会出现。
- 基于人类反馈的强化学习
那在经过了 SFT 以后,模型基本已经学会了如何在 Chat Template 约束下进行对话:看到
user的问题,就在assistant位置输出一段看起来很像人类助理的回复。但此时仍然存在一个关键缺口:它更像是在“模仿说话”,而不是在“学会什么叫好回答”。
换句话说,SFT 主要教会模型两件事:
但它并不天然知道:
原因也很简单,SFT 的监督信号来自“示范答案”。它学习的是“像示范那样回答”,而不是“在多个候选里学会选择更好的那个”。一旦遇到数据覆盖不到的边界场景,模型就可能出现“答得像,但不一定对/不一定合适”的情况——这就需要 RLHF 来补上。
RLHF(Reinforcement Learning from Human Feedback)要解决的问题可以概括为一句话:
让模型不仅能回答,还能更倾向于生成“人类更喜欢、更安全、更有帮助”的回答。
它的核心思路不是让人类一直手写标准答案(成本太高),而是把人类的监督方式换成更可规模化的一种形式:比较与偏好。具体的操作思路可以分成三步:
1)第一步:收集“偏好对比数据”(Comparison Data)
在 RLHF 中,人类标注者通常不会直接写“唯一正确答案”,而是做一件更容易、更一致的事:
这里选择的“更好”往往包含多个维度:
这一步的产物,就是图里所谓的 Comparison Data:大量“同题多答 + 人类偏好选择”的样本。
2)第二步:训练“奖励模型”(Reward Model)
有了偏好对比数据后,下一步不是直接训练最终模型,而是先基于这部分数据训练一个“裁判”——奖励模型(Reward Model, RM)。
奖励模型做的事情非常直观:
输入(prompt, response)→ 输出一个分数 r
分数越高,表示这条回答越符合人类偏好(更好、更安全、更有帮助)。
训练时,它会被逼着满足一个约束:
人类选择的“更好回答”得分应当高于“更差回答”。
所以如果说 SFT 是教模型“怎么答题”,那奖励模型就像是教会了一位“阅卷老师”:它能给每一份答案打分,而且打分标准尽量贴近人类的偏好。
3)第三步:用强化学习优化 SFT 模型(得到 Final Model)
有了“裁判”(奖励模型)之后,我们就可以让模型进入强化学习阶段:
但工程上这里还有一个极关键的稳定性约束:
不能为了拿高分就把模型改得面目全非,否则会发散、会乱、甚至出现“投机取巧”的行为。
因此 RLHF 通常会加一个“保持与参考模型接近”的约束(常见形式是 KL 惩罚)。也就是假如模型为了得更高分写的答案和原本模型输出的答案差别太大的时候,就得给模型一些惩罚使其得分更低,从而使其在“尽量不偏离 SFT 的基本对话能力”的前提下,把输出往更高质量、更安全的方向推。
这种方法也常常被人们称为是 PPO(Proximal Policy Optimization)算法。PPO 最初并不是专门为大语言模型提出的,而是一个通用的强化学习算法。它的核心目标,是在提升策略表现的同时,限制每次更新不要走得太猛,从而保证训练更稳定。PPO 论文中提出了一个“裁剪后的 surrogate objective(代理目标)”,用来避免新策略相对旧策略发生过大的跃迁。所以从直觉上说,PPO 很像是一边踩油门(追求更高奖励),一边踩着刹车(不让模型偏离太远)。
- 有用性(helpfulness):是否真正解决了问题
- 真实性/可靠性(truthfulness):是否胡编、是否夸大
- 清晰性(clarity):结构是否清楚、重点是否明确
- 安全性(safety):是否包含不当、危险、违规内容
- 礼貌与语气(harmlessness/politeness):是否攻击、歧视、冒犯
- 对同一个 prompt(同一个问题)
- 给出多个候选回答(通常由模型生成)
- 让人类选择:哪个更好?
- 哪种回答更有帮助、更清晰、更可靠
- 遇到不确定时该不该承认不确定
- 什么内容属于高风险、应当拒绝或谨慎回答
- 什么情况下应该先反问澄清而不是直接输出
- 以及更现实的一点:人类更偏好怎样的语气、长度与结构
- 从 SFT 模型出发(这是起点,非常重要)
- 给定 prompt,让模型生成回答
- 用奖励模型给这个回答打分
- 更新模型参数,让它以后更倾向生成“高分回答”
- 对话结构与角色边界:知道什么时候轮到自己回答、回答要长什么样
- 回答的基本模式:分点、解释、给步骤、给代码等常见“助理式输出”
- RLHF 优化
不过,PPO 用在大语言模型对齐时也有明显缺点:
也正因为这些问题,后来研究者开始思考:
能不能不显式训练奖励模型,也不真的跑一整套强化学习,而是直接利用“偏好数据”来优化模型?
这就引出了 DPO(Direct Preference Optimization),其核心思想是既然我们已经有了“哪个回答更好、哪个回答更差”的偏好数据,那是否可以直接让模型提高“好回答”的概率、降低“差回答”的概率,而不必再显式训练一个奖励模型,并在其上跑 PPO?
传统 RLHF 通常需要两步:
而 DPO 则把这个过程进行了“合并”与“重参数化”,把原来需要奖励模型和 RL 的问题,直接转化为一个基于偏好对的优化目标。它不再要求模型在训练中不断采样、打分、更新,而是直接使用如下形式的数据:
训练目标很直观:
让模型在同一个 prompt 下,对 chosen 的偏好更高、对 rejected 的偏好更低,同时又不要偏离参考模型太远。
所以对于 DPO 技术而言,它不需要显式训练奖励模型,也不需要在训练过程中真的跑强化学习采样内环,因此整体训练更加简单、轻量,也更接近我们熟悉的监督微调范式。论文实验也表明,DPO 在一些任务上能够达到与 PPO 类 RLHF 相当甚至更好的效果,同时实现和训练都更简单。Hugging Face 的 TRL 文档也明确把 DPO 描述为比 PPO 更简单的偏好优化流程。
在 PPO 和 DPO 之后,研究界和工业界还提出了很多偏好优化与后训练算法变体。它们大多仍然围绕同一个核心目标展开:
如何让模型更符合人类偏好,同时让训练更稳定、成本更低、效果更好。
例如,围绕 DPO 就出现了不少改进版本,去处理:
而在更广泛的后训练框架里,也陆续出现了像 GRPO 这类方法,用于进一步改进策略优化过程。Hugging Face 当前的 TRL 文档中,就已经把 SFT、Reward Modeling、PPO、DPO、GRPO 等并列作为常见的大模型后训练方法。后续课程中,我们也会给大家演示如何基于自己造的偏好数据集结合 PPO 算法进行模型的训练。
- 不同偏好数据质量差异;
- 超参数 β/betaβ 的敏感性;
- “只会压低 rejected,不一定真正学到 preferred” 这类问题。
- prompt
- chosen response(人类更喜欢的回答)
- rejected response(人类不喜欢的回答)
- 流程复杂:需要 SFT 模型、奖励模型、参考模型等多个组件;
- 训练成本高:训练时要不断让模型采样生成回答;
- 实现细节多:超参数、KL 系数、奖励缩放等都比较敏感;
- 训练不够“像监督学习”:调起来往往比 SFT 麻烦很多。
- 先训练一个奖励模型;
- 再用强化学习去优化语言模型。
user_prompt = "用一句话解释什么是注意力。" messages = [{"role": "user", "content": user_prompt}]
这个 user_prompt 主要是设置我们传给模型的问题 —— “用一句话解释什么是注意力”。而这个 messages 则是将提问的问题放入到格式化 JSON 中。这个 JSON 主要是分成两个部分,一个是传入角色信息(role),另一个是传入对应的内容(content)。
前面在讲解 SFT 微调训练的时候提到过,一般角色信息有四个,包括 system, user, assistant, tool。那这里我们传入的就是最基础的 user,当然我们也可以给其加上系统提示词以及前面的对话历史:
GPT plus 代充 只需 145user_prompt = "用一句话解释什么是注意力。" messages = [ # ① 系统提示词(只放一次,通常放最前面) { "role": "system", "content": "你是一名严谨、善于用通俗语言讲解人工智能概念的大学教师。" }, # ② 历史对话:用户上一轮提问 { "role": "user", "content": "能先简单说一下什么是神经网络吗?" }, # ③ 历史对话:模型上一轮回答 { "role": "assistant", "content": "神经网络是一种通过多层神经元模拟人脑信息处理方式的机器学习模型。" }, # ④ 当前这一轮:用户的新问题 { "role": "user", "content": user_prompt } ]
那此时模型返回的内容就会根据前面传入的信息发生改变。那假如我们要进行提示词工程的设置其实就是修改这部分内容进行实现的。但这里的 messages 内容并不会直接传入模型中,其还是需要前面 SFT 训练时提到的对话模版(chat template)转换后才能够使用,所以这里 messages 的主要目的是让使用者输入的时候更统一更方便,并不是为了让模型进行理解的。
text = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True, enable_thinking=False, )
那在准备好了对话的文本内容后,下一步就是要讲这部分文本内容转化为在 SFT 微调时适配的对话模版,这样模型才能够知道:
“看到
<|im_start|>user之后,接下来通常是人类问题;看到
<|im_start|>assistant之后,接下来通常是我要生成的回答。”
这里必要传入的参数就是前面设置好的 messages ,除此之外,还有一些其他参数可以进行设置,包括:
tokenize:是否将转换后的内容转为 token 的形式。假如选择的是 False(当前的情况),那么返回的内容将会是字符串。反之是 True 的话则会返回 token ids。但是分开的好处在于能够更可控,也更好让大家理解。假如图方便的话,比如在官方的指引中就直接使用 True 并获取结果了。add_generation_prompt:这个决定了是否自动帮你加上“assistant 开始说话”的那一截文本协议(<|im_start|>assistant)。假如关掉的话这段就不会进行显示了,这样可能会导致模型的输出出现问题,因此一般情况下是需要打开的。enable_thinking:是否开启思考模式。在 deepseek-r1 风靡全国后,越来越多的模型有了所谓的思考模式。从本质上来说,思考模式其实就是在输出前先让模型生成一段内容,然后再去生成正式的内容。所以为了把思考的内容和正式内容区分开来,那就通过了一对标签来将思考的内容进行了划分。开启了思考模式的话就会在这个标签之间生成内容,关闭的话这个标签还会存在但里面就没有内容,并且把空的标签作为初始 prompt 放入。…
所以在经过拼接后,原本的:
GPT plus 代充 只需 145messages = [{"role": "user", "content": "用一句话解释什么是注意力。"}]
会变成:
<|im_start|>user 用一句话解释什么是注意力。<|im_end|> <|im_start|>assistant
这里之所以要把 <|im_start|>assistant 放在提前最开始其实是告诉大模型接下来要进行回复了。然后假如我们关闭了推理模式就会直接把
标签放在起始输入的内容中,那么模型就默认已经推理完了就会直接输出答案。
而假如开启了的话那么标签就不会存在,那么由于模型在 SFT 训练的过程中都是需要先思考再输出正确答案的,所以其会在正式输出的时候会先思考再输出最终结果。
假如是前面加上系统提示词和聊天记录的话,那将会转换成对应的格式(此时的思考模式是开启的状态):
GPT plus 代充 只需 145<|im_start|>system 你是一名严谨、善于用通俗语言讲解人工智能概念的大学教师。<|im_end|> <|im_start|>user 能先简单说一下什么是神经网络吗?<|im_end|> <|im_start|>assistant 神经网络是一种通过多层神经元模拟人脑信息处理方式的机器学习模型。<|im_end|> <|im_start|>user 用一句话解释什么是注意力。<|im_end|> <|im_start|>assistant
inputs = tokenizer([text], return_tensors="pt").to(model.device)
但假如我们希望更底层的控制传出的格式和使用的方式,那就可以使用上面这段代码。这里就是根据 tokenizer.json 文件将输入的字符串([text])转为 pytorch 格式的张量(return_tensors="pt")。同时将输入张量移动到模型所在设备(GPU/CPU),否则会报 device mismatch。
假如此时打印一下这个 inputs,可以看到以下内容:
GPT plus 代充 只需 145{'input_ids': tensor([[, 872, 198, 11622, , , , , 1773, , 198, , 77091, 198, , 271, , 271]], device='cuda:0'), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], device='cuda:0')}
总的来说,这就是一个字典的形式,里面有两个键:
input_ids是 tokenizer 把字符串映射成的 token 编号序列,比如第一个 就是<|im_start|>。attention_mask是模式“该不该看”的指示器,其告诉模型哪些 token 是“真实输入”,哪些是 padding。这里所谓的 padding 就是为了满足长度要求添加的无意义的内容,假如是 1 的话这个 token 要参与注意力计算,假如是 0 的话就可以忽略。那这里都是 1 就代表这句话不需要进行 padding 。
最后两个的 device 都是 cuda:0,这意味着我使用的是 GPU 来进行 tokenize 转换的,并且用的是第一张的显卡。从这一刻开始,语言被彻底映射为数值,后续全部是向量与矩阵运算。
那其实有个更简单的转换方式,那就是直接在前面 apply/_chat/_template 的方法里将参数 toeknize 改成 True :
text = tokenizer.apply_chat_template( messages, tokenize=True, add_generation_prompt=True, enable_thinking=True, )
此时就会返回转换后的结果:
GPT plus 代充 只需 145{'input_ids': tensor([[, 872, 198, 11622, , , , , 1773, , 198, , 77091, 198, , 271, , 271]], device='cuda:0'), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], device='cuda:0')}
当然前面这种方法是从更底层的角度来进行实现的,会对整体输出的格式和使用的设备会有更精确的把控。
使用方法
generated = model.generate( inputs, max_new_tokens=60, do_sample=True, temperature=0.6, top_k=20, top_p=0.95, )
在进行转换后,我们输入的内容已经准备输入到大模型中获取回复了。这个时候我们就需要使用 model.generate 的方法来将内容传入并获取回复。
首先这里我们需要必须传入的是 inputs 里的内容,但是由于参数需要的是 input_ids 和 attention_mask,所以我们需要通过 对原本字典里的内容进行解包,比如对下面这个代码解包:
GPT plus 代充 只需 145generated = model.generate(inputs)
那么我们将会得到:
model.generate( input_ids=inputs["input_ids"], attention_mask=inputs["attention_mask"] )
也就是:
GPT plus 代充 只需 145model.generate( input_ids=tensor([[, 872, 198, 11622, , , , , 1773, , 198, , 77091, 198, , 271, , 271]], device='cuda:0'), attention_mask=tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], device='cuda:0') )
除了传入这两个参数以外,我们还可以传入其他常用参数,比如:
max_new_tokens:这个参数主要是控制模型最终输出的参数数量,假如超过了设置好的数量就会被截断。

do_sample:指是否采用 随机采样(sampling) 方式生成 token。假如是 false 的话采用的是贪心解码(每步选最大概率),true 的话则会按概率分布随机采样。随机采样能够让模型的回复更加多样。

temperature:模型的温度情况,本质是对概率分布做缩放。越小的 temperature 其稳定性越好,越大的话随机性越大。

top_k:在每一步生成中,只从概率最高的前 K 个 token中进行采样。这样不仅能够让模型的输出有随机性,同时也能剪掉极低概率 token ,进而防止生成明显不合理的词。

top_p:指从最小的 token 集合中采样,使其累计概率 ≥ 0.95。比如前五个词的概率加起来超过 95% 的话,那就只保留前五个词即可。所以假如同时有 top/_p 和 top/_k 存在,那就会考虑其交集。

那后面几个其实在 generation_config.json 文件中都有,假如我们不进行设置的话,那就会直接采用该文件里的参数值:
{ "bos_token_id": , "do_sample": true, "eos_token_id": [ , ], "pad_token_id": , "temperature": 0.6, "top_k": 20, "top_p": 0.95, "transformers_version": "4.51.0" }
这也是千问官方推荐使用的参数值。所以一般情况下我们只需要传入 inputs 并进行解包,同时传入 max_new_tokens 保证生成的 token 数量即可生成回复。
此时运行后生成的结果就还是前面看到的张量信息,这个也就是模型输入后获取的结果:
GPT plus 代充 只需 145tensor([[, 872, 198, 11622, , , , , 1773, , 198, , 77091, 198, , 271, , 271, , , , 34204, , , 57191, 27369, 3837, 23031, , 88086, , 5373, 99257, 57191, , 9370, , , 1773, ]], device='cuda:0')
后续我们需要对该信息进行解析才能获取返回的内容。也就是前面在原理时候的提到过的反向映射(decode)。

原理解析
那既然这里的 token 是一个个生成的,那到底这个生成背后到底是如何实现的呢?
我们可以把 generate() 理解为一个自动循环器:它会不断重复下面这套流程:
- 把当前的 token 序列喂给模型(也就是已经编码好的
input_ids) - 模型会输出一张“候选表”,里面对词表里每个 token 都打了一个分数(可以理解成“可能性大小”)

- 按生成策略从候选里选一个 token(选出来的就是“下一 token”)
- 把这个 token 追加到序列末尾
- 回到第 1 步继续循环
所以 generate() 的本质就是一个循环:预测 → 选择 → 追加 → 再预测。这也是 CausalLM 的核心:自回归生成(Autoregressive Generation)。
但是模型不会一直生成下去,常见停止方式有两种:
- //达到
max_new_tokens//:比如这里设置了60,最多生成 60 个新 token 就停。 - 生成到结束符(EOS):模型一旦生成了“结束标记”,就代表它认为回答该收尾了,也会停。这个也可以在 generation/_config.json 文件里看到,就是 token ID 为 (
<|im_end|>) 或 (<|endoftext|>) 其中一个。
new_tokens = generated[0][inputs["input_ids"].shape[1]:]
在获取到回复的内容张量后,由于 generated 返回的张量是包含输入和输出 token 内容的完整序列。为了能够让返回的内容更干净一些,我们需要将输入部分的内容删除,只保留输出部分的内容。
由于当前的 tensor 是一个二维的张量(从 [[…]] 的结构可看出),所以我们需要先通过 generated[0] 将张量转为一维的:
GPT plus 代充 只需 145tensor([, 872, 198, 11622, , , , , 1773, , 198, , 77091, 198, , 271, , 271, , , , 34204, , , 57191, 27369, 3837, 23031, , 88086, , 5373, 99257, 57191, , 9370, , , 1773, ], device='cuda:0')
然后通过获取 inputs["input_ids"].shape[1] 获取到输入内容的长度信息后,通过 generated[0][input_length:] 实现从“输入 token 结束的位置”开始,一直到生成序列的末尾,也就是input/_ids 的最后一个 token 之后的所有 token 。通过这样的方式我们就能够实现结果的获取。
此时返回的内容就是:
tensor([[, , , 34204, , , 57191, 27369, 3837, 23031, , 88086, , 5373, 99257, 57191, , 9370, , , 1773, ]], device='cuda:0')
GPT plus 代充 只需 145answer = tokenizer.decode(new_tokens, skip_special_tokens=True).strip()
在获取到最终结果的张量信息后,我们可以再次通过 tokenizer.decode() 对原本的内容进行反向映射,也就是把 token id 序列转为人类可读文本。最后的 .strip() 其实主要是去掉首尾空白字符(换行、空格)。
在 decode() 中常见的参数包括:
token_ids:这个是必填的参数,也就这里的 new/_tokens。但是这里不能是二维的张量,必须是一维的。skip_special_tokens:其作用是决定是否在解码时,跳过“特殊 token”。这些特殊的 token 也就是<|im_start|>、<|im_end|>等。假如开启了那就不再会显示这些特殊的内容了,直接展示结果(但是 think 标签由于需要进行区分所以还会保留)。
最后输出的结果如下所示:
注意力是指个体在认知过程中对目标信息的集中和聚焦,以便更有效地处理和利用这些信息。
假如是开启了思考模式的话,那么该结果将可能会是:
GPT plus 代充 只需 145
好的,用户让我用一句话解释什么是注意力。首先,我需要确定用户的需求是什么。他们可能是在学习心理学、计算机科学或者相关领域,想要一个简洁的定义。注意力是一个核心概念,涉及人的认知过程,所以需要准确且简明的表达。 接下来,我要考虑用户的背景。如果是学生,可能需要基础的定义;如果是专业人士,可能需要更深入的解释。但用户没有特别说明,所以保持通用性比较好。然后,我要确保用一句话涵盖注意力的定义,同时包含关键要素,比如关注、集中、处理信息等。 可能需要注意术语的准确性,比如“注意力”通常指的是大脑处理信息的能力,而不仅仅是关注。需要确认是否有必要提到处理信息,但用户只要求一句话,所以可以省略。另外,可能需要强调注意力的动态变化,比如从注意到转移,但用户可能只需要静态定义,所以保持简洁。 最后,检查句子是否流畅,是否准确传达了注意力的概念,同时没有冗余。确保没有语法错误,用词恰当。
注意力是指大脑在处理信息时的集中能力,即对目标信息的聚焦和处理。
在这节课里,我们从“pipeline 一行调用模型”的黑盒用法,进一步走到了 Transformers 底层推理代码的完整链路,把一次本地大模型对话的全过程拆解成了可以理解、可以复现的工程步骤。
首先,我们明确了这段代码的总体逻辑:加载资源 → 构造输入 → tokenize 数字化 → generate 自回归生成 → decode 还原输出。其中 tokenizer 负责把自然语言转换成 token/id(以及反向还原),AutoModelForCausalLM 负责根据输入张量输出 logits,并通过解码策略一步步生成新的 token,最终形成可读的回答。
在理解“模型如何跑起来”之外,我们还进一步把视角扩展到“模型为什么能对话”。我们强调:CausalLM 从头到尾的学习目标几乎不变——始终是预测下一个 token。模型从随机参数到具备语言能力,关键在于训练数据与训练阶段的逐层塑形:预训练用海量语料让模型学会语言规律与统计知识,SFT 用高质量指令对话样本教模型“按对话结构进行回答”,RLHF 则在此基础上引入人类偏好与安全约束,让模型逐步理解“什么是更好的回答、什么该说什么不该说”。
特别是在 SFT 部分,我们结合 Qwen3 的 Chat Template 解释了“角色边界”为什么如此重要:通过 system/user/assistant/tool 等 role 和 <|im_start|>/<|im_end|> 等特殊 token,模型才真正知道“哪段是用户、哪段是助理、从哪里开始生成”。而 add_generation_prompt、enable_thinking 等参数,则进一步决定了生成的起点与推理输出的形式。
最后,我们回到推理侧的工程细节:apply_chat_template 把 messages 渲染成模型输入文本;tokenizer(…, return_tensors=“pt”) 把文本编码为张量;generate() 通过 temperature/top_k/top_p 等策略控制生成的随机性与质量;再通过“截取新增 token + decode”拿到干净的最终回答。至此,你已经具备了读懂并改造本地推理代码的能力——后续不论是加入系统提示词、加入多轮历史对话、接入工具调用,还是进一步走向 LoRA/QLoRA 的微调实践,都有了清晰的底层认知与工程抓手。
读者福利:倘若大家对大模型感兴趣,那么这套大模型学习资料一定对你有用。
针对0基础小白:
包括:大模型学习线路汇总、学习阶段,大模型实战案例,大模型学习视频,人工智能、机器学习、大模型书籍PDF。带你从零基础系统性的学好大模型!
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓


👉AI大模型学习路线汇总👈
大模型学习路线图,整体分为7个大的阶段:(全套教程文末领取哈)
第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;
第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;
第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;
第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;
第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;
第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;
第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。
👉大模型实战案例👈
光学理论是没用的,要学会跟着一起做,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

👉大模型视频和PDF合集👈
这里我们能提供零基础学习书籍和视频。作为最快捷也是最有效的方式之一,跟着老师的思路,由浅入深,从理论到实操,其实大模型并不难。

👉学会后的收获:👈
• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;
• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;
• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;
• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。
👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓
👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/244517.html