Qwen多轮对话speical token学习方式

目录

[一、核心前提:特殊 Token 在训练中的共性](#一、核心前提:特殊 Token 在训练中的共性)

[二、阶段 1:预训练阶段(Base 模型)------ 仅用 <|endoftext|>](#二、阶段 1:预训练阶段(Base 模型)—— 仅用 <|endoftext|>)

[三、阶段 2:对话微调阶段(Chat 模型)------ 核心用 <|im_start|>/<|im_end|>](#三、阶段 2:对话微调阶段(Chat 模型)—— 核心用 <|im_start|>/<|im_end|>)

[四、训练后模型对 Token 的认知变化](#四、训练后模型对 Token 的认知变化)

五、补充:训练时的技术细节

总结

[多轮对话中special token的组织形式:](#多轮对话中special token的组织形式:)

参考:


你关注的这 3 个核心特殊 Token(<|endoftext|><|im_start|><|im_end|>)在 Qwen 模型预训练对话微调 阶段的应用逻辑完全不同,是模型能理解 "文本边界" 和 "对话结构" 的核心 ------ 预训练阶段主要用 <|endoftext|> 划分样本,微调阶段则靠 <|im_start|>/<|im_end|> 构建结构化对话数据。

一、核心前提:特殊 Token 在训练中的共性

无论预训练还是微调,这 3 个 Token 都有 2 个关键训练属性(由代码中 AddedToken 的参数决定):

  1. special=True:训练时被标记为 "特殊 Token",不参与分词器的子词拆分,始终作为单个完整 Token 处理;
  2. normalized=False:训练时不做文本归一化(如大小写转换、空格清理),保证 Token 格式绝对固定;
  3. lstrip=False/irstrip=False:训练时不会忽略 Token 左右的空格,避免对话结构错位。

二、阶段 1:预训练阶段(Base 模型)------ 仅用 <|endoftext|>

Qwen 预训练的核心目标是学习通用文本的语义和语法,此时 <|im_start|>/<|im_end|> 尚未被引入(这两个是对话微调阶段的专属 Token),仅 <|endoftext|> 发挥作用:

  1. 核心作用:划分预训练样本边界

预训练数据是海量无结构文本(书籍、网页、代码等),<|endoftext|>样本分隔符,用于告诉模型 "这是一个样本的结束,下一个是新样本"。

  1. 具体应用方式
  • 数据预处理 :将海量文本切分为若干 "样本片段"(长度匹配模型上下文窗口,如 2048/4096 Token),每个片段末尾拼接 <|endoftext|>

  • 训练输入格式

    plaintext

    复制代码
    样本1文本...<|endoftext|>样本2文本...<|endoftext|>样本3文本...<|endoftext|>
  • 模型学习目标 :模型需要预测每个位置的下一个 Token,当预测到 <|endoftext|> 时,模型知道 "当前样本结束,下一个 Token 是新样本的开头",从而避免样本间的语义干扰。

  1. 关键细节
  • 预训练阶段,<|im_start|>/<|im_end|> 不会出现在训练数据中,模型对这两个 Token 无任何先验认知;
  • <|endoftext|> 的 Token ID 是预训练阶段固定的(如 Qwen-7B 中为 151643),训练时作为 "终止符" 参与损失计算,但不会被模型随机生成(仅作为样本边界)。

三、阶段 2:对话微调阶段(Chat 模型)------ 核心用 <|im_start|>/<|im_end|>

Qwen-Chat 模型是在 Base 模型基础上做 "对话微调",此时 <|im_start|>/<|im_end|> 成为核心,<|endoftext|> 则退为辅助角色:

  1. 核心目标

让模型学会 "理解多轮对话结构"------ 区分「系统指令」「用户提问」「助手回答」的角色边界,生成符合人类对话逻辑的回复。

  1. 具体应用方式(结构化训练数据)

微调数据会被强制构造成「<|im_start|>+角色+文本+<|im_end|>」的固定格式,每个对话轮次都是一对 <|im_start|>/<|im_end|>,示例如下:

单轮对话训练样本

复制代码
<|im_start|>system
你是一个乐于助人的AI助手,回答简洁明了。<|im_end|>
<|im_start|>user
什么是Qwen模型?<|im_end|>
<|im_start|>assistant
Qwen(通义千问)是阿里云研发的大语言模型,支持多轮对话和代码生成。<|im_end|><|endoftext|>

多轮对话训练样本

复制代码
<|im_start|>system
你是数学老师,解答数学问题。<|im_end|>
<|im_start|>user
1+1等于多少?<|im_end|>
<|im_start|>assistant
1+1等于2。<|im_end|>
<|im_start|>user
那2+2呢?<|im_end|>
<|im_start|>assistant
2+2等于4。<|im_end|><|endoftext|>
  1. 各 Token 的微调分工
Token 微调阶段作用
`< im_start >` 标记 "某角色发言的开始",后跟角色名(system/user/assistant),让模型识别发言者身份;
`< im_end >` 标记 "某角色发言的结束",与 `< im_start >` 成对,划分单轮发言的边界;
`< endoftext >` 标记 "整个对话样本的结束",放在最后一轮 `< im_end >` 之后,终止当前样本的训练;
  1. 训练过程的关键逻辑
  • 输入 / 输出构造
    • 输入:完整的结构化对话文本(含所有 <|im_start|>/<|im_end|>);
    • 输出:与输入一致,但仅对 "助手回答部分" 计算损失(即模型只需学习生成「<|im_start|>assistant<|im_end|>」之间的内容);
  • 梯度更新 :模型学习 "看到 <|im_start|>user 后,生成符合逻辑的 <|im_start|>assistant 回复",<|im_start|>/<|im_end|> 作为结构标记,帮助模型定位 "该生成哪部分内容";
  • 停止生成学习 :微调时模型被训练为 "生成到 <|im_end|> 后停止",最终对外提供服务时,<|endoftext|> 作为兜底的生成终止符。

四、训练后模型对 Token 的认知变化

  1. 预训练后:模型仅认识 <|endoftext|>,知道它是 "文本结束";
  2. 微调后:模型学会:
    • <|im_start|> + 角色名 → 触发对应角色的发言逻辑;
    • <|im_end|> → 结束当前角色的发言,准备接收下一轮输入;
    • <|endoftext|> → 整个对话结束,停止生成。

五、补充:训练时的技术细节

  1. Token ID 固定 :这 3 个 Token 的 ID 在训练前就被硬编码到分词器中(如 <|im_start|>=151644,<|im_end|>=151645),训练过程中不会改变;
  2. 损失屏蔽<|im_start|>/<|im_end|>/<|endoftext|> 作为结构标记,不参与损失计算(模型只需预测 "语义文本",无需预测这些标记);
  3. 批量训练适配 :当批量样本长度不一致时,用 <|endoftext|> 作为 pad_token 填充(与你之前分词器配置 pad_token="<|endoftext|>" 一致),避免填充 Token 干扰对话结构。

总结

这 3 个特殊 Token 在训练中的应用核心是 "分层划分边界":

  1. 预训练 :靠 <|endoftext|> 划分 "独立文本样本",学习通用语义;
  2. 对话微调 :靠 <|im_start|>/<|im_end|> 划分 "对话角色 / 轮次",学习结构化对话逻辑,<|endoftext|> 兜底标记 "对话样本结束"。

正是这种训练时的结构化设计,让 Qwen-Chat 模型能精准理解多轮对话的角色和上下文,生成符合人类交互逻辑的回复。

多轮对话中special token的组织形式:

qwen_generation_utils:

python 复制代码
def make_context(
    tokenizer: PreTrainedTokenizer,
    query: str,
    history: List[Tuple[str, str]] = None,
    system: str = "",
    max_window_size: int = 6144,
    chat_format: str = "chatml",
):
    if history is None:
        history = []

    if chat_format == "chatml":
        im_start, im_end = "<|im_start|>", "<|im_end|>"
        im_start_tokens = [tokenizer.im_start_id]
        im_end_tokens = [tokenizer.im_end_id]
        nl_tokens = tokenizer.encode("\n")

        def _tokenize_str(role, content):
            return f"{role}\n{content}", tokenizer.encode(
                role, allowed_special=set()
            ) + nl_tokens + tokenizer.encode(content, allowed_special=set())

        system_text, system_tokens_part = _tokenize_str("system", system)
        system_tokens = im_start_tokens + system_tokens_part + im_end_tokens

        raw_text = ""
        context_tokens = []

        for turn_query, turn_response in reversed(history):
            query_text, query_tokens_part = _tokenize_str("user", turn_query)
            query_tokens = im_start_tokens + query_tokens_part + im_end_tokens
            response_text, response_tokens_part = _tokenize_str(
                "assistant", turn_response
            )
            response_tokens = im_start_tokens + response_tokens_part + im_end_tokens

            next_context_tokens = nl_tokens + query_tokens + nl_tokens + response_tokens
            prev_chat = (
                f"\n{im_start}{query_text}{im_end}\n{im_start}{response_text}{im_end}"
            )

            current_context_size = (
                len(system_tokens) + len(next_context_tokens) + len(context_tokens)
            )
            if current_context_size < max_window_size:
                context_tokens = next_context_tokens + context_tokens
                raw_text = prev_chat + raw_text
            else:
                break

        context_tokens = system_tokens + context_tokens
        raw_text = f"{im_start}{system_text}{im_end}" + raw_text
        context_tokens += (
            nl_tokens
            + im_start_tokens
            + _tokenize_str("user", query)[1]
            + im_end_tokens
            + nl_tokens
            + im_start_tokens
            + tokenizer.encode("assistant")
            + nl_tokens
        )
        raw_text += f"\n{im_start}user\n{query}{im_end}\n{im_start}assistant\n"

    elif chat_format == "raw":
        raw_text = query
        context_tokens = tokenizer.encode(raw_text)
    else:
        raise NotImplementedError(f"Unknown chat format {chat_format!r}")

    return raw_text, context_tokens

参考:

https://blog.csdn.net/u011995719/article/details/139046487

https://zhuanlan.zhihu.com/p/660147228

https://blog.csdn.net/m0_52827625/article/details/154799076

相关推荐
金融小师妹9 小时前
机器学习捕捉地缘溢价:黄金突破一周高位,AI预测模型验证趋势强度
大数据·人工智能·深度学习
一招定胜负9 小时前
自然语言处理CBOW模型:基于上下文预测中间词
人工智能·深度学习·机器学习
汤姆yu10 小时前
基于深度学习的杂草检测系统
人工智能·深度学习
LaughingZhu10 小时前
Product Hunt 每日热榜 | 2026-01-06
人工智能·经验分享·深度学习·神经网络·产品运营
狮子座明仔10 小时前
HierGR:美团外卖搜索的层级语义生成式检索系统
人工智能·深度学习·语言模型·自然语言处理
老吴学AI11 小时前
斯坦福AI顶级课程:AI 职业发展建议与市场展望(详细逐字稿)by 吴恩达和劳伦斯
人工智能·深度学习·机器学习·vibe coding
import_random11 小时前
[深度学习]LSTM模型的构建模块(如何添加层)
深度学习
t1987512811 小时前
神经网络控制的多方法融合:PID、模型预测控制(MPC)与自适应策略
人工智能·深度学习·神经网络
brent42311 小时前
DAY47 简单CNN
深度学习·神经网络·cnn
540_54011 小时前
ADVANCE Day41
人工智能·python·深度学习