近期获得/加深的一些知识理解

GPT结构、训练与推理分析

图解GPT
理解prefill阶段是生成kv cache以及用最后一个token对之前的kv进行计算得到一个概率,后续token同理。vllm就是对各个模型进行推理上的重构,而本身的modeling_qwen2.py这类其实是训练用的文件。推理阶段还需要重写。实际上公司内部模型部署vllm就是转化成qwen格式然后调用vllm的qwen接口……

bash脚本文件语法

写sh脚本的时候,’=’前后不能有空格,与本人python编程习惯背道而驰…..

python小知识点——import

使用

1
2
import sys  
sys.path.insert(0, sys.path[0] + "/../")

进行导入的时候,需要注意的是,这个添加的路径是当前工作目录的相对路径,而不是这个文件的相对路径。
也就是说,如果一个处于该文件父目录的文件调用了该文件,那么该文件的这个代码就会被解释成“将父目录的父目录加入path”,与预期的“将父目录加入path”不符合
导入模块应该采用以下写法,即获取当前文件的父目录,然后操作,如sys.path.insert(0, parent_dir)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import os
import sys
import importlib.util

# 获取当前文件的目录
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
# 构造tokenization_milm3.py的绝对路径
#module_path = os.path.join(parent_dir, "test.py")

# 获取模块名和spec
#module_name = "test"
#module_spec = importlib.util.spec_from_file_location(module_name, module_path)

# 加载并执行模块
#module = importlib.util.module_from_spec(module_spec)
#module_spec.loader.exec_module(module)

这样写,相对导入是确定了的。
关于import,其中’.’要不要加,在实际使用的时候还要斟酌,一般来说module内部的相互import应该加,而module外部import module内部的内容不应该加。
也就是说,只有在该项目/文件夹 被导入时,项目/文件夹内部使用.来相对导入,才不会报错。否则,如果在内部直接使用.或..来import,则跑不通。
通常来说,module内部文件组织大可以直接import,只是在最后对外的整合接口里面,需要用相对导入?
needlebench的代码例子中,对于每种length,生成三类数据集,最终该长度的数据集对外的总接口文件如下:

1
2
3
4
5
6
7
8
9
10
from .needlebench_multi_reasoning_4k import get_datasets as get_datasets_mul_reasoning
from .needlebench_single_4k import get_datasets as get_datasets_single_needle
from .needlebench_multi_retrieval_4k import get_datasets as get_datasets_mul_needles

def get_datasets(tokenizer_path):
res = []
res.extend(get_datasets_single_needle(tokenizer_path))
res.extend(get_datasets_mul_needles(tokenizer_path))
res.extend(get_datasets_mul_reasoning(tokenizer_path))
return res

这里相对导入就是必须的,不然父目录import会报错

qwen2的代码里面没有yarn,是否表面qwen2只是在rope和dynamic rope的基础上实现了128k?

qwen2的代码,有32层,前28层是full attention, 后4层是4096窗长的sliding window attention,最后一层的感受场大小为4 * 4096,也就是16k?
实际上,代码中是,如果设置使用swa,那么就在28层之后使用,否则就是全部层都全注意力。当然,这只是transformer里面的基础设置。qwen2.5各个模型有各自的config,都是不同的。观看2.5-32b的config,SWA基本上已经弃用了。qwen2.5-32b本身最大位置编码已经到了131072,即4096*32, 然后rope的base也乘100变成了1000000,正好和苏神博客中提到的,NTK-scaling与yarn对做大规模预训练的公司来说没啥区别,都可以获得更好的初始模型长文本效果,而且前者更方便实现。

vllm推理加速

或许可以参见https://github.com/vllm-project/vllm/blob/main/vllm/attention/backends/xformers.py#L392
通过对prefill阶段进行并行计算来加速TTFT。这个阶段类似于训练过程,prompt加入attn mask然后进行attn的计算操作,在模型的每一层,都会存储对应的kv;和训练过程计算流程一致,除了没有LM head(因为LH head是在最上层CasualPretrainedModel加入的,其下逐层是:DecoderLayer-Decoder-Attention,训练过程中与输入shape相同的N*d维度的tensor最终会经过LM Head映射为N * V的tensor,V是词表大小,之后根据进行BCE loss的计算),这就是为什么prefill阶段是计算密集型的原因。当然不是说显存占用不大,只是和decode阶段比起来,计算量是尤其巨大的。
(感觉还有点不明白的地方,如具体计算的细节,但后面再说吧)

序列并行代码(序列并行的概念在不同框架里面可能并不一致)

xtuner里面的序列并行(sequence parallel),好像就是megatron里面的 Context Parallel并行;megatron里面的sequence parallel似乎没对attention进行切分并行,只在CP里面做了更进一步的实现
关于megatron的细节,这个博客中有一系列的文章,或许可供参考

