PaperReading:VLM 串讲
本文字数:21.7k 字 | 阅读时长 ≈ 85 min

PaperReading:VLM 串讲

本文字数:21.7k 字 | 阅读时长 ≈ 85 min

InternVL

从题目可以看出来,这篇文章的核心还是 scale up 大尺寸的 vision model,用其和 LLM 进行融合来完成生成相关的 task

1. 引子图

下面是 title 下面的一个图,表达了传统的 vision model 用分类任务训练,CLIP 用对比学习训练,本文的 scale 模型的同时用来做生成任务

在 abstract 部分,作者就挑明本文的一个核心贡献点,即 6B 的 vision 模块,并且经过训练可以很好的替代 ViT-22B,后面都围绕着个展开。

2. 动机部分

motivation:本文提到了当前的 vision model 很难和大的 LLM 配合,“commonly employ lightweight ‘glue’ layers, such as QFormer or linear projection”,并产生了一系列的缺点:(1)参数规模的不均匀(2)视觉模块和语言模块不一致的特征表达(3)视觉和语言之间连接低效

本文的主要贡献点:提出了 InternVL,scale 了 vision 部分,并且取得了 SOTA 的结果。(we formulate the InternVL, a large-scale vision-language foundation model, which aligns the representation of the scaled-up vision encoder with the LLM and achieves state-of-the-art performance on various visual and vision-language tasks.)简单来讲贡献为(1)vision 和 llm 部分的参数均衡(2)采用预训练的多语言 LLaMA 来初始化中间层,将 vision 和他对齐(3)渐进式的图文对齐:先用 noisy 的图文数据,然后转移到细粒度的图文数据

3. 模型架构

整体架构如下所示,作者还给定了 InternViT-6B 的整体尺寸

InternViT-6B:具体参数如上,这里作者使用 100M 的 LAION-en 数据子集来进行验证
QLLaMA:QLLaMA 被用来进行视觉语言对齐。QLLaMA 基于 LLaMA 开发的,新增了 96 个 learnable query 和 cross attention layers(引入 1B 参数),这些 layers 是随机初始化的。其实我读到这里的时候,我感觉有点奇怪,ViT+cross attention+QLLaMA 不就是一个基本的 VLM 的架构了吗,为啥还要以对齐作为目标呢?有点奇怪。
InternVL:上面虽然有点奇怪,但不影响我们后面的阅读。在 InternVL 部分,作者强调在上面训练好的模型部分,InternViT 可以用来进行 dense 或者 cls 的任务。此外还有生成任务和多模态对话任务,下面图中展示了这一点。

从上图可以看出来,对于对比学习,图像 feature 一个是直接从 InternViT 中得到,一个是通过 QLLaMA 之后再获得。text feature 均来自 QLLaMA。对于后面的生成任务,QLLaMA 可以加也可以不加,其实我这点挺疑惑的,不是很懂为什么要这么做,可能是觉得方法的通用性?

4. 对齐策略

训练策略分为三部分:对比学习,生成式学习和监督学习,这里就是对应上面方法框图的 1-3 部分

第一部分对比学习的数据集如下,一共有 6.03B 数据经过清理之后只保留了 4.98B

第二阶段采用生成式学习方法:这里除了 InternViT-6B,和 LLaMA,还引入了 cross attention layers,这里只微调可学习的 query 和 cross attention layers,其他的都 freeze 住。在第一步的基础上,把数据集从 4.98B 缩小到 1.03B。参考 BLIP2 的训练方法,loss 分为三部分,image-text contrastive (ITC) loss, image-text matching (ITM) loss, and image-grounded text generation (ITG) loss。这里老哥看了一下,和 BLIP2 不能说完全不同,只能说一毛一样。注意这里的对比损失(图中最右边),mask 的部分其实是为了 q 和 t 不相互干扰,因为这两个在计算 loss 的时候是不能相互看到的,因此是单一模态的 mask

第三阶段就是监督学习了,其实就是 next token prediction,选择微调 mlp 或者 mlp 和 QLLaMA,微调数据集一共 4M 左右

5. 多模态的 benchmark

这里有一些 zero-shot 的分类检索等指标,测试 InternViT-6B 的,这里就不介绍了,直接看多模态的 benchmark。这里其实有点误区,直接看结果吧。PT 和 FT 的数据量和 train params 感觉介绍的不是很清晰,再补充材料 A.4. Data Preparation for Pre-training 有相关介绍,有兴趣的同学可以看一下

InternVL-1.5

论文开始就提到了本文的一个核心点:赶超商业模型。然后贴出了与一些商业模型的对比

这里的指标有点高,AI2D,TextVQA,ChartQA,DocVQA 这些,我记得不应该有这么高

本文的一大改进有三点:(1)更强的视觉编码器(2)动态分辨率,将输入分为 1-40 个 tiles,每个 tile 448x448,以最高支持 4k 分辨率(3)跟高质量的双语数据。在 18 个 benchmark 中,在 8 个上达到了 SOTA

1. motivation

作者提出了当前模型的一些问题:(1)parameter scale。现在的模型小于 100B(2)图像分辨率受限(3)多语能力。最终作者也是从这三个角度去展开的。提出了 InternVL-1.5,针对上面的问题,作者提出了以下改进

  1. 通过持续学习,强化了 InternViT-6B,并且可以适用于各种 LLM 中
  2. 通过动态分辨率,讲输入图像切成 1-40 个 tiles,每个 tile 448x448,最高支持 4k 分辨率
  3. 通过引入高质量的双语数据,提升了多语能力

2. 模型架构

模型架构如下,在当时看来也没啥新鲜的地方,基本上大家都采用这种结构,ViT + MLP + LLM,这里用了 pixel shuffle 来降低 token 数量。视觉编码器用的是 InternViT-6B,语言模型用的 InternLM2-20B

训练的时候,对于每个输入,动态处理模块可以根据其分辨率和长宽比,最多可以分为 1-12 tiles,每个 tile 448x448,推理的时候最多可以切成 40 tiles(这块有点吹牛逼了,推理的视觉 tokens 多的话,模型效果未必好,特别是在 general 的数据上,不过作为 paper claim 的点也没问题),通过 pixel shuffle,每个 tile 表示 256 个 tokens

下面开始依次介绍每一个模块

2.1 视觉编码器

这里作者提到了一个 continuous training 的概念,简单来讲就是在 InternViT-6B 的基础上继续训练,一共有两个新的 ViT 版本

  1. InternViT-6B-448px-V1.2:作者发现倒数第四层的特征表现最佳,因此抛弃了最后三层,将输入分辨率从 224 提升到 448,并与 Nous-Hermes-2-Yi-34B 模型进行对齐,同时训练 vision 和 mlp 部分,来提升整体性能
  2. InternViT-6B-448px-V1.5: 这一部分是从 V1.2 继续训练而来,这里应该和 v1.2 差不多,有两个变化,一个是输入 tiles 从 1 改为 1-12,另一个是增大了训练数据集

这里作者还强调说,虽然训练 vision 的时候用的是 Nous-Hermes-2-Yi-34B,但是转移到 InternLM2-20B 上面的时候,效果也很好。

2.2 动态分辨率处理模块

这里将输入图片处理成 1-12 tiles,也就是说一共 35 中可能的组合,例如 {1:1, 1:2, 2:1, 3:1, …, 2:6},如果输入图像是 800x1200,那么先 resize 为 896x1344,然后切成 2x3 的 tile grid。训练的时候切片为 1-12 tiles,推理的时候最多可以切成 40 tiles,也就是说 token 一共从 256 最多到 10496。可以从下图看到,这里的切片,其实是直接 resize 的,不是 padding,这样可能会造成一些文本等的畸变?

2.3 训练数据集

预训练数据集:captioning 包含 53.9%,Detection 和 Grounding 包含 5.2%,OCR 包含 32%,Smaller ocr 包含 8.9%

微调数据集如下:为了增强中文能力,作者还用了一个翻译的 prompt 来生成中文数据

文中好像没有澄清定量的数据,只有占比

3. 实验结果

下面是 18 个 benchmark 的结果,InternVL-1.5 在 8 个上达到了 SOTA

此外作者还把分辨率进行了增大,固定 tiles,然后展示了相应的结果,但是长宽好像不知道?如果只是说 tiles 增大,但是长宽比也变化的话,可能不是很公平,这里论文我没注意细节

此外作者展示了一些定性结果,这里面有一个需要注意的,就是 object localization,因为这里并不是真正的定位,而且单纯的看图说话,没有 bounding box 的信息

InternVL-2

这个版本的模型和 InternVL-1.5 差不多,主要是 scale up 了模型和多模态 task

模型层面,从 1B 的端侧模型到 108B 的模型都有

1. motivation

一共有三个主要的设计:(1)progressive 的训练,从小的 llm 到大的 llm(2)多模态输入,接受文本,图片,视频和医学数据(3)多任务的输出,包含文本,图像,bounding box 等等,这里不是统一的建模,而是利用了其他任务的 decoder

2. 模型训练

下面是一些具体的训练细节

3. 实验结果

实验结果如下

InternVL-2.5

这篇文章是基于 InternVL-2 的改进,重点在训练测试以及数据质量上。根据原文:“systematically exploring the performance trends in vision encoders, language models, dataset sizes, and testtime configurations.”

