deepspeed/vllm底层原理

DeepSpeed 聚焦于大模型高效训练(同时支持推理优化),通过分布式技术突破训练的显存、算力瓶颈;

vLLM 专注于大模型高吞吐、低延迟推理,核心解决推理阶段的显存利用率请求处理效率问题。

 

一、DeepSpeed 底层原理

DeepSpeed 是微软推出的分布式训练 / 推理框架,核心目标是让超大模型(千亿、万亿参数)的训练变得可行,其底层核心围绕分布式训练技术显存优化技术

1. 核心:分布式训练技术(模型并行 + 数据并行)

DeepSpeed 的核心竞争力在于对分布式训练的极致优化,其中 模型并行(Model Parallelism)和数据并行(Data Parallelism)是基础,且针对超大模型做了深度扩展:
  • 数据并行(DP):将训练数据拆分到多个 GPU 上,每个 GPU 持有完整的模型副本各自计算局部梯度后,通过梯度同步(如 AllReduce 操作)更新全局模型参数。DeepSpeed 优化了梯度同步的通信效率,支持混合精度下的梯度压缩,减少通信开销。
  • 模型并行(MP):针对千亿参数模型无法放入单卡显存的问题,将模型的层 / 参数拆分到多个 GPU 上(横向拆分:同一层的参数拆分纵向拆分:不同层拆分到不同 GPU),每个 GPU 仅持有模型的一部分,通过前向 / 反向传播时的跨卡通信传递中间结果。
  • 扩展零冗余优化器(ZeRO):这是 DeepSpeed 最核心的创新,本质是优化器状态的并行化,解决了传统数据并行中优化器状态(参数、梯度、优化器缓存,如 Adam 的 m/v 动量)占用大量显存的问题。ZeRO 分为三个级别(ZeRO-1/2/3),逐步实现优化器状态、梯度、模型参数的跨卡分布式存储,彻底消除冗余,大幅降低单卡显存占用:
    • ZeRO-1:仅并行化优化器状态(Adam 的 m/v 动量),每个 GPU 仅存储部分优化器状态;
    • ZeRO-2:并行化优化器状态 + 梯度,每个 GPU 仅计算并存储部分参数的梯度;
    • ZeRO-3:并行化优化器状态 + 梯度 + 模型参数,每个 GPU 仅持有模型的一小部分参数,是训练万亿参数模型的核心技术。

2. 关键显存优化技术

除 ZeRO 外,DeepSpeed 还通过多种技术进一步压缩显存占用:
  • 混合精度训练(FP16/FP8):默认使用 FP16 精度进行前向 / 反向传播,仅在参数更新时使用 FP32 精度,相比纯 FP32 训练,显存占用减少 50%;同时支持 FP8 精度,进一步降低显存占用和计算开销。
  • 激活值检查点(Activation Checkpointing):训练时,反向传播需要依赖前向传播的激活值,传统训练会缓存所有激活值,占用大量显存。DeepSpeed 通过选择性缓存激活值反向传播时重新计算未缓存的激活值,以少量计算开销换取大幅显存节省。
  • 显存卸载(Offloading):将模型参数、优化器状态等从 GPU 显存卸载到 CPU 内存或 NVMe 固态硬盘,当需要计算时再加载回 GPU,适合 GPU 显存不足的场景,牺牲少量速度换取显存释放。

3. 其他核心能力

  • 支持流水线并行(Pipeline Parallelism,PP):将模型按层拆分成多个阶段,每个 GPU 负责一个阶段的计算,通过流水线调度提升计算利用率
  • 推理优化:提供 DeepSpeed-Inference,支持模型量化、张量并行推理,优化超大模型的部署效率,但推理并非其核心聚焦方向。

二、vLLM 底层原理

vLLM 是加州大学伯克利分校推出的 LLM 推理框架,核心目标是最大化推理吞吐量、降低延迟,其底层核心创新的内存管理机制高效的请求调度策略,关键原理如下:

1. 核心创新:PagedAttention(分页注意力机制)

