大模型多卡并行的一些尝试

发布于:2025-07-23 ⋅ 阅读:(18) ⋅ 点赞:(0)

前言

需要评估一个大模型 ,不涉及任何训练微调。我的a100有40G显存,8B模型以fp16加载只需要16G显存。在单卡的小型模型很简单,如图直接加载模型,移动上设备,然后传入评估函数即可。
但是,我需要测试一个更大的模型,27B以fp16加载需要54G,根本放不下。于是我申请了一个三卡的集群(A100(40G)*3)。后续就是考虑使用啥方法进行模型配置。

在这里插入图片描述

尝试1torch.nn.DataParallel

OOM(out of memery)失败。

  • torch.nn.DataParallel 仅适合多卡“复制模型+数据并行”,不适合超大模型拆分。
  • 对于超大模型,torch.nn.parallel.DistributedDataParallel + 张量/模型并行更合适,但这需要代码显著改动。
  • PyTorch 并没有开箱即用的“模型分片(tensor/model parallel)”功能,需要额外库(如 Megatron-LM)配合。
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

model_path = "/path/to/your/27B-model"
tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False)

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

model = AutoModelForCausalLM.from_pretrained(
    model_path,
    torch_dtype=torch.float16,
    low_cpu_mem_usage=True,  # 优化CPU内存占用
)
model = model.to(device)

# 多卡包装,适合模型能完整放入单卡的情况
if torch.cuda.device_count() > 1:
    model = torch.nn.DataParallel(model)

def evaluate(input_text):
    inputs = tokenizer(input_text, return_tensors="pt").to(device)
    model.eval()
    with torch.no_grad():
        outputs = model.generate(**inputs, max_length=100)
    print(tokenizer.decode(outputs[0], skip_special_tokens=True))


input_text = "Hello world"
evaluate(input_text)

尝试2accelerate

accelerate 是Huggingface提供的轻量多卡分布式与混合精度训练/推理工具,支持自动拆分模型权重到多卡,适合大模型推理。
成功但是速度些许慢。

from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer
import torch
from accelerate import init_empty_weights, load_checkpoint_and_dispatch, infer_auto_device_map

model_path = "/home/.../model/gemma_27B"

tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False)

# 1. 加载配置
config = AutoConfig.from_pretrained(model_path)

# 2. 用init_empty_weights创建空模型
with init_empty_weights():
    model = AutoModelForCausalLM.from_config(config)

# 3. 绑权重,避免警告
model.tie_weights()

# 4. 指定模型层不拆分的类名,避免层内跨设备切分
# 请替换成你模型中层的实际类名,或者打印确认
no_split_module_classes = ["Gemma2DecoderLayer"]

# 5. 自动推断设备映射,带上 no_split_module_classes 参数
device_map = infer_auto_device_map(
    model,
    max_memory={0: "30GB", 1: "40GB", 2: "40GB"},
    dtype=torch.float16,
    no_split_module_classes=no_split_module_classes,
)

print("Device map:", device_map)
print(type(model.model.layers[24]))

# 6. 加载权重并分配设备,带上 no_split_module_classes
model = load_checkpoint_and_dispatch(
    model,
    model_path,
    device_map=device_map,
    no_split_module_classes=no_split_module_classes,
    dtype=torch.float16,
)

model.eval()

def evaluate(input_text):
    inputs = tokenizer(input_text, return_tensors="pt").to("cuda:0")
    with torch.no_grad():
        outputs = model.generate(**inputs, max_length=100)
    print(tokenizer.decode(outputs[0], skip_special_tokens=True))

input_text = "Hello world"
evaluate(input_text)

尝试3vllm

因为我的模型不需要微调,只需要评估。我就使用了一个很出名的推理框架vllm.python代码是自己写的,脚本代码是我问同门说这个脚本启动更常见。
结果可以顺利运行,但是vllm封装得太好了,我的需求是要保留model供我调用。因此pass。

import os
from vllm import LLM, SamplingParams

# 设置可见的 CUDA 设备
os.environ["CUDA_VISIBLE_DEVICES"] = "0,1,2"

