news 2026/4/16 0:20:16

有关大模型的本质思考

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
有关大模型的本质思考

昨天和朋友讨论生成式大模型的机理时,谈到了一个之前没注意过的问题:

神经网络的输入输出往往是固定的,为什么大模型能处理不同长度文本?比如给GPT输入不同的提示词,它都能继续不断预测下一个词。

这个问题涉及到语言大模型的本质思考:一段文本输入到GPT模型中,模型是如何输出下一个Token的?

这个问题一下子没有答案,于是我在讨论之后,决定从源码的角度,重新来梳理一下GPT的生成过程。

代码下载

网上有不少GPT的代码实现,大多考虑不少工程实现的问题,导致代码变得复杂理解。

为了更容易理解代码,我下载了GPT-1[1]这个仓库的代码实现,它是一个基于GPT-1论文内容,非官方的Pytorch实现。

由于这个仓库的tokenizer等相关依赖不是很全,因此我让AI写了一段推理脚本,使用 GPT-2 的tokenizer,脚本内容如下:

importtorchfromtransformersimportGPT2TokenizerFastfrommodel.modelimportGPTdefmain():device=("mps"iftorch.backends.mps.is_available()else"cuda"iftorch.cuda.is_available()else"cpu")# 直接下载 GPT-2 tokenizer(自动缓存)tokenizer=GPT2TokenizerFast.from_pretrained("gpt2")tokenizer.pad_token=tokenizer.eos_token vocab_size=tokenizer.vocab_size seq_len=128# GPT 模型model=GPT(vocab=vocab_size,seq=seq_len,n_layers=4,n_heads=8,dim=256,hidden=1024,dropout=0.1,device=device).to(device)model.eval()# 原始文本texts=["Hello world!","GPT predicts next token."]# tokenizer → idsenc=tokenizer(texts,padding="max_length",truncation=True,max_length=seq_len,return_tensors="pt")x=enc["input_ids"].to(device)# [B, T]ignore=enc["attention_mask"].to(device)# 1=valid, 0=pad# forwardwithtorch.no_grad():logits=model(x,ignore)print("Logits shape:",logits.shape)# next token demofori,textinenumerate(texts):last=ignore[i].sum().item()-1next_id=logits[i,last].argmax().item()next_token=tokenizer.decode([next_id])print(f"\nSample{i}")print("Input:",text)print("Next token:",repr(next_token))if__name__=="__main__":main()

首次运行,会下载以下文件,这些文件在开源的模型库中也往往出现。

tokenizer_config.json: 100%|████████████████████████████████████████████████████████████████████████████████████████████████| 26.0/26.0 [00:00<00:00, 20.2kB/s] vocab.json: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████| 1.04M/1.04M [00:00<00:00, 4.68MB/s] merges.txt: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████| 456k/456k [00:00<00:00, 2.17MB/s] tokenizer.json: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████| 1.36M/1.36M [00:00<00:00, 3.09MB/s] config.json: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████| 665/665 [00:00<00:00, 1.36MB/s]

这几个文件的作用分别如下表所示:

文件名所属范畴主要作用
tokenizer_config.json分词器配置分词器的初始化参数和特殊标记定义。
vocab.json分词器数据Token 到数字 ID 的映射字典。
merges.txt分词器数据BPE 等算法的子词合并规则。
tokenizer.json分词器 (现代格式)整合所有分词逻辑和数据的完整文件。
config.json模型配置模型架构(网络结构)的超参数和配置。

分词器基本原理

为什么要下载这几个文件?主要目的是要下载分词器

大模型的输入和输出是以Token作为计量单位,那么大模型如何知道,哪些词是作为一个Token,这就需要通过分词器来界定。

比如,看DeepSeek-V3.2-Exp的开源仓库[2],也有tokenizer.json这个文件。

在里面搜索Hello这个单词,发现它的id是19923。

而打开 GPT-2 的 tokenizer.json,再搜索Hello这个单词,它的id是15496。

分词器就像一个词典,如果词典里词很多,那么模型的表示空间也很大。

换言之,如果某个小国家的语言没有被编码,纳入这个词典,就意味着模型是无法输出相关的语言的。

另外,对于中文,直接在这个词典里搜,是搜索不到的。

GPT-2的分词器对中文是这么操作的:先把文本转成 UTF-8 字节序列,再把字节映射到 256 个可见符号。

比如,把这个字,变成 UTF-8 字节。

"你".encode("utf-8")

结果如下:

E4 BD A0 (十六进制)

转换成十进制就是:

字节十进制
0xE4228
0xBD189
0xA0160

把 0–255 的 byte 映射到一组 可见 Unicode 字符,结果就是:

byte 228 → "ä" byte 189 → "½" byte 160 → "ł"

在这种映射规则下,这个字就被映射为:

你 → [228,189,160] → "ä½ ł"

对此,我们可以做一个实验论证,用这个分词器,输出的Token表示:

fromtransformersimportGPT2TokenizerFast tokenizer=GPT2TokenizerFast.from_pretrained("gpt2")tokens=tokenizer("你",add_special_tokens=False)print(tokens)