这是 vLLM 最根本的技术突破,解决了传统推理框架中注意力机制显存利用率低、内存碎片严重的问题。传统推理中,每个序列(Sequence)的 K/V 缓存(Key/Value Cache,注意力计算的核心中间数据)是连续存储的,当请求动态增减(如长序列、批量请求变化)时,会产生大量内存碎片且无法充分复用显存;而 PagedAttention 借鉴了操作系统的分页内存管理思想,对 K/V 缓存进行分页管理:
  • K/V 缓存分页:将每个序列的 K/V 缓存拆分成固定大小的 “页”(Page),每个页独立存储,不再要求连续内存空间;
  • 页表管理:为每个序列维护一张 “页表”,记录该序列的所有 K/V 页的内存地址,通过页表实现对离散页的快速寻址和访问
  • 注意力计算优化:在注意力分数计算时,PagedAttention 能够高效地对离散分布的 K/V 页进行聚合计算,无需将页重新拼接为连续内存,同时通过批量处理优化计算开销。
PagedAttention 的核心优势:① 彻底消除 K/V 缓存的内存碎片,大幅提升显存利用率(通常可达 90% 以上,远高于传统框架的 30%-50%);② 支持动态序列长度,无需预留大量连续显存,适配突发请求和长文本推理;③ 实现 K/V 缓存的高效复用,为批量请求调度提供基础。

2. 关键支撑:Continuous Batching(连续批处理)

传统 LLM 推理采用 “静态批处理”(Static Batching):即一次性收集一批请求,处理完成后再处理下一批,期间如果有新请求到达,需要等待当前批次结束,导致吞吐量低下、延迟升高;而 vLLM 基于 PagedAttention 实现了Continuous Batching(也叫 Dynamic Batching/Streaming Batching,连续批处理 / 动态批处理):
  • 请求动态调度:将每个请求的推理过程拆分为多个 “步骤”(Step,对应 Transformer 层的一次前向计算),调度器会持续从请求队列中选取就绪的请求步骤,动态组合成批次进行计算,无需等待整个请求完成;
  • 细粒度调度:当一个请求的某一步计算完成后,立即将其释放的计算资源分配给其他待处理的请求步骤,新到达的请求可以随时插入到当前正在处理的批次中,实现 “流水式” 的请求处理;
  • 吞吐量最大化:通过细粒度的动态调度,充分压榨 GPU 计算资源,避免 GPU 空闲,大幅提升推理吞吐量(通常是传统静态批处理框架的 5-10 倍)。

3. 其他优化技术

  • 显存优化:支持 K/V 缓存的量化(如 FP8/INT8),进一步降低显存占用;同时支持 GPU 显存不足时的 CPU 内存卸载,兼容低配置硬件;
  • 兼容广泛:无缝支持 Hugging Face 模型格式,无需修改模型代码即可部署,同时支持多 GPU 张量并行推理,适配大模型部署需求;
  • 调度优化:内置高效的请求调度器(如基于优先级的调度),支持对不同优先级的请求进行差异化处理,平衡吞吐量和延迟。

三、总结

  1. DeepSpeed 的核心是分布式训练技术(ZeRO 模型 / 优化器并行)和显存压缩,解决超大模型训练的显存 / 算力瓶颈,推理是其附加能力;
  2. vLLM 的核心是PagedAttention 分页注意力和Continuous Batching 连续批处理,解决推理阶段的显存利用率低、吞吐量低的问题,是专用推理框架;

四、deepspeed运行

1.deepspeed启动

deepspeed 启动器(CLI 前端)是 DeepSpeed 框架提供的分布式训练入口,负责统筹分布式环境的初始化和资源调度。

# 多节点运行
deepspeed --num_nodes 2 --num_gpus 8 --master_addr 192.168.1.100 --master_port 29500 train_qwen_32b.py 

1) --num_gpus:指定单节点 GPU 数量

  • 传递目标:最终会传递给 PyTorch 的分布式训练后端(torch.distributed),同时被 DeepSpeed 框架内部的资源管理器接收。
  • 核心作用:
    1. 告诉 deepspeed 启动器:当前单个计算节点上要使用的 GPU 卡数(示例中为 8 卡);
    2. 启动器会基于该参数,在当前节点上为每个 GPU 启动一个独立的训练进程(即 “单节点多进程”,8 卡对应 8 个进程);
    3. 后续通过 torch.distributed 完成单个节点内多 GPU 进程的通信初始化(如 NCCL 后端的进程组建立)。