知乎干货up主

  1. 真中合欢爱好者(发布的文章目前看来都挺有价值,LLM相关回答也不错)

    • CUDA算子理解
      本人是NLP搬砖工,并不专门研究CUDA算子。我看了几个回答没看见特别通俗易懂的,所以我想尝试以一个外行人的身份给其他外行人解释。所以我主要注重懂,而不是准。在CUDA生态的最低层就是硬件,也就是显卡里面的计算单元、显存、缓存、总线、控制器等等。计算单元负责数值和逻辑运算,缓存负责存储计算前的数据和计算结果,显存和缓存的作用相同,但是更大更慢。总线负责在各个部件之间传递数据,控制器负责调度。在硬件之上的是驱动。显卡插在电脑上,你的电脑应该如何访问显卡,如何传输数据,怎么获取显卡的结果是由驱动定义的。驱动再之上是算子,程序员通常会以c++语言来写一些计算程序,比如一个数加上另一个数,一个矩阵加上另一个矩阵等等,这些都是具体的程序。这些程序在程序员写好后,会被编译软件编译成汇编代码,然后再编译成机器码,保存下来。在cpu编程里,这样的一个逻辑简单、能完成某一种特定运算的功能通常被称为函数或简单程序,在gpu编程中称为算子。比如完成两个矩阵相加功能的称为加法算子,完成乘法的叫乘法算子。当然还有执行非常复杂功能的算子,比如flash attention算子,实现了整个self attention功能。当你需要某个功能而去运行某个算子时,实际就是将算子的机器码、待运算的数据通过驱动传递给显卡,结果再通过驱动返回。算子再往上就是机器学习框架,比如torch、tensorflow,或者其他框架。它们对一系列算子进行整合,实现“数学中函数级运算”的功能。最简单的矩阵加减乘除就不说了,比如要计算一个多元一次方程,只需要调用torch框架中的linear函数:y=torch.nn.Linear(40,1)(torch.randn(1,40)) 上面这一行torch代码就是计算一个40元一次方程。这背后涉及了矩阵的乘法和加法,需要调用乘法和加法算子,torch这个框架在背后帮我们调用了。那么在整个过程中cuda体现在哪里呢?首先我们用c++写的这个程序,就叫cuda程序,通常是以.cu结尾。然后把这个程序编译为机器码的过程要用到cuda编译器。为什么我们要用cuda算子完成一个功能,而不是cpu程序呢?这通常是为了利用gpu高并发的特性。在cpu编程里,大部分情况我们写的都是单线程程序,如果涉及到高性能计算或IO,一般会并发多线程,这个线程数量少的2-3个,多的也就30-40个,撑死了也就100,一般和cpu的核心数一个量级,因为每个cpu核心同一时间只能处理一个线程,线程太多了相互抢占反而造成性能下降。就算有超线程,也不会超太多。但是在gpu里,我们为了完成一次计算,会启动上万甚至百万个线程。那么能实现如此高的并发,gpu肯定要设计一套不同于一般cpu的特殊架构和硬件,使用特别设计的计算单元、缓存、显存、总线、控制器,完成大量线程并发调度、大规模数据通信、大规模简单运算。这一套架构也称为cuda架构。所以总的来说,cuda就是英伟达为了大规模计算设计的一整套软硬件体系。也就是其他答主所解释的“平台”这一个概念。
    • 拒绝采样
    • ……..剩下的不放这儿了
  2. ybq——真中合欢爱好者的同公司饭搭子

训练模型小trick

  • 只用打榜数据退火,用大量数据微调,模型效果会变差
  • “小初始化、大权重衰减、适中学习率” 的组合能更好学习推理解,因为更可能促使模型通过低复杂度的优化路径逐步逼近目标函数,从而更好地理解数据中的组合性结构; (Paper Link:Implicit Regularization of Dropout)
    • 同一作者的 tpami 讨论了 dropout 对凝聚的作用:“研究发现Dropout可以在全训练过程中促使神经网络趋于参数凝聚,并且不要求小初始化,因此,在保持好泛化性的同时也不会遭受由于小初始化带来的训练慢的问题。其次,该研究发现并验证了使用Dropout训练的神经网络与标准梯度下降训练相比,具有更平坦的最小值,而该团队发现的隐式正则化正是训练可以找到平坦解的关键。”
  • 对单一任务数据集的选择,通常认为为避免过拟合的影响应当选择第一个高点,但是从实验来看,选择步数较深也有一定的价值。
  • 任务重复度的关系:
    • 4b-24l的实验表明,平均时不同ckpt对应的训练数据集的任务交叉度越低,4b平均上的效果越好。但是这可能和4b作为小模型,参数之间的耦合度比大模型更高有关。
    • 但是总之结论是需要在平均时增强数据集任务的分类独立度和数据集本身的质量。这与不同类别的任务推理与模型不同结构/参数有关的自然假设符合,会带来更可预期的平均结果。