DeepSeekV3带火大模型infra,入门看这篇就够了!

知乎:真中合欢
地址:https://zhuanlan.zhihu.com/p/10091011992

为什么会有这篇文章:虽然工作内容不是infra,但是我比较喜欢研究训练方法,魔改训练框架造轮子。正好最近看到OpenRLHF用ray管理VLLM的方案,感觉很有意思,遂研究了一下,发现VLLM的TP切分和Megatron是一套逻辑,用torch的rpc也可以代替ray的远程调用,所以打算用Megatron+TorchRPC+VLLM实现一套类似的框架,后期再把VLLM原地换掉直接megatron推理。在开始这个大工程之前,正好有机会写下这篇文章,就算是开工仪式了。

本文的主要内容

本文主要是从编程的角度,对LLM训练框架所涉及的一些前置编程知识进行讲解,并且会举一些应用技巧,对应到当前的LLM训练框架,辅助理解训练框架的代码逻辑。举个例子,下面是一段megatron初始化多卡通信组的代码:

    rank = torch.distributed.get_rank()
    for ranks in rank_generator.get_ranks('dp'): # 迭代生成所有数据并行ranks的列表
        group = torch.distributed.new_group(
            ranks, timeout=timeout, pg_options=get_nccl_options('dp', nccl_comm_cfgs)
        ) # 根据数据并行ranks创建通信组
        group_gloo = torch.distributed.new_group(ranks, timeout=timeout, backend="gloo")
        if rank in ranks: # 如果当前rank属于这个数据并行ranks,则保存创建的通信组
            _DATA_PARALLEL_GROUP = group
            _DATA_PARALLEL_GROUP_GLOO = group_gloo
            _DATA_PARALLEL_GLOBAL_RANKS = ranks

读完本文你会了解:

  1. 什么是rank、什么是world_size,什么是通信组group

  2. 为什么这段代码是先建通信组,再根据rank in ranks决定是否保存?判断rank in ranks 了再创建group行不行?

  3. 在子进程和子线程中创建通信组有什么区别和要注意的地方。

  4. backend="gloo":什么是backend,gloo backend 是干什么的,为什么不用nccl backend。

再比如下面是一段deepspeed在参数上注册的回调函数:

    def create_reduce_and_remove_grad_hooks(self):
        self.grad_accs = []
        for i, param_group in enumerate(self.bit16_groups): # 遍历混精优化器的参数
            for param in param_group:
                if param.requires_grad:

                    def wrapper(param, i):
                        param_tmp = param.expand_as(param) # 在原始的参数上建立一个视图
                        grad_acc = param_tmp.grad_fn.next_functions[0][0] # 获得这个这个视图的前一个算子

                        def reduce_partition_and_remove_grads(*notneeded):
                            self.reduce_ready_partitions_and_remove_grads(param, i)

                        self._grad_acc_hooks.append(grad_acc.register_hook(reduce_partition_and_remove_grads)) # 在视图的前一个算子上注册回调函数
                        self.grad_accs.append(grad_acc)

                    wrapper(param, i)

读完本文你会了解到:

  • register_hook是干什么,什么时候触发。

  • 通常都是直接在参数上注册hook,为什么这里要在参数的视图的前一个算子注册hook:param.expand_as(param).grad_fn.next_functions[0][0]

下面开始正文。

训练框架是干什么的