2) --num_nodes:指定分布式训练的总节点数

  • 传递目标:仅由 deepspeed 启动器自身解析使用(不直接传递给 torch.distributed,但会影响启动器对 torch.distributed 的配置)。
  • 核心作用:
    1. 告诉 deepspeed 启动器:整个分布式训练集群包含的物理节点总数(示例中为 2 个节点);
    2. 启动器结合 num_nodes 和 num_gpus,计算出训练的总进程数(示例中:2 节点 × 8 卡 / 节点 = 16 个进程);
    3. 跨节点进程的通信和资源分配提供基础(如确认每个节点的进程编号范围)。

3)--master_addr:指定主节点(master node)的 IP 地址

  • 传递目标:最终传递给 PyTorch 的分布式训练后端(torch.distributed),是跨节点通信的核心标识。
  • 核心作用:
    1. 在多节点训练中,需要选定一个节点作为 “主节点”(示例中 IP 为 192.168.1.100 的节点即为主节点),负责初始化分布式进程组、同步训练状态(如梯度同步、模型 checkpoint 保存)
    2. 其他从节点(slave nodes)通过该 IP 地址,找到主节点并建立网络连接,实现跨节点的数据传输(如 AllReduce 梯度聚合);
    3. 该参数需确保所有节点都能通过该 IP 相互 ping 通(需关闭防火墙或开放对应端口)。

4) --master_port:指定主节点的通信端口

  • 传递目标:与 master_addr 一致,最终传递给 PyTorch 的分布式训练后端(torch.distributed)。
  • 核心作用:
    1. 作为主节点的 “通信端口”,用于监听从节点的连接请求,实现跨节点进程间的网络通信(示例中端口为 29500);
    2. 避免端口冲突:需确保该端口在主节点上未被其他程序占用,且所有节点都能访问该端口;
    3. 配合 master_addr,形成唯一的 “主节点通信标识”(IP:端口),让所有分布式进程能够建立统一的通信链路。

2. 底层工作流程

1)启动器解析参数:

deepspeed 启动器首先解析 num_nodesnum_gpusmaster_addrmaster_port,计算总进程数,确认节点角色(主 / 从);

2)启动节点进程:

在每个节点上,启动器为 num_gpus 个 GPU 分别启动一个 train_qwen_32b.py 进程,并为每个进程分配唯一的 rank(全局进程编号)和 local_rank(节点内进程编号);

3)传递参数给 PyTorch 后端:

启动器将 master_addrmaster_portrankworld_size(总进程数,即 num_nodes×num_gpus)传递给 torch.distributed.init_process_group() 函数(该函数是 PyTorch 分布式初始化的核心,你的训练脚本中无需手动调用,DeepSpeed 已自动集成);

4)建立分布式通信:

torch.distributed 基于上述参数,通过 NCCL(GPU 分布式首选后端)建立跨节点、多 GPU 的进程通信组,后续训练中的梯度同步、模型参数并行等操作均基于该通信组完成;

5)DeepSpeed 利用配置:

DeepSpeed 框架内部会读取这些分布式参数,结合 deepspeed_config.json 中的 ZeRO 配置,完成模型参数、优化器状态的分布式拆分与调度。

3.补充说明

  • 这些参数不会直接作为脚本的命令行参数传入 train_qwen_32b.py(无法通过 argparse 在脚本中直接获取它们),而是由 deepspeed 启动器和 torch.distributed 底层自动解析使用;
  • 若需要在脚本中获取分布式相关信息(如 rankworld_size),可通过 torch.distributed 的 API 获取
import torch.distributed as dist

# 初始化后(DeepSpeed 已自动完成初始化)
rank = dist.get_rank()  # 全局进程编号
world_size = dist.get_world_size()  # 总进程数
local_rank = int(os.environ.get("LOCAL_RANK", 0))  # 节点内 GPU 编号

五、deepspeed示例

lora训练train_qwen_32b模型 ,train_qwen_32b.py