下面是作者在 introduction 部分的指标对比,InternVL-2.5 性能也是遥遥领先

1. motivation

这里作者的主要动机是:系统探索了各种因素对 MLLM 的影响,包括(1)视觉编码器(2)语言模型(3)数据集规模(4)测试时间配置。以验证 scaling 和 performance 之间的关系

一些有趣的发现

  1. 当 MLLM 增大的时候,更大的 vision encoder 对训练数据的依赖更低
  2. 数据质量。Internvl-2.5 的数据规模是 InternVL-2 的 2 倍,但是经过了严格的过滤之后,在各种 benchmark 上有明显的提升
  3. Test-time scaling。这里其实更多的是让模型进行 COT 推理或者投票,来提升模型的性能,例如 MMMU 上使用 COT 性能提升了 3.7 个点

2. 模型架构

模型架构上和 InternVL-2 一样,这里不做介绍了,看下图

视觉编码器部分:还是和以前一样,分为两个尺寸,InternViT-6B 和 InternViT-300M,具体的细节如下,对于 6B 的模型,加了 QK-Norm 和 RMSNorm,V1.0 和 V1.2 的版本是在固定分辨率 448px 下训练的,而 V1.5 和 V2.0 则是动态分辨率,并且在 V1.5 版本,将 48 层的最后三层删掉了,保留了 45 层,用 NTP 训练的增强特征提取能力。对于 300M 的模型,从 CLIP-ViT-Large-336px 中初始化得到,并且少了 QK-Norm 和 RMSNorm,然后蒸馏 6B 模型,蒸馏结束之后用 NTP loss 进行训练。在本文引入了 v2.5 的模型版本 InternViT-300M-448px-V2.5

最后是一个整体的模型选择和参数的对比

3. 分辨率设置

这里的动态分辨率和 InternVL-2 是一样的,大概是有一些预定义的 tiles 组合,例如 {1:1, 1:2, 2:1, 3:1, …},首先计算当前图片的长宽比,然后从组合里面选取一个绝对值最接近的组合,然后 resize 成对应的 tiles grid,接下来就是切片等操作了。此外只要切片数量大于 1,就有一个 thumbnail 的图,否则没有

不同数据类型的数据格式。(a) 对于单图像数据集,分配给单个图像的最大块数 nmax,以确保输入的最大分辨率。(b) 对于多图像数据集,总块数 nmax 在样本中的所有图像之间按比例分配。© 对于视频数据集,该方法通过设置 nmax = 1 来简化方法,将单个帧调整为固定分辨率 448×448。下面图展示了这一点

4. 训练设置

训练策略分为三阶段,这里直接看图说话

(1)第一步只训练 MLP,这里用的是动态分辨率来训练的,训练的数据如下

(2)1.5 阶段是可选的,用来对 ViT 进行增量学习,只训练 ViT 和 MLP,这里用的训练数据和第一阶段一样
(3)第二阶段,训练 ViT,MLP 和 LLM,三部分学习率一样,用高质量数据

下面是不同尺寸模型的训练细节,上面作者强调说 ViT 的增量训练的过程中,即使在小的 LLM(20B)上训练,也能够很好的迁移到 72B 上(我确实不太明白为什么作者一直强调这个,从 InternVL 之前版本就开始强调了。。。)然后作者说使用的训练 Tokens 比 Qwen2-VL 少很多,Qwen2-VL 用了 1.4T Tokens

5. 一些 trick

(1)随机JPEG压缩。避免训练过程中的过拟合并增强模型在现实世界中的表现,作者采用了 JPEG 压缩。应用了质量等级在75到100之间的随机 JPEG 压缩,模拟互联网来源图像中常见的降级。这种增强提高了模型对噪声和压缩图像的鲁棒性,并通过确保在不同图像质量下的一致性表现来提升用户体验(不清楚具体效果,不做评价)

后面的 trick 之后再看。。。

6. 数据处理

数据配置

数据打包:打包的这个策略可以提升 GPU 利用率,首先随机采样一个样本 A(文本 1500,图片 2),然后从缓冲区查看是否有样本能和 A 拼到一起,比如找到一个 B,拼起来之后文本和 tile 总数符合要求,就将他们打包成一个复合样本,但是注意,AB 只能看到各自的文本和图片,不能交叉看到。同时维护一个缓冲区,存储未被打包的样本

数据过滤:作者发现在及时很少的噪声数据,也会对 LLM 有较大的影响(例如重复数据 / 错误数据),作者将重复数据作为最严重的问题之一。

  1. 文本过滤:(1) 基于LLM的质量评分:将数据集分类为不同的领域(例如,学科、编程、数学、通用)。使用预训练的 LLM 和领域特定的提示为每个样本分配一个范围为0到10的质量评分。低于指定阈值(例如,7)的样本将被移除,以确保数据质量。 (2) 重复检测:使用LLM结合专门的提示来识别重复模式。这些样本随后经过人工审核,得分低于阈值(例如,3)的样本将被移除,以维护数据质量。 (3) 启发式规则过滤:用特定规则,例如过滤掉长度异常的句子、过长的一系列零、重复行过多的文本等,以识别数据中的异常。尽管这种方法偶尔会产生误报,但它提高了异常样本的检测率。所有被标记的样本在最终移除之前都会经过人工审核。
  2. 多模态数据过滤:(1) 重复检测:排除了高质量的学术数据集,并使用特定的提示来识别剩余数据中的重复模式。这些样本在经过与文本数据相同的手动审查过程后被移除。 (2) 启发式规则过滤:应用类似的启发式规则,然后进行手动验证以确保数据集的完整性。

InternVL-3

在 abstract 部分,作者就强调了训练采用的是 native multimodal pre-training 方式,和之前先训练 vision encoder 再和 LLM 对齐的方式不一样,也是本文最大的一个改进了,下面是一个整体的结果表

1. motivation

在 introduction 部分,作者指出不用 native 训练的话需要用特定的辅助数据,例如 OCR 来将不同模态进行对齐,预训练过程中需要冻结模型和多阶段训练等,比较复杂。与其先对仅文本的大型语言模型进行预训练,然后通过多模态对齐进行调整以支持视觉处理,不如让 InternVL3 在预训练阶段共同接触仅文本语料库和多样的多模态数据集,从而学习多模态能力。

然后文中简述了一下其他贡献:variable visual position encoding 来适配长文,将 SFT 和 MPO 融合以及 test-time scaling,以及优化的 infrastructure 等等,这里作者展示了各种方法在 OpenCompass 上的结果

2. 模型架构

下面展示了整体的模型架构

这边感觉总有点扯,作者虽然说从 native 来训练,但是实际上作者为了加速训练,vit 和 llm 部分还是从预训练的初始化得到,不过 llm 用的是 base model,没有用 instruction-tuned 的模型,连接层用的 2 层 mlp,mlp 是随机初始化的。这里作者还提到了 pixel unshuffle 来提升高分辨率图像的处理能力

variable visual position encoding:

InternVL-3.5

这篇文章是基于 InternVL-3 的改进,提出了 Cascade Reinforcement Learning,包括 offline RL 和 online RL 来提升多模态推理能力。此外提出了 Visual Resolution Router 来动态调整视觉 tokens 来提升准确率。这个 trick 结合 Decoupled Vision-Language Deployment 将视觉语言模型分离,在不同 GPU 上运行,提升了推理速度。定量方面,提升了 16% 的性能提升和 4.05 的推理倍速。此外作者还强调了 GUI 的交互和 Embodied 策略(这个在 InternVL-3 其实就有提到过)

1. motivation

这里也没有什么特别的 motivation,主要就是提出的 Cascade RL framework:offline RL 和 online RL 来提升多模态推理能力,此外提出了 Visual Resolution Router (ViR) and Decoupled Vision-Language Deployment (DvD) 进行快速的推理。前者可以选择最合适的 visual tokens,后者在部署的时候将视觉和语言模型分离,在不同的 GPU 上运行,提升推理速度,下面是各种方法的性能图对比

总结一下,InternVL-3.5 提出了 Dense 和 MoE 的版本(在此之前没有 MoE 版本),提出了一系列的策略(如上)来提升模型性能和推理速度等,最后就是模型整体性能达到了一个很高的标准

2. 模型架构

整体分为 InternVL-3.5 和 InternVL-3.5-flash,架构和初代的 InternVL-1.5 版本没什么本质的区别,其中 InternVL-3.5-flash 加了一个 Visual Resolution Router (ViR) 模块,原来的 InternVL-3.5 中使用的是 Pixel Unshuffle 来处理高分辨率图像,降低 token 为原来的 1/4,这里的 ViR 可以进一步降低到原来的 1/16

具体的每个细节如下

Seed1.5-VL

1. 基本介绍

先是 Abstract 部分可以获得的信息:

Seed1.5-VL,这是一款多模态基础模型,专为促进通用的视觉-语言理解与推理而设计。其核心结构包括:

随后在 Introduction 部分,作者介绍了当前 VLM 的一些不足,同时对 Seed1.5-VL 的训练和评测进行了介绍:

整体上来讲,感觉 Seed1.5-VL 沿用了其他厂的很多技术吧,同时工程量也巨大,VLMs 哪里能力差,就用数据堆哪里

2. 模型架构