目前LLM训练有两大主流框架:Deepspeed和Megatron-LM。前者的主要提出和维护者是微软的工程师,后者是英伟达的工程师。两个框架从底层原理到设计语言可以说是大相径庭。训练框架的主要目标有2:一是在有限的GPU中尽可能地塞入一个大号模型,二是高效地利用多GPU进行训练。完成第一个目标主要依赖的是模型切分,或者更笼统地说是降低单卡显存占用。完成第二个目标依赖的是异步、高重叠度、高带宽的数据通信。Deepspeed在降低显存这方面应用的技术主要有Zero-1、2、3, 序列并行、CPU Offload,高效通信方面主要是依赖register_hook回调函数的异步通信、多cuda事件流、GPU计算重叠、连续锁页缓存。Megatron在降低显存方面的主要技术有Distributed Optimizer、Tensor Model Parallel、Pipeline Model Parallel、序列并行,在高效通信方面主要的技术有P2P通信、重叠流水线并行、梯度缓存。在设计语言上,DeepSpeed属于外挂框架,框架并不介入模型前向的计算图,因此对模型结构一般没有特殊要求,核心代码通过大量回调函数和torch派生类封装,并不暴露给用户。Megatron则是属于内嵌框架,直接改变模型计算图,因此限制模型结构必须是类Transformer的结构,代码全部暴露在外,不怎么依赖回调函数。外挂框架的好处是兼容性好,对新手友好,缺点是启动慢、计算速度略低,适合数据量不大、不关注训练加速技术,专注于模型和数据迭代的人。内嵌框架的优点是启动、训练效率高一点,对于想魔改底层的人更友好,但是对于想轻微修改训练逻辑的不太友好,适合大规模训练和想要了解、改动并行训练代码的人。

Hello World

我们以hello world来开场:

import torch
import os

if __name__ == '__main__':
    rank = int(os.getenv('RANK','0'))
    world_size = int(os.getenv('WORLD_SIZE','1'))

    torch.distributed.init_process_group(rank=rank, world_size=world_size, backend='nccl')
    print(f'Hello World from rank:{torch.distributed.get_rank()} out of {torch.distributed.get_world_size()} worlds.')

假设代码命名为t1.py,则可以用 torchrun --nproc-per-node 2 t1.py 来启动。(如果不做特殊说明,后面都用这组参数启动。后面给出的demo大多数没有GPU也可以跑,只需要将张量的创建位置改为cpu,以及通信相关的后端全部使用gloo后端即可)

正常情况下,可以看到程序打印出

图片

下面对代码稍作解释。

启动命令

torchrun是安装torch后自带的命令,作用是帮助我们以多进程方式启动指定脚本。

在分布式环境中,我们首先要区分多机和多卡,多机是指多台运行训练任务的服务器,多卡是指一台机器上有多个显卡。我们一般称一台服务器为node,有几台服务器就有几个node。

那么 --nproc-per-node 从字面意思上就能看出表示每台服务器上启动多少个进程。一般来说,我们启动的进程数与服务器上的gpu数相同,也就是8卡机 nproc-per-node 设为8。当然一台8卡机你可以只启动2个进程,也可以启动100个进程,这个并不是硬限制。但是保持进程数和卡数相同可以简化我们的编程逻辑,让每个进程只负责一张卡上的计算。

除了nproc-per-node,启动命令还有几个常见参数:

  • --master_addr和--master_port:当启动的是多机环境时,需要用这两个参数指定主机的IP地址和端口号。这两个参数在所有master和slaver机器上都是一样的,不需要修改。

  • --nnodes:当多机启动分布式时,使用这个参数,表明总共有多少台机器。master会根据这个参数配置来等待slaver链接,直到slaver数量凑够nnodes才会启动。这个参数在每台机器上都是一样的,不需要修改。

  • --node_rank:表示当前node是全部node中的第几个,这个参数需要根据启动的机器进行更改,master的rank必须是0。

上述启动参数要放在torchrun之后,脚本路径之前。写在脚本路径之前的参数不会被传递给脚本运行的环境,所以脚本中不要处理这些参数。

rank & world size

在启动的训练脚本中,需要先初始化分布式环境,也就是下面这几行:

    rank = int(os.getenv('RANK','0'))
    world_size = int(os.getenv('WORLD_SIZE','1'))
    torch.distributed.init_process_group(rank=rank, world_size=world_size, backend='nccl')

如果是使用torchrun启动的脚本,torch会帮你在环境变量中写入RANK和WORLD_SIZE这两个变量,在脚本中可以读出来。它们分别表示当前进程的序号和总进程数。这个进程序号是全局的进程序号,也就是说如果每台服务器启动8个进程,总共10台服务器参与训练,那么第十台机器的8个进程rank分别是72、73、74、75、76、77、78、79。world_size也是全局的进程数,而不是单台服务器上的进程数。

获得rank和world_size后,就可以用torch.distributed.init_process_group初始化分布式环境。