# # 1. 安装 PyTorch(推荐对应 CUDA 版本,示例为 CUDA 11.8)
# pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
#
# # 2. 安装 DeepSpeed(最新稳定版)
# pip install deepspeed
#
# # 3. 安装模型相关依赖(Hugging Face 生态)
# pip install transformers==4.36.2 datasets==2.14.6 accelerate==0.25.0 sentencepiece==0.1.99 protobuf==4.25.1
# pip install tokenizers==0.14.1 peft==0.7.1  # peft 用于高效微调(可选,如 LoRA)

import os
import torch
from datasets import load_dataset
from transformers import (
    AutoTokenizer,
    AutoModelForCausalLM,
    TrainingArguments,
    Trainer,
    DataCollatorForLanguageModeling,
    default_data_collator
)
import deepspeed
from peft import LoraConfig, get_peft_model, PeftModel  # LoRA 相关(可选)


# ====================== 1. 基础配置(可根据需求修改) ======================
class Config:
    # 模型路径(Hugging Face 本地路径或远程仓库,如 Qwen/Qwen-32B)
    model_name_or_path = "/path/to/local/Qwen-32B"
    # 数据路径(本地json文件或huggingface数据集名称)
    data_path = "/path/to/train_data.json"
    # 输出路径(模型、日志、checkpoint 保存目录)
    output_dir = "./qwen-32b-deepspeed-training"
    # 训练参数
    per_device_train_batch_size = 1  # 单卡批次大小(根据显存调整)
    gradient_accumulation_steps = 16  # 梯度累积步数(弥补单卡批次小的问题)
    num_train_epochs = 3  # 训练轮数
    learning_rate = 2e-5  # 学习率
    weight_decay = 0.01  # 权重衰减
    logging_steps = 10  # 日志打印步数
    save_steps = 100  # 模型保存步数
    fp16 = True  # 开启混合精度训练(降低显存占用)
    # DeepSpeed 配置文件路径
    deepspeed_config_path = "./deepspeed_config.json"
    # LoRA 配置(启用后为 LoRA 微调,关闭则为全参数微调)
    use_lora = True
    lora_rank = 8
    lora_alpha = 16
    lora_dropout = 0.05


# ====================== 2. 数据预处理 ======================
def preprocess_data(tokenizer, config):
    # 加载数据集(支持本地json,格式为 [{"text": "训练文本1"}, {"text": "训练文本2"}, ...])
    dataset = load_dataset("json", data_files=config.data_path)["train"]

    # 文本编码函数
    def encode_function(examples):
        # 对文本进行编码,截断最长序列(Qwen 最大序列长度通常为 2048/4096)
        inputs = tokenizer(
            examples["text"],
            truncation=True,
            max_length=2048,
            padding=False,
            return_tensors="pt"
        )
        # 因果语言模型的标签与输入一致
        inputs["labels"] = inputs["input_ids"].clone()
        return inputs

    # 批量处理数据集(num_proc 为进程数,根据CPU核心数调整)
    encoded_dataset = dataset.map(
        encode_function,
        batched=True,
        num_proc=8,
        remove_columns=dataset.column_names,  # 删除原始文本列,只保留编码后的列
        load_from_cache_file=False
    )

    # 划分训练集和验证集(按 9:1 划分)
    encoded_dataset = encoded_dataset.train_test_split(test_size=0.1, seed=42)
    return encoded_dataset["train"], encoded_dataset["test"]