结果如下:

{'input_ids': [19526, 254], 'attention_mask': [1, 1]}

再通过ID反推出token表示:

fromtransformersimportGPT2TokenizerFast tokenizer=GPT2TokenizerFast.from_pretrained("gpt2")print(tokenizer.convert_ids_to_tokens([19526,254]))

输出结果如下:

['ä½', 'ł']

在 tokenizer.json 中,可以找到ä½ł单独的Token表示。

所以,我们可以得到结论:对于汉字,GPT-2的分词器会把它拆成两个Token,这显然不是很合理。

因为对于英文you,却是一个Token。

为什么会出现这种情况呢?

原因是GPT-2刚出来的时候,没有针对中文进行优化,它底层采用的是BPE(Byte Pair Encoding)压缩算法,核心目标是是在固定符号表大小的前提下,最小化序列长度。

它会重复以下步骤:

  • 统计当前语料中所有相邻符号对(pair)的出现频率
  • 找到出现次数最多的 pair
  • 把这个 pair 合并成一个新符号
  • 把新符号加入 vocab(符号表)
  • 更新整个语料的表示

比如,you这个单词先拆解为you,然后发现语料中,这种组合很多,于是,you就自然被算法优化成一个Token。

而它们的语料中,中文内容很少,因此,汉字就会被自然拆解,而不是合并。

后面的一系列模型(LLaMA / Qwen / DeepSeek)都放弃 byte-BPE,而使用 SentencePiece / Unigram。这部分研究起来,可挖掘的东西还很多,这里就不作深入展开。

当然,也不是所有的汉字都会被拆成多个Token,merges.txt 这个文件就是专门用BPE算法计算Token的合并。

比如,在里面可以找到ä½ ¿这条规则,翻译成中文就是汉字使,它可以用一个Token来表征。

总之,为什么GPT-2要把所有非英文语言都用 byte 来表示呢?

主要原因是语言模型必须有固定大小的字典,这个字典(vocab)的大小必须在训练前确定。

UTF-8 byte 的范围是 [0, 255],数量固定,所有文本都能表示,它解决的是覆盖性问题,不会因为输入是词汇表里没有的新字符,而导致无法推理。

这样做显然会把语言本身固有的一些分词特征给破坏掉,并且会增加token表示,这是一种工程妥协。

但英文却没有再用 UTF-8 转换,因为英文的字符集本身就能百分百覆盖(a–z, A–Z,0–9,常见标点,
不到 100 个字符)。

而且,英文语法有个天然的优势是,单词之间用空格间隔,这和分词的逻辑天然适配。

这就是为什么即便大模型发展到现在,主流方式仍然会采用英文去写复杂的提示词。

模型处理过程

了解完分词器,下面进入到模型的处理过程。

模型初始化

首先初始化模型,传入以下参数:

vocab_size=tokenizer.vocab_size seq_len=128# GPT 模型model=GPT(vocab=vocab_size,seq=seq_len,n_layers=4,n_heads=8,dim=256,hidden=1024,dropout=0.1,device=device).to(device)

这里有两个参数很关键,第一个参数是vocab_size,这个上一节已经提过,是字典的大小。

第二个参数seq_len就是输入的上下文长度,在官方的GPT-1论文中,上下文长度是512,这里代码作者可能做了简化,固定成了128。

模型的上下文长度意味着什么?下面深入到模型代码中,进行分析。

在模型的实现代码中,上下文长度就干了一件事:对每个位置进行查表嵌入。

self.pos_embed=Embedding(seq,dim)self.pos=LongTensor([iforiinrange(128)])pe=self.pos_embed(self.pos)

Embedding就是查表的过程,根据位置的index,去一张表里,查询dim维的特征向量,表的长度,就是上下文长度。

index 0 → 向量 p₀ ∈ R^dim index 1 → 向量 p₁ ∈ R^dim index 2 → 向量 p₂ ∈ R^dim ... index 127 → 向量 p₁₂₇ ∈ R^dim

文本输入大模型之前,先通过分词器进行分词,然后将Token序列输入到模型中,同样通过查表Embedding的方式去变成dim维的特征向量。

self.bpe_embed=Embedding(vocab,dim).to(device)be=self.bpe_embed(x)

模型真正输入进去的,实际上就是dim维的特征向量的累加值:token的向量结果和位置编码的向量结果相加。

因此,本文开头的那个问题就已经得到解决,为什么不同长度的文本,模型都能处理呢?

因为不管有多长,只要不超出上下文范围,都会被映射成维度固定的特征向量,这样模型就能够统一处理。

所以,这个维度的选取就很关键,如果维度选取的很小,那么长文本反而被压缩成低维,语义信息丢失就很大,所以这个dim值至少要比上下文长度大,越大越有利于进行不同文本的抽象表征。

那么,如果输入超出上下文范围怎么办?从模型角度来说:无法处理。