def infer(model_path, prompts):
    # 初始化 LLM,使用 bfloat16 或 float32
    llm = LLM(
        model=model_path,
        tensor_parallel_size=2,
        dtype="bfloat16",  # 或者使用 "float32"
        seed=42,
        disable_log_stats=True,
        trust_remote_code=True,
        gpu_memory_utilization=0.95,
        enforce_eager=True,
    )

    # 设置采样参数
    sampling_params = SamplingParams(
        top_p=1.0,
        top_k=-1,
        n=3,
        temperature=0.5,
        max_tokens=2048
    )

    # 生成输出
    outputs = llm.generate(prompts=prompts, sampling_params=sampling_params)

    # 打印输出
    for output in outputs:
        print(output)

    return None

# 初始化输入
model_path = "/home/.../model/gemma_27B"  # 替换为您的模型路径
prompts = ["这是一个示例输入。"]  # 输入提示,可以是多个

# 调用 infer 函数
_ = infer(model_path, prompts)

#!/bin/bash

# 参数设置
CUDA_VISIBLE_DEVICES=0,1 # GPU编号
TOKENIZERS_PARALLELISM=false # 是否使用多线程分词器
MODEL_NAME="Qwen2.5-VL-7B-Instruct"  # 模型名称
PORT=8000  # 端口号
GPU_MEMORY_UTILIZATION=0.9  # GPU 内存利用率

MODELS_FOLDER="/home/Disk/llm_models/"  # 模型存放路径
TENSOR_PARALLEL_SIZE=2  # 模型并行度
MAX_MODEL_LEN=30000  # 最大模型长度

MODEL_PATH="${MODELS_FOLDER}/${MODEL_NAME}"
SERVED_MODEL_NAME=$(echo $MODEL_NAME | awk -F'/' '{print $NF}')
LOG_FILE="./logs/vllm_${SERVED_MODEL_NAME}.log"  # 日志文件

echo "Starting vLLM server..."
echo "  CUDA_VISIBLE_DEVICES: ${CUDA_VISIBLE_DEVICES}"
echo "  MODEL_PATH: ${MODEL_PATH}"
echo "  SERVED_MODEL_NAME: ${SERVED_MODEL_NAME}"
echo "  PORT: ${PORT}"
echo "  GPU_MEMORY_UTILIZATION: ${GPU_MEMORY_UTILIZATION}"
echo "  LOG_FILE: ${LOG_FILE}"

nohup bash -c "CUDA_VISIBLE_DEVICES=${CUDA_VISIBLE_DEVICES} vllm serve ${MODEL_PATH} --served-model-name ${SERVED_MODEL_NAME} --port ${PORT} --trust-remote-code --tensor-parallel-size ${TENSOR_PARALLEL_SIZE} --gpu-memory-utilization ${GPU_MEMORY_UTILIZATION} --max_model_len ${MAX_MODEL_LEN}" > ${LOG_FILE} 2>&1 &

尝试4 deepspeed

类似3
DeepSpeed的Inference功能支持大模型零冗余优化(ZeRO)、张量并行、流水线并行等,推理效率和显存利用率都不错。
使用门槛比accelerate稍高,需配置相关参数。

import os
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
import deepspeed

os.environ["CUDA_VISIBLE_DEVICES"] = "0,1,2"
model_path = "/home/.../model/gemma_27B"

tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False)

print("Loading model with DeepSpeed inference...")

model = AutoModelForCausalLM.from_pretrained(
    model_path,
    torch_dtype=torch.float16,
    device_map="cpu",   # 先加载到 CPU
    low_cpu_mem_usage=True  # 尽量节省 CPU 内存
)

model = deepspeed.init_inference(
    model,
    mp_size=3,
    dtype=torch.float16,
    replace_method="auto",
    replace_with_kernel_inject=True,
)


model.eval()


def eval(model, tokenizer, input_text, dataset_name):
    print(f"Evaluating on dataset: {dataset_name}")
    inputs = tokenizer(input_text, return_tensors="pt", padding=True)
    device = next(model.module.parameters()).device
    inputs = {k: v.to(device) for k, v in inputs.items()}
    with torch.no_grad():
        outputs = model.generate(
            **inputs,
            max_length=100,
            do_sample=True,
            top_p=0.9,
            temperature=0.7,
            num_return_sequences=1
        )
    generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
    print("Input text:", input_text)
    print("Generated text:", generated_text)


input_text = "Hello, how are you?"
dataset = "ptb_text_only"

eval(model, tokenizer, input_text, dataset)

结论

大模型多卡推理,最简单可用、灵活的方案是 accelerate,适合评估和开发;追求极致性能或服务部署可考虑 DeepSpeed 或 vllm。


网站公告

今日签到

点亮在社区的每一天
去签到