# ====================== 3. 模型与Tokenizer加载 ======================
def load_model_and_tokenizer(config):
    # 加载 Tokenizer(Qwen 使用 sentencepiece 分词,需开启 padding_side)
    tokenizer = AutoTokenizer.from_pretrained(
        config.model_name_or_path,
        trust_remote_code=True,  # Qwen 需开启该参数
        padding_side="right"  # 右填充,适配因果语言模型
    )
    # 设置 pad_token(若未设置,默认使用 eos_token)
    if tokenizer.pad_token is None:
        tokenizer.pad_token = tokenizer.eos_token

    # 加载 Qwen-32B 模型
    model = AutoModelForCausalLM.from_pretrained(
        config.model_name_or_path,
        trust_remote_code=True,
        torch_dtype=torch.float16,  # 使用 FP16 精度加载模型,降低显存
        device_map="auto",  # 自动分配模型到 GPU/CPU
        low_cpu_mem_usage=True  # 降低 CPU 内存占用
    )

    # 启用 LoRA 微调(可选,大幅降低显存占用)
    if config.use_lora:
        lora_config = LoraConfig(
            r=config.lora_rank,
            lora_alpha=config.lora_alpha,
            target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],  # Qwen 注意力层目标模块
            lora_dropout=config.lora_dropout,
            bias="none",
            task_type="CAUSAL_LM"
        )
        model = get_peft_model(model, lora_config)
        # 打印 LoRA 可训练参数占比
        model.print_trainable_parameters()

    return model, tokenizer


# ====================== 4. 训练主函数 ======================
def main():
    # 加载配置
    config = Config()
    # 创建输出目录
    os.makedirs(config.output_dir, exist_ok=True)

    # 加载模型和 Tokenizer
    model, tokenizer = load_model_and_tokenizer(config)

    # 数据预处理
    train_dataset, val_dataset = preprocess_data(tokenizer, config)

    # 数据拼接器(用于批量拼接数据,填充到批次最大长度)
    if config.use_lora:
        data_collator = default_data_collator
    else:
        data_collator = DataCollatorForLanguageModeling(
            tokenizer=tokenizer,
            mlm=False,  # 因果语言模型关闭 MLM(掩码语言模型)
        )

    # 定义训练参数
    training_args = TrainingArguments(
        output_dir=config.output_dir,
        per_device_train_batch_size=config.per_device_train_batch_size,
        gradient_accumulation_steps=config.gradient_accumulation_steps,
        num_train_epochs=config.num_train_epochs,
        learning_rate=config.learning_rate,
        weight_decay=config.weight_decay,
        logging_steps=config.logging_steps,
        save_steps=config.save_steps,
        fp16=config.fp16,
        save_total_limit=3,  # 最多保存 3 个 checkpoint
        evaluation_strategy="steps",  # 按步数进行验证
        eval_steps=config.save_steps,  # 每 save_steps 步验证一次
        load_best_model_at_end=True,  # 训练结束后加载最优模型
        deepspeed=config.deepspeed_config_path,  # 指定 DeepSpeed 配置文件
        remove_unused_columns=False,
        report_to="none",  # 关闭 wandb 日志(如需开启,可改为 "wandb")
        seed=42,
    )

    # 初始化 Trainer
    trainer = Trainer(
        model=model,
        args=training_args,
        train_dataset=train_dataset,
        eval_dataset=val_dataset,
        data_collator=data_collator,
        tokenizer=tokenizer,
    )

    # 开始训练
    print("===== 开始训练 Qwen-32B 模型 =====")
    trainer.train()

    # 保存最终模型
    print("===== 训练完成,保存最终模型 =====")
    trainer.save_model(os.path.join(config.output_dir, "final_model"))
    tokenizer.save_pretrained(os.path.join(config.output_dir, "final_model"))


if __name__ == "__main__":
    main()

deepspeed_config.json文件

{
    "train_batch_size": 16,
    "train_micro_batch_size_per_gpu": 1,
    "gradient_accumulation_steps": 16,
    "fp16": {
        "enabled": true,
        "loss_scale": 0,
        "loss_scale_window": 1000,
        "initial_scale_power": 16,
        "hysteresis": 2,
        "min_loss_scale": 1
    },
    "zero_optimization": {
        "stage": 2,
        "allgather_partitions": true,
        "allgather_bucket_size": 5e8,
        "overlap_comm": true,
        "reduce_scatter": true,
        "reduce_bucket_size": 5e8,
        "contiguous_gradients": true
    },
    "optimizer": {
        "type": "AdamW",
        "params": {
            "lr": 2e-5,
            "betas": [0.9, 0.999],
            "eps": 1e-8,
            "weight_decay": 0.01
        }
    },
    "scheduler": {
        "type": "WarmupLR",
        "params": {
            "warmup_min_lr": 0,
            "warmup_max_lr": 2e-5,
            "warmup_num_steps": 1000
        }
    },
    "gradient_clipping": 1.0,
    "steps_per_print": 10,
    "wall_clock_breakdown": false
}