架构整体上和 Qwen-VL 系列很像,也是现在比较流行的 NaViT + MLP + LLM 结构,ViT 都用 Native 的形式,MLP 是两层,LLM 依然是 MoE 架构

2.1 视觉编码器

当前 VLMs 的视觉编码器(如 Qwen2-VL 和 InternVL-2.5)在处理不同分辨率(动态分辨率)输入时存在局限性:现有视觉编码器大多基于固定分辨率架构(例如,1D flatten位置嵌入),当输入图片或视频的分辨率变化时,必须对位置编码进行调整(如从1D转为2D RoPE,或插值适应不同分辨率),这种调整容易丢失细节,无法完全保留原始视觉内容的精度,导致模型在处理动态或复杂视觉任务时性能受限(感觉这个问题只针对 InternVL 的视觉编码器?Qwen2-VL好像已经是 NaViT 了

解决方案:

视觉编码器架构

处理流程:首先将输入图像双线性插值到 $28\times 28$ 的分辨率,接着,将缩放后的图像划分为 $4\times4$ 的非重叠 patch,并将每个 patch 展平成向量,随后通过线性投影(patch embedding层)将其映射到高维 embedding 空间。对多图输入的处理,将多张图片的 patch 序列拼接为一个长序列,并在自注意力计算中应用 attention mask,确保每张图片内的 tokens 仅关注自身内容。Transformer 编码器对拼接序列进行上下文编码后,对每张图片的输出 patch embedding 采用 $2\times2$ 平均池化,减少空间维度。池化后的特征再输入 MLP 适配层进行维度映射,最终与语言模型(LLM)结合,完成视觉和语言的多模态融合与理解。

# 输入:多张图片 images,形状 (batch_size, H, W, C)
images_resized = bilinear_interpolate(images, size=(28, 28))
patches = split_into_patches(images_resized, patch_size=(14, 14))

# Patch embedding
tokens = linear_patch_embedding(patches)
all_tokens = concatenate(tokens)  # 形状 (total_patches, embed_dim)
attention_mask = create_attention_mask(tokens)

# Transformer编码
encoded_tokens = transformer(all_tokens, mask=attention_mask)
pooled_tokens = average_pool(encoded_tokens, kernel_size=2)

# MLP适配 + 输入LLM
adapted_tokens = MLP_adaptor(pooled_tokens)
final_tokens = adapted_tokens

视觉编码器训练流程

训练策略:

训练目标:训练分为三个阶段

视频处理

采用 Dynamic Frame-Resolution Sampling strategy,视频被处理成图像帧,采样规则如下:

为了显式标记每帧在视频中的时间位置,在每帧前添加时间戳token(例如[1.5 second])。此外,引入了空间维度采样和回退机制,简明规则如下:

3. 预训练

3.1 训练数据

Seed1.5-VL 的预训练预料包含 3T tokens,这些数据被归类为不同的类别

3.1.1 Generic Image-Text Pairs & Knowledge Data

作者首先提出了数据噪声问题,并设计了一套数据筛选与重采样流程:

在收集了海量的Web图文对数据后,作者发现存在两大问题:

数据不平衡处理:使用BioTrove大规模物种分类数据集(含1.6亿张图片,覆盖36万+物种),团队设计了三种采样策略进行对比实验:

随后在 Balanced10k (常见物种)与 Rare2k (稀有物种)测试集上评估,实验结果如下:随机采样在稀有物种识别上表现不佳,Max1k-46M 显著提升稀有物种识别能力,Max100-15M 虽进一步提升稀有识别,但对常见物种效果下降

为解决视觉知识学习中的类别不平衡问题,Seed1.5-VL 采用了:

这种预筛选+语义标注+稀有采样增强的组合方法,提升了模型对稀有视觉知识(如稀有物种、特定品牌)的学习能力,同时保持了对常见概念的泛化能力,为多模态预训练打下坚实基础。

3.1.2 3.1.2 Optical Character Recognition (OCR)

3. 实验结果

GPT-1

回答你的问题:GPT-1 在输入时已经包含了多个答案,那么如何计算每个答案的概率?

是的,在 GPT-1 的方法中,每个候选答案都是作为完整输入的一部分,并且 Transformer 处理它们时,模型已经看到了答案。但计算最终概率时,GPT-1 不会直接基于整个句子计算,而是计算答案部分(tokens)的条件概率,即计算 答案给定上下文的 log-likelihood。

  1. GPT-1 计算多个候选答案的概率的方法

假设我们有一个问答任务:
• 上下文(context document):

太阳是太阳系中最大的一颗天体。

•	问题:

太阳系中最大的天体是什么?

•	候选答案:
•	(a) “太阳”
•	(b) “地球”

(1) 组合输入

按照 GPT-1 文章中的方法,我们会为每个答案构造一个完整的输入:

输入 1: [太阳是太阳系中最大的一颗天体; 太阳系中最大的天体是什么?; $; 太阳]
输入 2: [太阳是太阳系中最大的一颗天体; 太阳系中最大的天体是什么?; $; 地球]

然后,Transformer 处理整个输入序列,但概率计算只针对答案部分。

(2) Transformer 计算的隐藏状态

在 Transformer 计算时:
• 整个输入都进入 Transformer,并且所有 tokens 都会有一个隐藏状态向量 H_t。
• 自注意力(Self-Attention) 允许 Transformer 看到完整的输入,包括候选答案。
• 但 概率计算时,我们只关心答案 token 的 log-likelihood。

例如,Transformer 计算每个 token 的条件概率:
$$
P(w_t | w_1, w_2, …, w_{t-1})
$$

对于 输入 1(候选答案是“太阳”):
$$
P(\text{“太”} | \text{前面所有 token})
$$
$$
P(\text{“阳”} | \text{前面所有 token + “太”})
$$
最终答案的 log-likelihood:
$$
\log P(“太阳”) = \log P(\text{“太”}) + \log P(\text{“阳”})
$$

对于 输入 2(候选答案是“地球”):
$$
P(\text{“地”} | \text{前面所有 token})
$$
$$
P(\text{“球”} | \text{前面所有 token + “地”})
$$
最终答案的 log-likelihood:
$$
\log P(“地球”) = \log P(\text{“地”}) + \log P(\text{“球”})
$$

  1. 为什么 GPT-1 要这么计算?

因为 GPT-1 是 自回归语言模型(Autoregressive Model),它只能从左到右计算 token 的生成概率。换句话说:
1. 虽然 Transformer 可以看到整个输入,但它是基于条件概率逐步预测 token 的。
2. GPT-1 计算每个 token 生成的 log-likelihood,并对整个答案部分求和,以衡量答案的合理性。
3. 最终选择 log-likelihood 最大的答案作为模型的预测结果。

  1. softmax 归一化

为了做最终决策,模型会对多个候选答案的 log-likelihood 进行 softmax 归一化:
$$
P(a_k) = \frac{e^{\log P(a_k)}}{\sum_{j} e^{\log P(a_j)}}
$$
应用到具体例子:
$$
log P(“太阳”) = -0.5
$$
$$
log P(“地球”) = -1.8
$$

计算 softmax:
$$
P(“太阳”) = \frac{e{-0.5}}{e{-0.5} + e^{-1.8}} \approx 0.82
$$
$$
P(“地球”) = \frac{e{-1.8}}{e{-0.5} + e^{-1.8}} \approx 0.18
$$
最终选择:

模型预测答案 = “太阳”(因为它的概率最高)

  1. 关键点总结
    1. Transformer 看到整个输入,包括候选答案,但概率计算时只针对 答案部分。
    2. 模型计算答案部分的 log-likelihood,即 逐 token 计算生成概率,并求和。
    3. 对多个候选答案的 log-likelihood 进行 softmax 归一化,选取概率最高的答案。
    4. GPT-1 不能直接做分类,它只能通过语言模型的方式计算答案在上下文中的合理性。

GPT-2

这部分讲的是 GPT-2 在初始化权重时的特殊处理方法,主要是为了缓解深层神经网络的梯度消失或梯度爆炸问题。我来一步步解释。

  1. 为什么需要特殊的初始化策略?

(1) 残差连接(Residual Connection)的问题

在深度神经网络(尤其是 Transformer)中,残差连接(Residual Connection) 被广泛使用:
x_{l+1} = x_l + f(W_l, x_l)
其中:
• x_l 是第 l 层的输入。
• f(W_l, x_l) 是该层的变换,例如自注意力(Self-Attention)或前馈神经网络(Feedforward Network)。
• 残差连接的作用:允许梯度直接在层与层之间传播,帮助训练非常深的模型。

(2) 残差路径(Residual Path)累积效应

但当层数增加时,残差路径会导致梯度逐层累积,可能导致梯度爆炸或梯度消失:
• 梯度爆炸(Gradient Explosion):
• 假设残差层的激活值是 均值为 0,方差为 \sigma^2 的随机变量。
• 在 N 层网络中,最终的激活值可能会有 N \cdot \sigma^2 的增长,导致数值过大,使得训练变得不稳定。
• 梯度消失(Gradient Vanishing):
• 如果层的权重初始化过小,累积的梯度可能会指数级衰减,使得深层网络的训练变得困难。

  1. GPT-2 的初始化策略

为了减少这些问题,GPT-2 采用了 权重缩放(Weight Scaling)策略:

W_{\text{init}} = W_{\text{raw}} \cdot \frac{1}{\sqrt{N}}

(1) 公式含义
• W_{\text{init}}:实际用于模型的权重。
• W_{\text{raw}}:未经缩放的随机初始化权重(例如从正态分布抽样)。
• N:残差层(Residual Layers)的总数。
• 缩放因子 \frac{1}{\sqrt{N}}:
• 让每层的贡献在整个网络中不会随着层数增加而过大。
• 使得整个网络的方差保持稳定,不会随着深度增加而膨胀。

(2) 为什么要用 \frac{1}{\sqrt{N}} 进行缩放?
• 直观上,它保证了所有残差层的累积贡献不会随着层数增加而过大。
• 这和 Xavier Initialization(Glorot 2010) 类似,Xavier 初始化的思想是:
\text{Var}(W) = \frac{1}{\text{fan-in}}
• 其中 fan-in 是输入单元的数量。
• 这样可以保证前向传播和反向传播的信号保持在合理的范围内。

GPT-2 采用了类似的思想,但特别针对 残差路径 进行缩放,以适应 Transformer 架构的深度问题。

  1. 直观理解

可以这样想:
• 没有缩放时(普通初始化):
• 每一层残差连接都会增加一个数值,随着模型越来越深,数值可能会越来越大(梯度爆炸)或越来越小(梯度消失)。
• 加了 \frac{1}{\sqrt{N}} 缩放后:
• 每一层的贡献会变小,总体上随着深度增加仍然保持稳定。
• 这样就能训练更深的 Transformer 模型,而不会因梯度问题影响训练效果。

  1. 关键总结
    • 问题:深层残差网络的梯度可能随着层数增加而累积,导致训练不稳定(梯度爆炸或梯度消失)。
    • GPT-2 解决方案:
    • 初始化时,将残差层的权重乘以 \frac{1}{\sqrt{N}},让每层的贡献均匀分布,防止梯度累积过大。
    • 效果:
    • 使得 GPT-2 能够稳定训练 48 层深的 Transformer 结构,并保持梯度稳定。

这是一种非常重要的优化技巧,在更大规模的模型(如 GPT-3)中仍然被采用!

Deepseek v1

1. 模型结构

Deepseek LLM 第一版本。

目前,ChatGPT、Claude、Bard 等封闭模型引领了 LLM 的潮流,但同时也激发了开源 LLM 的快速发展,如 LLaMA 系列。LLaMA系列凭借高效架构和优越性能,成为开源社区的重要基准。然而,开源社区在固定规模(7B、13B、34B、70B)模型上投入较多,却相对忽视了LLM的扩展规律(Scaling Laws)研究。

重点对于模型训练和 scaling law 的研究。针对 7B 与 67B 规模的 LLM,探索了批量大小、学习率的扩展趋势,并分析了数据与模型扩展的最优策略。研究表明,不同数据集会影响扩展规律,因此在不同数据上推广扩展规律需谨慎。

在实践中,构建了全新的开源大模型,预训练数据涵盖2万亿 tokens(主要为中文与英文)。模型架构沿用LLaMA,但调整了学习率策略,以支持持续训练。微调阶段,收集了 100万+ 实例进行 SFT,并采用直接偏好优化(DPO)提升对话质量。

模型各项参数对比

参数 7B 模型 67B 模型
层数(nlayers) 30 层 95 层
总参数量 7B(70 亿) 67B(670 亿)
隐藏层维度(dmodel) 4096 8192
序列长度(Sequence Length) 2304 4608
上下文长度(Context Length) 4096 4096
训练 Token 量 2.0T 2.0T
学习率 4.2e-4 3.2e-4
主要优化点 采用标准 Transformer 结构,适用于计算资源有限的环境 通过增加层数(depth)扩展模型,而非增加 FFN 宽度,提高性能
归一化方法 Pre-Norm 结构,采用 RMSNorm Pre-Norm 结构,采用 RMSNorm
激活函数 SwiGLU SwiGLU
前馈网络(FFN) 8/3 dmodel 8/3 dmodel
位置编码 旋转位置编码(RoPE) 旋转位置编码(RoPE)
注意力机制 标准 MHA Grouped-Query Attention(GQA)
查询头数(nheads) 32 64
键/值头数(nkv_heads) 32 8
主要优化点 采用标准 MHA,不使用 GQA,计算完整性较高 采用 GQA,减少 KV 计算量,提高推理速度,减少 KV Cache 存储需求

什么是 Transformer 的“深度”和“宽度”?

  1. Transformer 的“深度”指的是模型中堆叠的 Transformer 层(Layer)的数量。
  2. “宽度”主要有两个维度:
    • 隐藏层维度($d_{model}$):每个 token 被表示成的向量长度,也就是每层的特征维度。比如 $d_{model} = 4096$ 表示每个 token 是用一个 4096 维的向量表示的。
    • 前馈网络中间层维度(FFN 中间宽度):每层 Transformer 中 FFN(前馈神经网络)模块的隐藏层维度,通常是 4*$d_{model}$ 或更大。

2. 训练策略

AdamW 优化器: 配置如下β₁ = 0.9,β₂ = 0.95,权重衰减(Weight Decay)= 0.1,初始权重使用标准差为 0.006 的正态分布初始化

学习率调度器: Multi-Step Scheduler,相比常见的 Cosine 调度器,DeepSeek 使用了 多阶段学习率调度(multi-step):Warmup 前 2000 步提升至最大学习率,训练 80% token 后下降为 31.6%,训练 90% token 后降为 10%,保持到训练结束,这种调度方式便于继续训练(continual training),相比 cosine 更灵活。

梯度裁剪: 为了避免梯度爆炸,设置裁剪阈值为:Gradient Clipping = 1.0

随着模型规模变大,Batch Size 增加,学习率相应略微降低。

模型规模 Batch Size 学习率
DeepSeek 7B 2304 4.2e-4
DeepSeek 67B 4608 3.2e-4

DeepSeek 采用了 AdamW + Multi-Step LR + Gradient Clipping 的组合,兼顾了训练稳定性、性能提升和后期可持续训练需求,是大模型训练中的一个高质量参考方案。

3. Scaling Laws

Scaling Law(扩展规律)是指:在固定计算预算下,模型性能如何随着参数量(模型规模)和训练数据量的变化而变化。简单来说,就是在给定资源下,“是加大模型还是多喂数据更有效”?

  1. 超参数扩展规律(Hyperparameter Scaling)

通过拟合得出经验公式(Compute Budget = C):

$$
\text{Batch Size}{\text{opt}} = 0.292 \cdot C^{0.3271} \\
\text{Learning Rate}{\text{opt}} = 0.3118 \cdot C^{-0.125}
$$

这为训练更大模型时的参数设置提供了数学指导。

  1. 模型规模与数据量的最优分配

$$
M_{\text{opt}} = 0.1715 \cdot C^{0.5243} \
D_{\text{opt}} = 5.8316 \cdot C^{0.4757}
$$

也就是说,在计算量提升时,更多资源应投入在模型扩展上,而不是单纯扩充数据量。

  1. 不同数据质量下的扩展策略变化

这个结论也可以用于反向评估数据质量:如果你在一个数据集上训练时发现更倾向扩大模型,可能说明它质量不错。

验证与预测

Deepseek v2

核心是 Multi-head Latent Attention (MLA) 和 DeepSeekMoE

1. 模型结构

与 v1 版本相比,v2 版本在模型结构上进行了优化,采用了 MoE 结构,并引入了 sparse 稀疏激活技术。

要理解什么是 MoE 模型,首先需要了解什么是 Dense 模型。“Dense 模型”指的是:每一个 token 在每一层都会激活模型中所有的参数进行计算,常见的 GPT-3、LLaMA、ChatGPT 等模型大多属于 dense 模型。

比如一个 Dense 模型总参数量是 70B,那每个 token 的前向计算也用掉这 70B 参数——每层都不落下,每个专家都要参与。

DeepSeek-V2 是 MoE(Mixture-of-Experts)稀疏激活模型,总参数量是 236B,但每次只用其中的一小部分(21B)来进行计算。

Dense vs. MoE 模型对比总结:

Dense 模型(如 DeepSeek 67B) MoE 模型(如 DeepSeek-V2)
总参数量 67B(= 每 token 实际使用) 236B(每 token 实际只用 21B)
每 token 计算量 高(全部参数参与) 低(只激活部分专家)
推理速度 慢,吞吐小 快,吞吐大(提升 5.76x)
KV 缓存 小(减少 93.3%)
训练成本 低(节省 42.5%)
性能 同样甚至更高性能

DeepSeek-V2 的构建过程可分为预训练、监督微调和强化学习三大阶段。首先,模型基于一个规模高达 8.1 万亿 token 的高质量多源语料库进行预训练。与前代模型 DeepSeek 67B 相比,该语料在中文数据的覆盖上显著增强,同时整体数据质量也得到大幅提升。在完成基础预训练后,模型进一步引入了 150 万条多领域对话数据,包括数学、编程、写作、推理与安全等内容,开展监督微调(Supervised Fine-Tuning),形成对话能力较强的 DeepSeek-V2 Chat (SFT) 版本。最后,为了更好地贴合人类偏好,模型采用 GRPO(Group Relative Policy Optimization)强化学习算法进行优化,产出最终版本 DeepSeek-V2 Chat (RL),在多个任务和场景中展现出优异的对齐能力与交互表现。

科研使用:为进一步推动 MLA 与 DeepSeekMoE 架构的研究与应用,团队还同步开源了轻量级版本模型 —— DeepSeek-V2-Lite。该模型在设计上同样集成了 MLA 与 DeepSeekMoE 两大核心技术,具备优秀的推理效率和训练性价比。V2-Lite 的总参数规模为 15.7B,但每次仅激活 2.4B 参数进行计算,极大降低了资源门槛,适合更广泛的开源社区用户在研究探索和实际部署中使用。

2. 核心模块设计

2.1 Multi-Head Latent Attention

📌 背景问题:传统 MHA 的 KV 缓存很大

在推理时,传统 MHA 要对每一层、每个 token 保留:
• K_t:Key 向量
• V_t:Value 向量

每个 token 要缓存 K_t 和 V_t,维度是 nₕ × dₕ,再乘以层数 l,非常大。

✅ MLA 的解决方案:低秩联合压缩(Low-Rank Key-Value Joint Compression)

MLA 的核心思想是:

不再分别生成 Key 和 Value,而是先将输入压缩成一个 低维 latent 表示,再从这个 latent 表示中恢复出 Key 和 Value。

🔧 具体步骤如下:

🧮 Step 1:将输入投影成一个压缩向量(latent vector)

c_{KV_t} = W_{D_{KV}} · h_t

•	h_t 是当前 token 的隐藏状态(维度 d)
•	W_{D_{KV}} 是一个降维矩阵(维度 d_c × d)
•	得到一个 压缩向量 c_{KV_t},它的维度是 d_c,远小于原始 nₕ × dₕ

🧮 Step 2:再分别用上采样矩阵恢复 Key 和 Value

k^C_t = W_{U_K} · c_{KV_t}
v^C_t = W_{U_V} · c_{KV_t}

•	W_{U_K} 是用来恢复 Key 的上采样矩阵(维度 nₕ × dₕ × d_c)
•	W_{U_V} 是用来恢复 Value 的上采样矩阵(同理)

注意:这个压缩和恢复操作只在 训练时计算一次,而在 推理时只缓存 c_{KV_t} 这个 latent 向量即可,无需显式存下 K/V。

🚀 优势是什么?

对比项 传统 MHA MLA(Ours)
缓存内容 每 token 缓存 Key + Value(2 × nₕ × dₕ) 每 token 只缓存一个 latent 向量(d_c)
计算量 正常 更省计算(因为共享路径)
推理内存 非常大 极大缩小(减少 93.3%)
性能 高 更高(没有牺牲性能)

📌 总结一句话:

MLA 利用 低秩联合压缩 的方式,把 Key 和 Value 压缩成一个小的 latent 向量,大大减小了 KV 缓存,同时保持甚至提升了性能,是 DeepSeek-V2 提速提效的核心技术之一。

  1. 输入:隐藏状态 $h_t$
    维度为 d,即 Transformer 层的输出。
  2. Step 1:降维
    使用投影矩阵 $W_{D_{KV}}$ 将 $h_t$ 压缩为低维 latent 表示 $c_{KV_t}$。
    输出维度为 $d_c$,大大低于原始的 Key/Value 维度。
  3. Step 2:分别恢复 Key 和 Value
    用 $W_{U_K}$ 将 $c_{KV_t}$ 映射为 Key $k^C_t$。
    用 $W_{U_V}$ 将 $c_{KV_t}$ 映射为 Value $v^C_t$。
    两者维度为 n_h × d_h,用于标准 attention 计算。

伪代码如下

# h_t: hidden state of current token, shape (d,)
# W_D_KV: down-projection matrix, shape (d_c, d)
# W_U_K, W_U_V: up-projection matrices, shape (n_h * d_h, d_c)

# Step 1: Compress
c_KV_t = W_D_KV @ h_t  # shape: (d_c,)

# Step 2: Recover Key and Value
k_C_t = W_U_K @ c_KV_t  # shape: (n_h * d_h,)
v_C_t = W_U_V @ c_KV_t  # shape: (n_h * d_h,)

# Reshape to (n_h, d_h) if needed for multi-head processing
k_C_t = k_C_t.reshape(n_h, d_h)
v_C_t = v_C_t.reshape(n_h, d_h)

你提到的这段内容讲的是 MLA(Multi-head Latent Attention)在训练阶段对 Query 的进一步压缩策略。虽然这个压缩不能减少推理时的 KV cache,但可以 减少训练时的激活内存(activation memory),提升训练效率。下面是逐句解释👇

🧠 原文解析:

Moreover, in order to reduce the activation memory during training…

此外,为了在训练过程中 减少激活内存的使用(也就是每一步中间结果要暂存的内容),

…we also perform low-rank compression for the queries, even if it cannot reduce the KV cache:

我们对 Query 也做了低秩压缩(Low-Rank Compression),即使它并不会减少推理时的 KV 缓存大小。
→ 这个压缩是为了 节省训练资源,而不是推理资源。

c_Q_t = W_DQ · h_t

第一步,把当前 token 的隐藏状态 $h_t$(维度为 $d$)
用一个 下采样矩阵 $W_{DQ}$(维度 $d_c’ × d$) 投影成一个压缩向量 $c_Q_t$。
这个向量的维度是 $d_c’$,远小于原始的 Query 维度。

q_C_t = WUQ · c_Q_t

然后再用一个 上采样矩阵 $W_{UQ}$(维度 $n_h·d_h × d_c’$)
把 $c_Q_t$ 重新映射成原本的 Query 向量 $q_C_t$(维度为 $n_h × d_h$)。

✅ 变量说明:

符号 含义
$h_t$ 当前 token 的隐藏状态,维度为 $d$
$c_Q_t$ Query 的低维 latent 表示,维度为 $d_c’$
$q_C_t$ 恢复后的 Query 向量,维度为 $n_h × d_h$
$W_{DQ}$ 压缩用的下采样矩阵,维度 $d_c’ × d$
$W_{UQ}$ 解压用的上采样矩阵,维度 $n_h·d_h × d_c’$

🔧 总结:为什么这么做?

虽然 Query 的 KV 不需要缓存(因为每次前向都会重新算),但:
• 在训练中,为了反向传播,所有中间变量都要暂存(activation);
• Query 计算过程也会占用显存,特别是在层数、batch、sequence 较大时;
• 所以通过压缩 Query,可以节省很多训练显存,训练更轻量;

✅ 一句话总结:

MLA 对 Query 做了低秩压缩,虽然不影响推理时的缓存,但显著减小了训练时的内存压力,是一种 训练效率优化手段。

能节省多少显存

这是个非常实际的问题。下面我帮你对比一下 MLA 和传统 MHA 在 Query 压缩上的显存节省效果,并给出一个估算过程和总结。

📌 目标:对比训练阶段的 Query 部分激活内存开销

✅ 1. 传统 MHA 的 Query 激活存储成本

对于每一个 token,在每一层都要计算:

q_t = W_Q @ h_t

•	W_Q 的输出是 q_t,维度是:n_h × d_h(比如 128 × 128)
•	所以,每个 token、每一层,需要缓存 n_h × d_h 个浮点数作为中间激活

✅ 2. MLA 中的 Query 压缩策略

MLA 把 Query 的计算改成了两步:

c_Q_t = W_DQ @ h_t # 压缩到低维 latent,维度 d_c’
q_C_t = W_UQ @ c_Q_t # 再上采样回 Query 向量

•	所以在反向传播中,只需要缓存 c_Q_t(维度 d_c’)作为中间激活
•	相比原来的 n_h × d_h,节省了很多内存!

🧮 举个例子计算一下节省比例:

假设:
• n_h = 128(head 数量)
• d_h = 128(每个 head 的维度)
• d_c’ = 1536(MLA 中 Query 压缩的 latent 维度)

🔹 MHA 的激活存储:

128 × 128 = 16,384 float

🔹 MLA 的激活存储:

c_Q_t: 1536 float

🔹 节省比例:

(1 - 1536 / 16384) ≈ 90.6%

✅ 结论总结:

方法 每 token 激活量(float 数) 相对节省
MHA n_h × d_h = 16,384 -
MLA d_c’ = 1536 🟢 节省约 90.6% 激活内存

💡 一句话总结:

MLA 对 Query 的低秩压缩在训练中可 节省约 90% 的 Query 激活内存,尤其在大 batch 和长序列训练时,对显存压力的缓解非常明显。

2.2 DeepSeekMoE

2. 训练策略

Deepseek v3

MoE架构

根据这一段的描述,新的误差方程和之前的误差方程的主要区别在于专家网络的输出和它们之间的相互依赖性。

  1. 新的误差方程:
    • 每个专家必须输出整个输出向量:在新的误差方程中,每个专家网络都必须生成整个输出向量,而不是仅仅生成一个“剩余”部分来弥补其他专家的误差。这样,专家的目标是直接根据输入数据来生成完整的输出。
    • 专家之间的耦合变弱:尽管仍然存在间接的耦合,因为一个专家的权重变化可能会影响 gating 网络的选择(从而改变专家的分配),但这种耦合的影响不会直接改变专家对某个训练样本的误差。这意味着专家之间的影响减少,每个专家更独立地工作。
    • 责任分配:通过 gating 网络的选择机制,系统会根据每个专家的误差表现来动态分配责任。如果某个专家在某个样本上误差较小(比所有专家的加权平均误差要小),它就会获得更多的责任,反之则减少。最终,系统会倾向于让一个专家专门负责每个训练样本。

  2. 之前的误差方程:
    • 线性组合和残差:之前的误差方程要求每个专家的输出要与其他专家的输出共同作用,最终形成一个加权的输出。为了最小化误差,每个专家的目标是抵消其他专家输出中留下的残差。
    • 强耦合:在这种情况下,由于误差的最小化需要多个专家的合作,专家之间有强耦合,即一个专家的输出变化会影响其他专家的误差和权重。这种合作使得每个专家的学习目标和其他专家的输出密切相关,导致每个任务可能需要多个专家共同参与。

主要区别:
• 输出目标的变化:新的误差函数要求每个专家生成完整的输出向量,而不是仅仅修正残差;这使得每个专家的学习目标更加独立。
• 专家之间的耦合度:新误差函数降低了专家之间的直接耦合,专家的学习不再依赖于其他专家的输出。之前的误差函数则导致了强耦合,多个专家需要共同合作解决每个任务。
• 责任分配的机制:在新的误差函数中,责任分配更加动态和独立,专家根据其表现获得不同的责任,而不是依赖于其他专家的输出。之前的误差函数中,专家的责任更依赖于其他专家的协作。

总结:

新的误差函数鼓励每个专家独立地生成完整的输出并竞争任务的责任,而不是像之前那样依赖于其他专家的输出来补偿误差。这样,系统能更有效地分配任务,减少多个专家共同参与每个任务的情况。

误差函数

好的,我们将继续使用 $E_c = \langle | \mathbf{d_c} - \mathbf{o_{c_i}} |^2 \rangle = \sum_i p_{c_i} | \mathbf{d_c} - \mathbf{o_{c_i}} |^2$ 来举第三个例子,并最终通过表格对比三个误差函数。

  1. 最初的误差函数:
    $$
    E_c = | \mathbf{d_c} - \sum_i p_{c_i} \mathbf{o_{c_i}} |^2
    $$

例子:
• 目标输出:$d_c = [0.5, 0.5]$
• 3 个专家:
• 专家 1 输出:$o_{c_1} = [0.3, 0.7]$,贡献比例:$p_{c_1} = 0.2$
• 专家 2 输出:$o_{c_2} = [0.6, 0.4]$,贡献比例:$p_{c_2} = 0.5$
• 专家 3 输出:$o_{c_3} = [0.4, 0.6]$,贡献比例:$p_{c_3} = 0.3$

步骤 1:计算加权输出
• 专家 1 加权输出:
$p_{c_1} \mathbf{o_{c_1}} = 0.2 \times [0.3, 0.7] = [0.06, 0.14]$
• 专家 2 加权输出:
$p_{c_2} \mathbf{o_{c_2}} = 0.5 \times [0.6, 0.4] = [0.3, 0.2]$
• 专家 3 加权输出:
$p_{c_3} \mathbf{o_{c_3}} = 0.3 \times [0.4, 0.6] = [0.12, 0.18]$

步骤 2:计算总加权输出
$$
\sum_i p_{c_i} \mathbf{o_{c_i}} = [0.06, 0.14] + [0.3, 0.2] + [0.12, 0.18] = [0.48, 0.52]
$$

步骤 3:计算误差
$$
E_c = | \mathbf{d_c} - \sum_i p_{c_i} \mathbf{o_{c_i}} |^2 = | [0.5, 0.5] - [0.48, 0.52] |^2 = (0.02)^2 + (-0.02)^2 = 0.0008
$$

  1. 当前的误差函数:

$$
E_c = - \log \left( \sum_i p_{c_i} e^{-\frac{1}{2} | \mathbf{d_c} - \mathbf{o_{c_i}} |^2} \right)
$$

例子:
• 目标输出:$d_c = [0.5, 0.5]$
• 3 个专家:
• 专家 1 输出:$o_{c_1} = [0.3, 0.7]$,贡献比例:$p_{c_1} = 0.2$
• 专家 2 输出:$o_{c_2} = [0.6, 0.4]$,贡献比例:$p_{c_2} = 0.5$
• 专家 3 输出:$o_{c_3} = [0.4, 0.6]$,贡献比例:$p_{c_3} = 0.3$

步骤 1:计算每个专家的误差
• 专家 1 的误差:
$$
| \mathbf{d_c} - \mathbf{o_{c_1}} |^2 = (0.5 - 0.3)^2 + (0.5 - 0.7)^2 = 0.04 + 0.04 = 0.08
$$
$$
e^{-\frac{1}{2} | \mathbf{d_c} - \mathbf{o_{c_1}} |^2} = e^{-\frac{1}{2} \times 0.08} \approx e^{-0.04} \approx 0.961
$$
• 专家 2 的误差:
$$
| \mathbf{d_c} - \mathbf{o_{c_2}} |^2 = (0.5 - 0.6)^2 + (0.5 - 0.4)^2 = 0.01 + 0.01 = 0.02
$$
$$
e^{-\frac{1}{2} | \mathbf{d_c} - \mathbf{o_{c_2}} |^2} = e^{-\frac{1}{2} \times 0.02} \approx e^{-0.01} \approx 0.990
$$
• 专家 3 的误差:
$$
| \mathbf{d_c} - \mathbf{o_{c_3}} |^2 = (0.5 - 0.4)^2 + (0.5 - 0.6)^2 = 0.01 + 0.01 = 0.02
$$
$$
e^{-\frac{1}{2} | \mathbf{d_c} - \mathbf{o_{c_3}} |^2} = e^{-\frac{1}{2} \times 0.02} \approx e^{-0.01} \approx 0.990
$$

步骤 2:计算加权概率
$$
E_c = - \log \left( 0.2 \times 0.961 + 0.5 \times 0.990 + 0.3 \times 0.990 \right)
E_c = - \log \left( 0.1922 + 0.495 + 0.297 \right)
E_c = - \log (0.9842) \approx 0.0163

  1. 新误差函数:

$$
E_c = \langle | \mathbf{d_c} - \mathbf{o_{c_i}} |^2 \rangle = \sum_i p_{c_i} | \mathbf{d_c} - \mathbf{o_{c_i}} |^2
$$

例子:
• 目标输出:$d_c = [0.5, 0.5]$
• 3 个专家:
• 专家 1 输出:$o_{c_1} = [0.3, 0.7]$,贡献比例:$p_{c_1} = 0.2$
• 专家 2 输出:$o_{c_2} = [0.6, 0.4]$,贡献比例:$p_{c_2} = 0.5$
• 专家 3 输出:$o_{c_3} = [0.4, 0.6]$,贡献比例:$p_{c_3} = 0.3$

步骤 1:计算每个专家的误差
• 专家 1 的误差:
$$
| \mathbf{d_c} - \mathbf{o_{c_1}} |^2 = (0.5 - 0.3)^2 + (0.5 - 0.7)^2 = 0.04 + 0.04 = 0.08
$$
• 专家 2 的误差:
$$
| \mathbf{d_c} - \mathbf{o_{c_2}} |^2 = (0.5 - 0.6)^2 + (0.5 - 0.4)^2 = 0.01 + 0.01 = 0.02
$$
• 专家 3 的误差:
$$
| \mathbf{d_c} - \mathbf{o_{c_3}} |^2 = (0.5 - 0.4)^2 + (0.5 - 0.6)^2 = 0.01 + 0.01 = 0.02
$$

步骤 2:加权计算误差
$$
E_c = 0.2 \times 0.08 + 0.5 \times 0.02 + 0.3 \times 0.02
E_c = 0.016 + 0.01 + 0.006 = 0.032
$$

  1. 对比总结:
误差函数 计算方法 最终误差计算
最初的误差函数 加权平均多个专家的输出与目标输出之间的差异,并计算最终输出与目标输出的误差。 计算加权输出与目标输出的平方差,得到误差 0.0008。
当前的误差函数 计算每个专家输出与目标输出之间的误差,使用指数函数对每个专家的贡献进行加权,并通过对数求和得到误差。 通过加权求和计算每个专家贡献,最终得到误差 0.0163。
新误差函数 计算每个专家的输出与目标输出的平方误差,并根据专家的贡献比例加权计算总误差。 计算专家输出与目标输出的平方误差,最终得到误差 0.032。

总结:
• 最初的误差函数 通过多个专家的输出加权平均来计算最终误差,每个专家的目标是修正其他专家的残差。
• 当前的误差函数 使用了指数加权的方式,通过最大化概率来选择合适的专家,较好的专家会被赋予更大的责任。
• 新误差函数 强调了直接计算每个专家的误差,并通过加权来得出最终误差。

VILA

1. 特点

2. 模型结构

vison-linear-llm 结构

3. 训练

VILA2

InternLM

训练分为三个阶段,dataset preparation,model pretraining,alignment,这点好像没有什么特别的

1. 分词器

使用 BPE 算法进行多语训练,token 总数为 65.5K

2. 模型结构

104B 的模型大小,82 层 transformer layers,head 为 80,head 维度 128,即总维度 10240

3. 训练

用了 1.6T 的 token 数据,主要进行中英的训练,其他语言也有,但是很少

训练数据

训练首先进行预训练,随后用 5M 的数据 SFT,接下来 RLHF

InternLM2

读了一下 InternLM2,给我最大的感受就是,论文过于学术,话太多了。。

模型大小有 1.8B,7B 以及 20B。

1. 分词器

采用 cl100k 的前 60004 个 tokens 以及自定义了 32397 个 chinese tokens,此外加了 147 个 spare token 使得总 tokens 数量为 256 的倍数

2. 模型结构

原文中扯了很多,但是好像和 LLaMA 没什么本质区别

3. 训练

采用的 GQA attention,预训练阶段首先用 4k context 训练,接下来转为高质量的 32k 数据

训练数据部分的文章也非常枯燥,建议只看图就可以了,很多细节感觉可以放到补充材料里

预训练数据

总数数据汇总如下

1. Text data

web pages, papers, patents, and books

数据统计如下

数据处理流程如下所示

2. Code data

代码数据的分布如下

3. Long Context Data

LLaMA

1. 分词器

使用 BPE 算法,从 SentencePiece 中实现的,所有的数字都分开成独立的数字形式

2. 模型结构

模型采用 Transformer 结构,区别如下

模型的具体参数如下

3. 训练

仅使用开源数据集,数据组成如下,所有的数据在 tokenize 之后包含大约 1.4T 的 tokens

对于每个子集,列出了采样比例、在 1.4T 令牌上训练时对子集执行的迭代次数和磁盘大小。1T 代币上的预训练运行具有相同的采样比例

LLaMA2

提出了 LLaMA2 和 LLaMA2-Chat,相比于 LLaMA 的改进在于提升训练预料,上下文长度加倍,采用 Grouped-query attention(GQA),发布了 7B,13B,34B 以及 70B 的版本,具体如下

1. 分词器

使用与 LLaMA 相同的 tokenzier,即由 SentencePiece 实现的 BPE 算法,一共 32000 个 token

2. 模型结构

与 LLaMA 基本相同,该用了 GQA 来替换 Attention

3. 训练

预训练数据

没怎么透露,可能只是使用了更多的高质量数据

微调数据

Quality is all you need.

使用数以万计的高质量标注数据足矣,因此这里使用了 27540 个标注数据

LLaMA3

LLaMA3 支持多语言,coding,reasoning,tool usage,最大的 dense model 为 405B,支持 128K 的上下文 token

文中指出高质量的基座模型有三个关键点: data, scale, managing complexity

1. 分词器

2. 模型结构

模型结构与 LLaMA 和 LLaMA2 相比没有很大的变化,主要是数据质量和 training scale 的改变

3. 训练

训练的整体 pipeline 如下所示

Pre-Training Data

预训练数据大多来自 web,过滤分为下面步骤: PPI and safety filtering; Text extraction and cleaning; De-duplication; Model-based quality filtering; Code and reasoning data; Multilingual data

Data mix: 知识分类,小模型预训练,数据汇总(50%的 general knowledge,25%的 mathematical 和 reasoning tokens,17% code tokens,8%多语 tokens)

Scaling Laws

千问是阿里的开源大模型系列,目前基本处于国内开源模型中的第一梯队,下面对 Qwen 系列进行讨论

Qwen

这篇技术报告一共提出了以下几个 Qwen 系列模型: Qwen,Qwen-Chat,Code-Qwen,Code-Qwen-Chat,Math-Qwen-Chat,其中 Chat 模型是经过 RLHF 得到的

1. 分词器

用 BPE 算法,对中英文以及其他语言进行训练,总共的词表为 152K,这里 tokenzier 的压缩率比其他模型的高,意味着更少的 token 可以传递更多的信息

2. 模型结构

模型结构和 LLaMA 很像,但是有以下区别

QwenLM 预训练系列如下

3. 训练

总 tokens: 1.8B,7B 和 14B 分别为 2.2T,2.4T 和 3.0T

训练数据包含公共网络文档,百科全书,书记,代码等等,训练数据是多语种的,中英文占很大比重

使用自回归的方式训练 QwenLM,上下文长度为 2048,在 attention 模块中使用 Flash Attention,使用 AdamW 优化器,$\beta_{1}=0.9$ $\beta_{2}=0.95$ $\epsilon=10^{-8}$,使用余弦退火,最小学习率是峰值的 0.1,使用 BF16 进行训练

在 Attention 的时候使用 NTK 插帧技术对 RoPE 进行插值

Qwen2

本篇技术报告一共发布了四个 dense models,Qwen-0.5B,Qwen-1.5B,Qwen-7B,Qwen-72B,此外还有 57B 的 MoE 模型,所有的模型都在 7T tokens 上训练的

1. 分词器

和 Qwen 相同,包含 151643 个 regular tokens 以及 3 个 control tokens

2. 模型结构

Qwen2 Dense Model

Qwen2 Mixture-of-Experts Model

模型的详细参数如下,其中 Qwen2-57B-A14B 是从 Qwen2-7B 继承而来

3. 训练

总 tokens: 0.5B,1.5B,7B,72B 和 57B-A14B 分别为 12T,7T,7T,7T,4.5T

Qwen2 专注于数据集的 refine 以及 context lengths 的处理

训练数据集

long-context 的训练: 使用 YARN 和 DCA 的训练策略,可以让模型可处理的 token 拓展到 131072 个

Qwen-VL

1. 分词器

添加的特殊 token 一共有 6 个 <img> </img> <box> </box> <ref> </ref>

2. 模型结构

3. 训练

总图文: 一二三阶段分别为 1.4b, 78M, 350K 图文

训练分三阶段,其中经过 multi-task pre-training 的是 Qwen-VL 模型,经过 Supervised Fine-tuning 的是 Qwen-VL-Chat 模型

训练的整个流程如下所示

Qwen2-VL

Qwen2-VL 引入了 Naive Dynamic Resolution 的机制,确保模型可以动态的处理变化的分辨率,并且集成了 Scaling laws

Qwen2-VL 可以理解图片和视频

1. 分词器

2. 模型结构

处理图片和视频均采用 675M 的 ViT,LLM 采用 Qwen2

下面分别介绍这三个模块

Naive Dynamic Resolution

这个模块参考的是 Google DeepMind 的一个工作,叫做 NaViT,这个工作主要是为了解决 ViT 在推理时不能接受不同分辨率输入的问题

其实本质上来讲,ViT 可以接受任意分辨率的输入,但是因为位置编码的问题,所以不能直接使用,需要进行一些处理

  1. ViT理论上可以推理任意分辨率吗?理论上,ViT 的 patch embedding 和主干 Transformer 都不限制输入 patch 数,只要显存够,序列多长都可以。但是ViT 的**位置编码(positional embedding)**通常是训练时以固定尺寸(比如 14×14=196 个 patch)初始化和学习的。推理时,如果输入 patch 数量变了,就要插值 position embedding(比如把 14×14 插值成 16×16)。插值虽然能凑用,但会带来“语义漂移”,模型泛化能力会变差,尤其是分辨率跨度较大、长宽比变化时,模型性能大概率明显下降。

  2. ViT为何不能直接支持原生分辨率推理?ViT 只在特定分辨率训练过,比如 224×224,模型学到的空间结构、感知能力都被限定在这个分辨率和 patch 布局下。推理时直接用原生分辨率,意味着输入 patch 布局和训练时完全不同,很多位置编码参数没被充分学习过,模型难以泛化。实验事实:ViT 直接用大/小分辨率推理,或者输入不同长宽比,准确率、鲁棒性会大幅下降(详见 ViT/DeiT 论文附录和许多 benchmark 对比)。

  3. 为什么NaViT/Patch n’ Pack训练方法能解决这个问题?NaViT从一开始就支持任意分辨率、长宽比训练,通过Packing和灵活的位置编码(factorized/fractional 2D embedding),模型在训练时见过各种分辨率/长宽比的图片,空间表征能力被极大提升。位置编码被设计得可以泛化到新尺寸,而不是只会“插值凑合”。所以,NaViT在推理时用原生分辨率,性能和鲁棒性远高于普通ViT(具体可见NaViT和ViT在ImageNet、ObjectNet、ImageNet-A等多分辨率benchmark上的对比)。

  4. 举个实际例子:ViT 训练分辨率 224×224,推理 256×256 时,需要把 1D/2D position embedding 插值,性能通常会掉 3~5 个点甚至更多。NaViT 训练过程中本身就暴露了各种分辨率,模型天然适应多样输入,推理时可直接用原生分辨率,性能很稳。

  5. 一句话总结/本质区别:普通 ViT 在推理时虽然理论上能处理任意分辨率,但实际上没有泛化能力,依赖插值位置编码,效果差。NaViT 专门为多分辨率/长宽比设计了训练机制,模型和位置编码都能自适应多样输入,推理原生分辨率才真正 work。

下面是一个伪代码

batch_images = [img1, img2, img3, ...]  # 多张原图
packed_tokens = []
image_slices = []
max_seq_len = 512

for img in batch_images:
    # 1. patchify(假设是16×16的patch)
    patches = patchify(img, patch_size=16)
    # 2. 用patch embedding卷积生成token
    tokens = patch_embedding(patches)  # shape: [num_patches, emb_dim]
    # 3. 记录每张图token的起止索引
    start_idx = len(packed_tokens)
    packed_tokens.extend(tokens)
    end_idx = len(packed_tokens)
    image_slices.append((start_idx, end_idx))

# 4. padding补齐
while len(packed_tokens) < max_seq_len:
    packed_tokens.append(pad_token)

# 5. 生成attention mask(让不同图片的token互不注意,pad token也mask掉)

# 6. 输入到Transformer
output = transformer(packed_tokens, attention_mask)

Multimodal Rotary Position Embedding (M-RoPE)

在多模态大模型时代,如何让模型理解文本、图片、视频等不同输入的位置信息,是 Transformer 架构面临的重要挑战。传统的大模型,比如 LLM(大语言模型)中用的是一维旋转位置编码(1D-RoPE),这种方式只能编码序列(如文本)的位置信息,对于图片和视频这样拥有二维甚至三维空间结构的数据就不够用了。

M-RoPE的创新点

M-RoPE(Multimodal Rotary Position Embedding,多模态旋转位置编码)应运而生。它的核心思想,就是将传统RoPE的位置编码从一维拆解为三维,分别对应:

不同模态下的M-RoPE怎么用?

M-RoPE带来了什么好处?

伪代码

def m_rope_embedding(token, mode):
    if mode == "text":
        pos_id = token_idx  # 1D
        embedding = rope_1d(pos_id)
    elif mode == "image":
        temporal_id = 0
        height_id = row_idx
        width_id = col_idx
        embedding = rope_3d(temporal_id, height_id, width_id)
    elif mode == "video":
        temporal_id = frame_idx
        height_id = row_idx
        width_id = col_idx
        embedding = rope_3d(temporal_id, height_id, width_id)
    return embedding

例子:文本、图片、视频的M-RoPE编码

1. 文本(Text)

假设有一句话:“Hello world”,分词后有5个token,index分别是0~4。对每个token:temporal_id = 0height_id = 0width_id = token的位置(0, 1, 2, 3, 4),实际实现里,因为是文本,通常三个id都一样,也可以都用token的位置。M-RoPE实际效果等价于传统的1D-RoPE。

2. 图片(Image)

假设输入是一张 $3\times 4$ 的patch网格图片(3行4列),共12个patch。对每个patch的token:temporal_id = 0(因为不是视频,没有时间维),height_id = 行号(0, 1, 2)width_id = 列号(0, 1, 2, 3),比如patch在第2行第3列(行号从0开始),那么temporal_id = 0height_id = 1width_id = 2

col=0 col=1 col=2 col=3
(0,0,0) (0,0,1) (0,0,2) (0,0,3)
(0,1,0) (0,1,1) (0,1,2) (0,1,3)
(0,2,0) (0,2,1) (0,2,2) (0,2,3)

所有patch的temporal_id都为0,只有height和width随位置变化。

3. 视频(Video)

假设有2帧视频,每帧图片patch网格为 $3\times 4$。对于第1帧(frame_idx = 0):temporal_id = 0height_id = 行号width_id = 列号,对于第2帧(frame_idx = 1):temporal_id = 1height_id = 行号width_id = 列号,比如第2帧的第2行第3列patch:temporal_id = 1height_id = 1width_id = 2,每个视频patch token都有唯一的(temporal, height, width)三元组。

假如我们要为所有token生成M-RoPE编码,编码过程简化伪代码如下:

def get_mrope_ids(mode, token_idx, row_idx=None, col_idx=None, frame_idx=None):
    if mode == "text":
        # 文本序列
        return (token_idx, token_idx, token_idx)  # 或全部用token_idx
    elif mode == "image":
        # 图片patch
        return (0, row_idx, col_idx)
    elif mode == "video":
        # 视频patch
        return (frame_idx, row_idx, col_idx)

总结

每个token输入transformer前,都会赋予这样一个三元组编号,分别用RoPE编码,然后合成最终的位置embedding。这就是M-RoPE的实质。

import torch
import torch.nn as nn

# --------- 1. 假设的原始输入(文本、图片、视频) -------------
text_tokens = torch.randint(1000, (6,))  # 文本:假设已经分词为6个token
image = torch.randn(3, 48, 64)  # 图片:单张图片,shape [3, 48, 64],3通道,48×64像素
video = torch.randn(2, 3, 48, 64)  # 视频:2帧,每帧[3, 48, 64]

# --------- 2. patch/token embedding ----------------------------
# (A) 文本 token embedding
vocab_size = 1000
embed_dim = 48
text_embedding = nn.Embedding(vocab_size, embed_dim)
text_token_emb = text_embedding(text_tokens)  # shape [6, 48]

# (B) 图片 patch embedding
patch_size = 16
patch_embedding = nn.Conv2d(3, embed_dim, kernel_size=patch_size, stride=patch_size)
img_patches = patch_embedding(image.unsqueeze(0))  # [1, 48, H', W']
H_p, W_p = img_patches.shape[2], img_patches.shape[3]
img_token_emb = img_patches.flatten(2).squeeze(0).transpose(0, 1)  # [num_patches, 48]

# (C) 视频 patch embedding
video_patches = patch_embedding(video.view(-1, 3, 48, 64))  # [2, 48, H', W']
T = video_patches.shape[0]
video_token_emb = video_patches.flatten(2).transpose(1, 2).reshape(-1, embed_dim)  # [T*num_patches, 48]
H_vp, W_vp = video_patches.shape[2], video_patches.shape[3]


# --------- 3. 生成M-RoPE的位置ID -------------------------------
# text_token_emb: [6, 48]  # 文本
# img_token_emb: [12, 48]  # 图片
# video_token_emb: [24, 48]  # 视频

# (A) 文本
text_len = text_token_emb.shape[0]
temporal_ids_text = torch.arange(text_len)
height_ids_text = torch.arange(text_len)
width_ids_text = torch.arange(text_len)

# (B) 图片
num_img_patches = H_p * W_p
temporal_ids_img = torch.zeros(num_img_patches, dtype=torch.long)
height_ids_img = torch.arange(H_p).repeat_interleave(W_p)
width_ids_img = torch.arange(W_p).repeat(H_p)

# (C) 视频
num_vid_patches = T * H_vp * W_vp
temporal_ids_vid = torch.arange(T).repeat_interleave(H_vp * W_vp)
height_ids_vid = height_ids_img.repeat(T)
width_ids_vid = width_ids_img.repeat(T)

# --------- 4. M-RoPE编码实现(见前例,也可封装成函数) ---------------
def rotary_embedding(pos_ids, dim):
    inv_freq = 1.0 / (10000 ** (torch.arange(0, dim, 2).float() / dim))
    sinusoid_inp = torch.einsum("i,j->ij", pos_ids.float(), inv_freq)
    emb = torch.cat([torch.sin(sinusoid_inp), torch.cos(sinusoid_inp)], dim=-1)
    return emb

def mrope_embedding(temporal_ids, height_ids, width_ids, dim):
    sub_dim = dim // 3  ### 每个维度占用的维度数
    temp_emb = rotary_embedding(temporal_ids, sub_dim)
    h_emb = rotary_embedding(height_ids, sub_dim)
    w_emb = rotary_embedding(width_ids, dim - 2*sub_dim)
    pos_emb = torch.cat([temp_emb, h_emb, w_emb], dim=-1)
    return pos_emb

# --------- 5. 获得最终的token + 位置编码 -------------------------
text_pos_emb = mrope_embedding(temporal_ids_text, height_ids_text, width_ids_text, embed_dim)
text_final_emb = text_token_emb + text_pos_emb  # [6, 48]

img_pos_emb = mrope_embedding(temporal_ids_img, height_ids_img, width_ids_img, embed_dim)
img_final_emb = img_token_emb + img_pos_emb  # [num_img_patches, 48]

vid_pos_emb = mrope_embedding(temporal_ids_vid, height_ids_vid, width_ids_vid, embed_dim)
vid_final_emb = video_token_emb + vid_pos_emb  # [T*num_vid_patches, 48]

# --------- 6. 拼成Transformer输入或分别处理 ------------------------
# 你可以将三种类型的final_emb拼在一起形成一个长序列送入Transformer(记得为不同模态分配不同的起始position偏移)。
# 或者根据任务需要分别送入不同模型分支。
print("文本序列输入 shape:", text_final_emb.shape)
print("图片序列输入 shape:", img_final_emb.shape)
print("视频序列输入 shape:", vid_final_emb.shape)

Unified Image and Video Understanding

在 Qwen2-VL 多模态大模型中,图片和视频是一起训练的,这样模型既能懂图片,也能理解视频内容。

对于视频:

对于图片:

此外,为了让训练既高效又能处理长视频,每段视频的输入都会动态调整分辨率,确保总的token数量不超过16384,这样既节省了显存,也保证了模型能看到较长的视频。

3. 训练

采用三阶段训练,LLM 来自 Qwen2,Vison encoder 来自 DFN(fixed position embedding 用 RoPE-2D 代替),下面的 token 不仅包含文本 token,还包含图片 token

第一阶段:只训练视觉编码器(ViT)

第二阶段:端到端联合训练(解冻所有参数)

第三阶段:冻结ViT,仅微调LLM(instruction tuning)

三阶段训练是为了让模型先建立强健的视觉表征,再实现端到端跨模态对齐,最后通过单独微调语言部分提升指令理解和实际应用能力,各阶段目标清晰、互不干扰,能显著提升最终多模态模型性能和泛化能力。

训练使用的是 ChatML 的数据形式

bounding box 形式