在初始化之后的任何地方,都可以用torch.distributed.get_rank()和torch.distributed.get_world_size()来获取当前进程序号和总进程数。

另外需要提的一点是,torch并没有local_rank 这个概念。这是一些训练框架自己定义的,通常用来表示当前进程是这台服务器上的第几个进程。

后端 backend

在初始化命令中,我们还指定了backend参数,这个参数表示的是分布式环境默认使用的通信后端是什么,一般可以选择 nccl、gloo和mpi后端。gpu to gpu的通信选择nccl后端。cpu to cpu的通信选择gloo后端,一般不太会用到mpi后端。

nccl通信会比gloo通信快,所以应该尽量使用nccl进行通信。但是在有些时候,比如读取数据的阶段,如果多个进程之间需要通信,一般使用用gloo通信。因为这时,我们并不希望这些还没有开始正向传播的张量过早出现在gpu上,避免过多占用显存。在后面讲group的部分,我们会提到怎么让训练进程使用nccl后端,数据读取进程使用gloo后端。

训练脚本开始阶段的一些小细节

set device

分布式训练中,在我们创建gpu张量时,经常使用torch.cuda.current_device()获取当前rank使用的cuda设备,然后直接在这个设备上创建。使用这个函数的前提是之前通过set_device显式设定过默认设备。另外还有一些算子会要求用户必须执行过set_device。

所以一个好习惯是,在执行主要的训练逻辑之前,尽早设置默认的cuda设备:

devices = torch.cuda.device_count()
torch.cuda.set_device(rank % devices)

如果不手动设置,current_device默认会返回cuda:0。

固定随机种子

分布式训练时随机种子是一个容易忽略的事,但却是很重要的事。在脚本初始化阶段设置一个固定的随机种子有两个好处,一个是让实验可复现,另一个就是让不同rank的模型以相同的方式初始化,不然还要用通信操作同步一下各个模型来保证模型初始化状态的一致性。

def set_random_seed(seed):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)

通信算子

通信算子指的是在分布式训练中,能够在不同rank之间进行数据交换的算子。torch提供了很多通信相关的算子,最常用可以划分为5类:规约、聚集、广播、点对点通信和其他。

规约

规约最典型的就是torch.distributed.all_reduce,这个算子能够对不同rank之间的数据进行求和、求均值、最大值等操作,特点是不论有多少个rank,最终结果只有一个tensor。例如下面这段代码,作用是对所有rank之间的"tensor"求和:

import torch
import os

if __name__ == '__main__':
    rank = int(os.getenv('RANK','0'))
    world_size = int(os.getenv('WORLD_SIZE','1'))

    torch.distributed.init_process_group(rank=rank, world_size=world_size, backend='nccl')
    devices = torch.cuda.device_count()
    torch.cuda.set_device(rank % devices)

    tensor = torch.tensor([rank +。1], dtype=torch.long, device='cuda')
    torch.distributed.all_reduce(tensor)
    print(f'rank:{rank} {tensor}')

rank0的tensor=1,rank1的tensor=2,求和结果为3,因此能看到这样的输出:

图片

规约操作基本可以算是最常用的通信算子。例如rank之间通过规约操作同步梯度、计算平均loss,再比如分布式softmax利用规约计算最大值、归一化分母等等。

聚集

聚集操作中最典型的是torch.distributed.all_gather(),能够把不同rank的数据收集到一起,特点是参与通信的rank有多少,就会得到多少个tensor。例如下面这段代码,作用是将所有rank的tensor收集到一起:

import torch
import os

if __name__ == '__main__':
    rank = int(os.getenv('RANK','0'))
    world_size = int(os.getenv('WORLD_SIZE','1'))

    torch.distributed.init_process_group(rank=rank, world_size=world_size, backend='nccl')
  &nbs