六、deepspeed和llama factory相辅相成

LLaMA Factory:
  1. 定位:上层大模型一站式微调 / 训练工具(开源社区推出),核心聚焦于 “简化大模型调参流程,降低大模型微调的技术门槛”。
  2. 本质:不关注底层的 “显存如何压缩”“梯度如何同步”,而是将 Hugging Face、Peft、DeepSpeed 等底层工具进行封装,提供统一、简洁的配置接口(命令行 / 网页端),让用户无需编写复杂代码,即可快速完成 LLaMA、Qwen、Baichuan 等主流大模型的微调(如 LoRA、全参数微调等)。
  3. 类比:相当于大模型训练的 “驾驶控制台”,负责让用户无需了解 “发动机原理(底层优化)”,即可通过简单的 “按钮和旋钮(配置项)” 操控 “汽车(模型训练)”。
DeepSpeed:
  1. 定位:底层分布式训练 / 推理优化框架(由微软推出),核心聚焦于 “突破大模型训练 / 推理的显存和算力瓶颈”。
  2. 本质:它不直接提供 “便捷的调参流程”,而是为超大模型(如 Qwen-32B、LLaMA-70B)提供底层支撑 —— 通过 ZeRO 系列并行、显存卸载、混合精度训练等技术,降低模型对硬件的要求,提升训练 / 推理的效率。
  3. 类比:相当于大模型训练的 “动力优化系统”,负责让 “汽车(模型训练)” 在有限的 “燃油(硬件资源)” 下跑得更快、更远。

实际生产和科研中,LLaMA Factory 常作为上层调参入口,DeepSpeed 作为底层硬件优化引擎,二者协同使用

流程如下:
  1. 用户通过 LLaMA Factory 的 Web UI 或命令行,配置模型名称(如 Qwen-32B)、数据路径、微调方式(如 QLoRA);
  2. 在配置中指定 DeepSpeed 配置文件(如 --deepspeed ./ds_config.json),启用 DeepSpeed 优化;
  3. LLaMA Factory 自动调用 DeepSpeed 启动器,初始化分布式环境,加载 ZeRO 配置,完成显存优化;
  4. 训练过程中,DeepSpeed 负责底层的梯度同步、显存管理,LLaMA Factory 负责上层的流程调度、参数管理;
  5. 训练完成后,LLaMA Factory 自动完成模型合并与保存,用户可直接使用微调后的模型。

比如LLaMA-Factory中的 Chat Template:

不同大模型在官方预训练或微调阶段,使用了各自固定的对话文本格式(即原生 Chat Template),这种格式是模型 “熟悉” 的输入输出范式。

Chat Template 针对不同模型差异化设计的核心作用,是让训练数据的格式与模型原生预训练 / 微调格式保持一致。

LLaMA Factory 作为一站式微调工具,LLaMA Factory 已经预先内置了主流大模型与其专属 Chat Template 的对应关系,用户无需手动编写模板。当用户选择某个模型(如通过命令行 --model_name_or_path Qwen/Qwen-7B-Chat 或 Web UI 选择模型)时,LLaMA Factory 会自动识别模型类型,并加载对应的预设 Chat Template。LLaMA Factory 也支持用户根据需求自定义 Chat Template(适配小众模型或特殊训练场景)。用户可以通过配置文件或命令行参数,指定自定义的模板格式,覆盖内置模板的默认行为。

LLaMA 3 原生模板(Meta 官方定义,包含特殊标记 <|begin_of_text|><|user|><|assistant|>):

<|begin_of_text|><|user|>你好<|end_of_text|><|assistant|>您好,请问有什么可以帮到您?<|end_of_text|>

Qwen 原生模板(通义千问官方定义,标记更简洁):

用户:你好
助手:您好,请问有什么可以帮到您?

ChatGLM 原生模板(智谱 AI 定义,适配中文对话场景):

<|user|>你好<|assistant|>您好,请问有什么可以帮到您?

 

posted @ 2025-12-29 14:46  wangssd  阅读(6)  评论(0)    收藏  举报