有些大模型平台能处理超长文本,不过是在后台做了工程优化,进行文本截取或压缩,而ChatGPT这方面的处理不多,这就是为什么输入很长的信息时,它会提示“文本过长,无法输入”。

有了这个洞察之后,下面再思考一个更深入的问题:高维的特征向量如何进行语义表示?

答案是:完全靠训练获得,大力出奇迹。

一开始,这两个表中的特征向量都是随机噪声,然后通过模型的训练,逐渐去贴合训练样本。

normal_(self.bpe_embed.weight, mean=0.0, std=0.02) normal_(self.pos_embed.weight, mean=0.0, std=0.02)

到底用多少维的特征向量表示合适,初始化如何利用先验知识?这方面的可解释性太差了,所以GPT模型的本质就是“复读机”:给它看过什么内容,它就倾向于输出什么内容。

之后,模型就更加“暴力”了,堆叠多个输入和输出相同的transformer block,来不断做scaling,最后算出下一个token的概率。

理解完大模型输出的机理后,对KV-Cache的理解也会更加深刻。

为什么要用KV-Cache呢,原因是模型的输入是个顺序累加过程。

之前对话的历史记录,会被拼接到后续对话的头部,进行计算。

之前的token之间的attention已经在对话中计算过,这部分就可以缓存下来,后面不用再算,只需要再额外计算之前的token和最新输入token之间的attention就可以了,如此,便是空间换时间。

总结

搞清楚大模型输出的原理后,会发现一件很“滑稽”的事:不管输入的是长是短,每个token在向量空间中的维度是固定的,长短只是影响并行计算之间Attention计算的效率。

换言之,对于一段相同长度的问题来说,困难问题和简单问题所带来的计算量是一样的。这合理吗?显然不合理。

之前看到智谱清言CEO张鹏的一期访谈,其中就谈到,对于GPT的架构来说,它没法实现AGI,因为从根上来说,“它不知道自己不知道”,以至于经常“一本正经的胡说八道”。

另外,分词器并不是一个语言公平的工具,它和大模型之间是完全割裂的。

这就让我想起 DeepSeek-OCR 发布时,就看到有人抨击:分词器实在是“丑陋”的设计,也许用图像作为token输入能完全取代它。

总之,GPT把人类带到了一条堆算力的路线上,虽然它很暴力,不优雅,但如果没有它的帮助,想必我也无法在短短的一晚上想清楚这些问题。

参考

[1] https://github.com/akshat0123/GPT-1
[2] https://huggingface.co/deepseek-ai/DeepSeek-V3.2-Exp/raw/main/tokenizer.json
[3] https://www.bilibili.com/video/BV1awiDBDEWS

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/4 5:05:59

Z-Image Turbo体验指南:1块钱起玩转阿里开源模型

Z-Image Turbo体验指南&#xff1a;1块钱起玩转阿里开源模型 引言&#xff1a;为什么选择云端体验Z-Image Turbo&#xff1f; 阿里最新开源的Z-Image Turbo模型在图像生成领域引起了广泛关注&#xff0c;但很多技术爱好者在本地部署时遇到了各种报错和兼容性问题。如果你也遇…

作者头像 李华
网站建设 2026/4/14 7:26:16

ComfyUI恐惧症治愈:Z-Image云端极简模式体验

ComfyUI恐惧症治愈&#xff1a;Z-Image云端极简模式体验 1. 为什么你需要Z-Image极简模式 如果你曾经被ComfyUI复杂的节点连线界面吓退&#xff0c;那么Z-Image云端极简模式就是为你量身定制的解决方案。想象一下&#xff0c;ComfyUI原本的界面就像是一台专业录音棚的调音台&…

作者头像 李华
网站建设 2026/4/12 7:39:37

数字内容保存高效解决方案:让珍贵记忆永不丢失

数字内容保存高效解决方案&#xff1a;让珍贵记忆永不丢失 【免费下载链接】VK-Video-Downloader Скачивайте видео с сайта ВКонтакте в желаемом качестве 项目地址: https://gitcode.com/gh_mirrors/vk/VK-Video-Download…

作者头像 李华
网站建设 2026/4/13 21:00:38

国家中小学智慧教育平台教材下载完整指南:3步轻松获取电子课本

国家中小学智慧教育平台教材下载完整指南&#xff1a;3步轻松获取电子课本 【免费下载链接】tchMaterial-parser 国家中小学智慧教育平台 电子课本下载工具 项目地址: https://gitcode.com/GitHub_Trending/tc/tchMaterial-parser 还在为无法下载国家中小学智慧教育平台…

作者头像 李华
网站建设 2026/4/15 8:24:45

【稀缺技术揭秘】:超低延迟音视频传输是如何实现的?

第一章&#xff1a;超低延迟音视频传输的技术背景在实时通信、云游戏、远程医疗和在线教育等场景中&#xff0c;用户对音视频交互的实时性要求日益提升。传统流媒体协议如HLS或RTMP通常带来数秒级延迟&#xff0c;已无法满足当前业务需求。超低延迟音视频传输技术应运而生&…

作者头像 李华