<think>好的,我现在需要帮助用户了解多模态大模型的AI基础设施或架构,以及部署方面的内容。首先,我得理解用户的具体需求。用户提到了多模态大模型的基础设施、架构和部署,可能他们正在考虑如何搭建或应用这样的系统,或者想深入了解其技术细节。 接下来,我需要回顾已有的知识。多模态大模型通常处理多种类型的数据,比如文本、图像、音频等,因此架构需要支持这些数据的处理和融合。根据之前提供的引用,用户提到了LLM和VLM,也就是大型语言模型和视觉语言模型,这可能涉及到不同模态的融合技术。 然后,我应该考虑架构的关键组成部分。通常,AI大模型的基础设施包括数据处理层、模型训练层、推理层、资源管理和部署工具。对于多模态模型,可能还需要专门的模块来处理不同模态的数据,比如图像编码器、文本编码器等。此外,硬件加速如GPU和TPU的使用也是重要部分,因为大模型需要强大的计算资源。 部署方面,需要考虑模型优化技术,比如量化、剪枝和蒸馏,以减少模型大小和提高推理速度。同时,分布式训练和推理框架也是必须的,例如使用TensorFlow或PyTorch的分布式训练功能。另外,云原生部署和边缘计算可能也是用户关心的点,特别是如果应用场景需要低延迟或离线处理的话。 用户可能还关心如何确保系统的扩展性和稳定性,比如资源调度和容错机制。此外,安全性和数据隐私在多模态应用中同样重要,尤其是在处理敏感信息时。 我需要确保回答结构清晰,分点说明每个部分,并且引用提供的参考文献。比如,引用[^1]提到AI大模型在自然语言处理、计算机视觉等方面的应用,这可以支持多模态的必要性。引用[^2]提到整合语言能力和视觉认知,这与多模态架构的设计相关。 最后,生成相关问题时要涵盖架构设计、优化、应用场景和挑战,帮助用户进一步探索。需要确保问题具有针对性,能引导用户深入理解多模态大模型的各个方面。</think>### 多模态大模型的AI基础设施与架构解析 多模态大模型(如GPT-4、CLIP等)通过整合文本、图像、音频等不同模态的数据,实现跨模态理解与生成。其基础设施和架构设计需满足**大规模计算、异构数据处理、高效训练与推理**等需求。以下是核心架构组件及部署要点: --- #### 一、核心架构设计 1. **数据预处理层** - **多模态数据融合**:设计统一的数据表示(如向量嵌入),将文本、图像、音频映射到同一语义空间,例如通过对比学习(Contrastive Learning)对齐特征[^1]。 - **分布式存储**:使用分布式文件系统(如HDFS)或云存储(如AWS S3)管理海量异构数据。 2. **模型训练层** - **混合模态建模**:采用多编码器-单解码器架构(如Transformer),例如CLIP的图文双塔结构[^2]。 - **分布式训练框架**:基于PyTorch的`FSDP`(完全分片数据并行)或TensorFlow的`TPUStrategy`,支持千亿参数规模的训练。 3. **推理服务层** - **轻量化推理引擎**:通过模型剪枝、量化(如INT8)或知识蒸馏降低部署成本。 - **多模态交互接口**:提供统一的API网关,支持文本到图像生成、视频语义检索等跨模态任务。 --- #### 二、关键技术组件 1. **硬件加速** - 使用GPU集群(如NVIDIA A100)或TPU Pods进行并行计算,结合NVLink优化通信宽。 2. **异构计算框架** - **计算图优化**:利用ONNX Runtime或TensorRT加速模型推理。 - **跨模态对齐工具**:如FAISS库实现大规模向量相似性检索。 3. **资源管理与调度** - 基于Kubernetes的容器化部署,结合Slurm或Ray实现动态资源分配。 --- #### 三、部署实践与挑战 1. **云原生部署** - 通过AWS SageMaker、Google Vertex AI等平台实现弹性扩缩容,按需分配GPU资源。 2. **边缘计算优化** - 模型轻量化后部署至边缘设备(如手机、IoT设备),使用TensorFlow Lite或Core ML框架。 3. **安全与隐私** - 联邦学习(Federated Learning)保护数据隐私,结合同态加密(HE)实现安全推理。 --- #### 四、典型应用场景 1. **医疗诊断**:结合医学影像与文本报告生成诊断建议[^1]。 2. **智能客服**:同时理解用户的语音、文本和图像输入。 3. **工业质检**:通过视觉+传感器数据实现缺陷检测[^2]。 --- 相关问题
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值