DeepSeek-V4-Flash

对标题的评论会显示在这里

01-DeepSeek-V4-Flash-LoRA 及 SwanLab 可视化记录

对这一段的评论会显示在这里

本节介绍如何基于 transformers、peft 等框架,使用开源 Chat-甄嬛 项目中的嬛嬛数据集对 DeepSeek-V4-Flash 的 BF16 权重进行 LoRA 微调,以构建能够模拟甄嬛对话风格的个性化 LLM。数据集路径为 ../../dataset/huanhuan.jsonl,训练过程使用 SwanLab 记录和可视化指标。

对这一段的评论会显示在这里

LoRA 是一种高效微调方法,深入了解其原理可参见博客:知乎|深入浅出 LoRA

对这一段的评论会显示在这里

训练代码:01-DeepSeek-V4-Flash-LoRA.py
推理代码:01-DeepSeek-V4-Flash-LoRA-Inference.py
模型:DeepSeek-V4-Flash,本文训练使用社区发布的 BF16 权重
数据集:huanhuan
实验硬件:8 张 NVIDIA RTX PRO 6000 Blackwell Server Edition 96GB 显卡

对这一段的评论会显示在这里

目录

对这一段的评论会显示在这里

  1. 环境配置

对这一段的评论会显示在这里

实验所依赖的基础开发环境如下:

对这一段的评论会显示在这里
----------------
ubuntu 22.04
Python 3.12
cuda 13.0
pytorch 2.11.0
----------------
对这一段的评论会显示在这里

本文默认已安装上述 PyTorch 和 CUDA 环境。

对这一段的评论会显示在这里

首先 pip 换源加速下载并安装依赖包:

对这一段的评论会显示在这里
# 升级pip
python -m pip install --upgrade pip
# 更换 pypi 源加速库的安装
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

pip install transformers==5.12.1 # Hugging Face 的模型库,用于加载和训练模型
pip install accelerate==1.14.0 # 用于分布式训练和混合精度训练
pip install datasets==5.0.0 # 用于加载和处理数据集
pip install peft==0.19.1 # 用于 LoRA 微调
pip install swanlab==0.8.4 # 用于记录和可视化训练指标
pip install huggingface_hub # 用于从 Hugging Face 下载模型
pip install kernels==0.14.1 # 用于加载 FP4 + FP8 推理算子
对这一段的评论会显示在这里

  1. 模型下载

对这一段的评论会显示在这里

DeepSeek 官方发布的 deepseek-ai/DeepSeek-V4-Flash 为 FP4 + FP8 混合精度权重,适合推理部署,但不能直接用于本教程的 LoRA 训练。本文训练使用社区发布的 RedHatAI/DeepSeek-V4-Flash-BF16,推理时加载 DeepSeek 官方 FP4 + FP8 权重。

对这一段的评论会显示在这里

使用 huggingface_hub 中的 snapshot_download 函数下载模型。第一个参数 repo_id 为模型名称,第二个参数 local_dir 为模型的下载路径。

对这一段的评论会显示在这里

/root/autodl-tmp 路径下新建 model_download.py 文件并在其中粘贴以下代码,并保存文件。

对这一段的评论会显示在这里
from huggingface_hub import snapshot_download

model_dir = snapshot_download(
    repo_id="RedHatAI/DeepSeek-V4-Flash-BF16",
    local_dir="/root/autodl-tmp/RedHatAI/DeepSeek-V4-Flash-BF16",
)
print(f"模型下载完成,保存路径为:{model_dir}")
对这一段的评论会显示在这里

注意:请将 local_dir 修改为实际的模型下载路径。

对这一段的评论会显示在这里

在终端运行 python /root/autodl-tmp/model_download.py 执行下载,模型体积较大,下载时间较久。

对这一段的评论会显示在这里

推理部分使用官方 FP4 + FP8 权重,可以将下载代码中的 repo_idlocal_dir 分别修改为:

对这一段的评论会显示在这里
repo_id="deepseek-ai/DeepSeek-V4-Flash"
local_dir="/root/autodl-tmp/deepseek-ai/DeepSeek-V4-Flash"
对这一段的评论会显示在这里

注意:BF16 权重约为 532GB,官方 FP4 + FP8 权重约为 149GB。如果同时保存两份权重,建议预留不少于 700GB 的磁盘空间。AutoDL 实例的数据盘空间不足时,可以将权重放到文件存储路径,例如 /root/autodl-fs/models/DeepSeek-V4-Flash-BF16

对这一段的评论会显示在这里

  1. 指令集构建

对这一段的评论会显示在这里

LLM 的微调一般指指令微调过程。所谓指令微调,是说我们使用的微调数据形如:

对这一段的评论会显示在这里
{
  "instruction": "回答以下用户问题,仅输出答案。",
  "input": "1+1等于几?",
  "output": "2"
}
对这一段的评论会显示在这里

其中,instruction 是用户指令,告知模型其需要完成的任务;input 是用户输入,是完成用户指令所必须的输入内容;output 是模型应该给出的输出。

对这一段的评论会显示在这里

即我们的核心训练目标是让模型具有理解并遵循用户指令的能力。因此,在指令集构建时,我们应针对我们的目标任务,针对性构建任务指令集。

对这一段的评论会显示在这里

本节使用 Chat-甄嬛 项目作为示例,目标是构建能够模拟甄嬛对话风格的个性化 LLM,因此指令格式如下:

对这一段的评论会显示在这里
{
  "instruction": "你是谁?",
  "input": "",
  "output": "家父是大理寺少卿甄远道。"
}
对这一段的评论会显示在这里

本节使用的示例数据集位于 ../../dataset/huanhuan.jsonl,共 3729 条 instruction/input/output 数据。

对这一段的评论会显示在这里

  1. 数据格式化

对这一段的评论会显示在这里

LoRA 训练前需要对文本进行格式化和编码。input_ids 保存完整对话的 token ID,labels 中的用户输入部分设为 -100,训练时只计算助手回答部分的损失。

对这一段的评论会显示在这里

DeepSeek-V4-Flash 没有提供 Jinja 格式的 chat_template。本文按照模型仓库 encoding 目录中的官方 chat 模式定义对话格式:

对这一段的评论会显示在这里
def encode_chat_text(system_prompt, user_content, assistant_content=None):
    text = (
        "<|begin▁of▁sentence|>"
        f"{system_prompt}"
        "<|User|>"
        f"{user_content}"
        "<|Assistant|></think>"
    )
    if assistant_content is not None:
        text += f"{assistant_content}<|end▁of▁sentence|>"
    return text
对这一段的评论会显示在这里

然后我们就可以定义预处理函数 process_func,这个函数用于对每一个样本,编码其输入、输出文本并返回一个编码后的字典,方便模型使用:

对这一段的评论会显示在这里
def process_func(example):
    MAX_LENGTH = 2048  # 设置最大序列长度为 2048 个 token
    system_prompt = "现在你要扮演皇帝身边的女人--甄嬛。"
    instruction = str(example.get("instruction") or "")
    user_input = str(example.get("input") or "")
    output = str(example.get("output") or "")
    user_content = instruction + user_input

    prompt_text = encode_chat_text(system_prompt, user_content)
    full_text = encode_chat_text(system_prompt, user_content, output)

    prompt_ids = tokenizer(prompt_text, add_special_tokens=False)["input_ids"]
    full = tokenizer(full_text, add_special_tokens=False)
    input_ids = full["input_ids"]
    attention_mask = full.get("attention_mask", [1] * len(input_ids))
    labels = [-100] * len(prompt_ids) + input_ids[len(prompt_ids):]

    if tokenizer.eos_token_id is not None and (not input_ids or input_ids[-1] != tokenizer.eos_token_id):
        input_ids.append(tokenizer.eos_token_id)
        attention_mask.append(1)
        labels.append(tokenizer.eos_token_id)

    if len(input_ids) > MAX_LENGTH:  # 超出最大序列长度截断
        input_ids = input_ids[:MAX_LENGTH]
        attention_mask = attention_mask[:MAX_LENGTH]
        labels = labels[:MAX_LENGTH]

    return {
        "input_ids": input_ids,
        "attention_mask": attention_mask,
        "labels": labels,
    }
对这一段的评论会显示在这里

加载数据集:

对这一段的评论会显示在这里
from datasets import load_dataset

data_path = "../../dataset/huanhuan.jsonl"
dataset = load_dataset("json", data_files=data_path, split="train")
对这一段的评论会显示在这里

  1. 加载模型和 tokenizer

对这一段的评论会显示在这里

下面加载 tokenizer 和 DeepSeek-V4-Flash 的 BF16 权重。通过 dtype=torch.bfloat16 指定模型精度,并使用 device_map="auto" 将模型自动分配到可见的 GPU。

对这一段的评论会显示在这里
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

model_path = "/root/autodl-tmp/RedHatAI/DeepSeek-V4-Flash-BF16"

tokenizer = AutoTokenizer.from_pretrained(
    model_path,
    use_fast=True,
    trust_remote_code=True,
)
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token
if tokenizer.pad_token_id is None and tokenizer.eos_token_id is not None:
    tokenizer.pad_token_id = tokenizer.eos_token_id
tokenizer.padding_side = "right"

tokenized_id = dataset.map(process_func, remove_columns=dataset.column_names)

model = AutoModelForCausalLM.from_pretrained(
    model_path,
    dtype=torch.bfloat16,
    device_map="auto",
    trust_remote_code=True,
    low_cpu_mem_usage=True,
)
对这一段的评论会显示在这里

注意:请将 model_path 修改为实际的模型路径。

对这一段的评论会显示在这里

注意:这里需要填写 BF16 权重路径,不要使用官方 FP4 + FP8 权重路径进行训练。

对这一段的评论会显示在这里

注意:DeepSeek-V4-Flash 模型较大,建议使用 device_map="auto" 进行加载,避免在每张显卡上完整复制一份模型。

对这一段的评论会显示在这里

如果想要查看模型结构,可以打印模型:

对这一段的评论会显示在这里
print(model)
对这一段的评论会显示在这里

在 DeepSeek-V4-Flash 的 attention 模块中,可以看到与常见 LLaMA/Qwen 模型不同的投影层名称,例如 q_a_projq_b_projkv_projo_a_projo_b_proj。因此本文不会使用 q_projk_projv_projo_proj 作为 LoRA target。

对这一段的评论会显示在这里

  1. 定义 LoRA 配置

对这一段的评论会显示在这里

LoraConfig 用于设置 LoRA 微调参数,本文使用的主要参数如下:

对这一段的评论会显示在这里

task_type:微调任务类型,因果语言模型使用 CAUSAL_LM
target_modules:注入 LoRA 的模型层名称。
r:LoRA 的秩。
lora_alpha:LoRA 的缩放参数。
lora_dropout:LoRA 层的 Dropout 比例。

对这一段的评论会显示在这里

本文配置的 LoRA 缩放系数为 lora_alpha / r = 2

对这一段的评论会显示在这里
from peft import LoraConfig, TaskType, get_peft_model

config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,
    target_modules=["q_a_proj", "q_b_proj", "kv_proj", "o_b_proj"],
    inference_mode=False,     # 训练模式
    r=16,                     # LoRA 秩
    lora_alpha=32,            # LoRA 缩放参数
    lora_dropout=0.05         # Dropout 比例
)

model = get_peft_model(model, config)
model.print_trainable_parameters()
对这一段的评论会显示在这里

注意:这里没有注入 o_a_projo_a_proj 在 DeepSeek-V4-Flash 中是 grouped linear 结构,PEFT 会将其按普通 Linear 处理,训练时可能触发 grouped shape mismatch。因此本文只选择 q_a_projq_b_projkv_projo_b_proj

对这一段的评论会显示在这里

本教程配置下,可训练参数量约为 46,149,632。

对这一段的评论会显示在这里

  1. 自定义 TrainingArguments 参数

对这一段的评论会显示在这里

TrainingArguments 用于配置训练过程,本文使用的主要参数如下:

对这一段的评论会显示在这里

output_dir:模型的输出路径
per_device_train_batch_size:单次迭代的样本数。
gradient_accumulation_steps:梯度累积步数。
logging_steps:训练日志记录间隔。
max_steps:最大训练步数
gradient_checkpointing:使用计算时间换取更低的显存占用。

对这一段的评论会显示在这里
from transformers import TrainingArguments

args = TrainingArguments(
    output_dir="./output/DeepSeek-V4-Flash-LoRA",
    per_device_train_batch_size=1,
    gradient_accumulation_steps=16,
    logging_steps=1,
    max_steps=200,
    save_steps=100,
    save_total_limit=2,
    learning_rate=5e-5,
    save_on_each_node=True,
    bf16=True,
    fp16=False,
    gradient_checkpointing=True,
    gradient_checkpointing_kwargs={"use_reentrant": False},
    remove_unused_columns=False,
    report_to=[],
)
对这一段的评论会显示在这里

开启梯度检查点:

对这一段的评论会显示在这里
model.config.use_cache = False
if hasattr(model, "enable_input_require_grads"):
    model.enable_input_require_grads()
if hasattr(model, "gradient_checkpointing_enable"):
    model.gradient_checkpointing_enable(gradient_checkpointing_kwargs={"use_reentrant": False})
对这一段的评论会显示在这里

  1. SwanLab 可视化

对这一段的评论会显示在这里

SwanLab 简介

对这一段的评论会显示在这里

SwanLab 是一个开源的模型训练记录工具,面向 AI 研究者,提供了训练可视化、自动日志记录、超参数记录、实验对比、多人协同等功能。在 SwanLab 上,研究者能基于直观的可视化图表发现训练问题,对比多个实验找到研究灵感,并通过在线链接的分享与基于组织的多人协同训练,打破团队沟通的壁垒。

对这一段的评论会显示在这里

为什么要记录训练

对这一段的评论会显示在这里

相较于软件开发,模型训练更像一个实验科学。一个品质优秀的模型背后,往往是成千上万次实验。研究者需要不断尝试、记录、对比,积累经验,才能找到最佳的模型结构、超参数与数据配比。在这之中,如何高效进行记录与对比,对于研究效率的提升至关重要。

对这一段的评论会显示在这里

实例化 SwanLabCallback

对这一段的评论会显示在这里

建议先在 SwanLab 官网 注册账号,然后在训练初始化阶段选择

对这一段的评论会显示在这里

(2) Use an existing SwanLab account 并使用 private API Key 登录

对这一段的评论会显示在这里

SwanLab 与 Transformers 已经做好了集成,用法是在 Trainer 的 callbacks 参数中添加 SwanLabCallback 实例,就可以自动记录超参数和训练指标,简化代码如下:

对这一段的评论会显示在这里
from swanlab.integration.transformers import SwanLabCallback

swanlab_callback = SwanLabCallback(
    project="deepseek-v4-flash",
    experiment_name="huanhuan-r16-a32-200step",
)
对这一段的评论会显示在这里

独立训练脚本默认不启用 SwanLab,只有传入 --swanlab-project 时才会创建 SwanLabCallback;不启用时保持 TrainingArguments(report_to=[])

对这一段的评论会显示在这里

  1. 使用 Trainer 训练

对这一段的评论会显示在这里

我们使用 Trainer 类来管理训练过程。TrainingArguments 用于设置训练参数,Trainer 则负责实际的训练逻辑。

对这一段的评论会显示在这里
from transformers import DataCollatorForSeq2Seq, Trainer

trainer = Trainer(
    model=model,                 # 要训练的模型
    args=args,                   # 训练参数
    train_dataset=tokenized_id,  # 训练数据集
    data_collator=DataCollatorForSeq2Seq(
        tokenizer=tokenizer,
        padding=True,
        label_pad_token_id=-100,
    ),
    callbacks=[swanlab_callback],
)

trainer.train()                  # 开始训练
trainer.save_model(args.output_dir)
tokenizer.save_pretrained(args.output_dir)
对这一段的评论会显示在这里

完整训练代码见 01-DeepSeek-V4-Flash-LoRA.py。在 8 张显卡的环境中,可以使用以下命令启动训练并开启 SwanLab 记录:

对这一段的评论会显示在这里
python 01-DeepSeek-V4-Flash-LoRA.py \
    --swanlab-project deepseek-v4-flash \
    --swanlab-experiment huanhuan-r16-a32-200step
对这一段的评论会显示在这里

  1. 训练结果演示

对这一段的评论会显示在这里

训练完成后,打开 SwanLab,可以查看训练过程中记录的参数和训练指标。

对这一段的评论会显示在这里

访问可视化训练过程:huanhuan-r16-a32-200step

对这一段的评论会显示在这里

在 SwanLab 上查看训练过程中的 loss、grad_norm、learning_rate 和 epoch 变化:

对这一段的评论会显示在这里
SwanLab 训练过程
SwanLab 训练过程
对这一段的评论会显示在这里

本次实验完成 200 step,训练耗时约 74 分钟,最终 train_loss 为 2.605。

对这一段的评论会显示在这里

训练完成后的 LoRA 权重会保存在:

对这一段的评论会显示在这里
./output/DeepSeek-V4-Flash-LoRA
对这一段的评论会显示在这里

至此,DeepSeek-V4-Flash 的 LoRA 微调训练已完成。如果需要进一步改善效果,可以增加训练步数或根据任务构建更高质量的数据集。

对这一段的评论会显示在这里

  1. 加载 LoRA 权重推理

对这一段的评论会显示在这里

得到 checkpoint 之后,加载官方 FP4 + FP8 基础模型并挂载 LoRA 权重进行推理。完整推理代码见 01-DeepSeek-V4-Flash-LoRA-Inference.py

对这一段的评论会显示在这里
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
from peft.functional import cast_adapter_dtype

model_path = "/root/autodl-tmp/deepseek-ai/DeepSeek-V4-Flash" # 官方 FP4 + FP8 基础模型路径
lora_path = "./output/DeepSeek-V4-Flash-LoRA" # 训练得到的 LoRA 权重路径,按实际填写


def encode_chat_text(system_prompt, user_content, assistant_content=None):
    text = (
        "<|begin▁of▁sentence|>"
        f"{system_prompt}"
        "<|User|>"
        f"{user_content}"
        "<|Assistant|></think>"
    )
    if assistant_content is not None:
        text += f"{assistant_content}<|end▁of▁sentence|>"
    return text


# 加载 tokenizer
tokenizer = AutoTokenizer.from_pretrained(lora_path, use_fast=True, trust_remote_code=True)
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token
if tokenizer.pad_token_id is None and tokenizer.eos_token_id is not None:
    tokenizer.pad_token_id = tokenizer.eos_token_id

# 加载官方 FP4 + FP8 基础模型
model = AutoModelForCausalLM.from_pretrained(
    model_path,
    dtype=torch.bfloat16,
    device_map="auto",
    trust_remote_code=True,
    low_cpu_mem_usage=True,
)

# 加载 LoRA 权重,并将 adapter 权重转换为可计算的浮点精度
model.load_adapter(
    lora_path,
    adapter_name="default",
    low_cpu_mem_usage=True,
)
cast_adapter_dtype(model, adapter_name="default")
model.set_adapter("default")
model.eval()

# 使用 DeepSeek-V4 官方 chat 模式构造对话
text = encode_chat_text(
    system_prompt="现在你要扮演皇帝身边的女人--甄嬛。",
    user_content="你是谁?",
)

inputs = tokenizer(text, return_tensors="pt").to(model.device)

with torch.no_grad():
    generated_ids = model.generate(
        **inputs,
        max_new_tokens=128,
        do_sample=False,
        pad_token_id=tokenizer.pad_token_id,
        eos_token_id=tokenizer.eos_token_id,
)

output_ids = generated_ids[0][inputs["input_ids"].shape[1]:]
generate_text = tokenizer.decode(output_ids, skip_special_tokens=True)
generate_text = generate_text.split("<|end▁of▁sentence|>", 1)[0].strip()
print(generate_text)
对这一段的评论会显示在这里

运行推理脚本:

对这一段的评论会显示在这里
python 01-DeepSeek-V4-Flash-LoRA-Inference.py \
    --model-path /root/autodl-tmp/deepseek-ai/DeepSeek-V4-Flash \
    --adapter-path ./output/DeepSeek-V4-Flash-LoRA
对这一段的评论会显示在这里

输出示例:

对这一段的评论会显示在这里
我是甄嬛,家父是大理寺少卿甄远道。
对这一段的评论会显示在这里

从输出可以看出,LoRA 权重已成功加载,模型能够按照训练数据中的人物设定回答。

对这一段的评论会显示在这里

02-DeepSeek-V4-Flash SGLang 部署

对这一段的评论会显示在这里

本节介绍如何使用 SGLang 部署 DeepSeek-V4-Flash,并提供 OpenAI 兼容接口。教程包含两种部署方式:

对这一段的评论会显示在这里

RTX PRO 6000 Blackwell:使用 SGLang 官方 Docker 镜像和官方验证配置;
NVIDIA H200:使用 Python 环境部署,本文已在单机 4 张 H200 上完成启动和接口验证。

对这一段的评论会显示在这里

本文使用 DeepSeek 官方发布的 DeepSeek-V4-Flash Instruct 权重。该权重约 149GB,共 46 个 safetensors 分片,其中 MoE 专家为 FP4,Attention 和 Dense 层为 FP8。

对这一段的评论会显示在这里

模型:deepseek-ai/DeepSeek-V4-Flash
框架:SGLang
官方部署配置:DeepSeek-V4 SGLang Cookbook
实测硬件:4 张 NVIDIA H200 141GB

对这一段的评论会显示在这里

目录

对这一段的评论会显示在这里

  1. 模型与硬件准备

对这一段的评论会显示在这里

1.1 下载模型

对这一段的评论会显示在这里

可以使用 Hugging Face CLI 下载模型:

对这一段的评论会显示在这里
pip install -U huggingface_hub
hf download deepseek-ai/DeepSeek-V4-Flash \
    --local-dir /root/autodl-fs/models/DeepSeek-V4-Flash
对这一段的评论会显示在这里

国内网络环境也可以使用 ModelScope:

对这一段的评论会显示在这里
pip install -U modelscope
modelscope download \
    --model deepseek-ai/DeepSeek-V4-Flash \
    --local_dir /root/autodl-fs/models/DeepSeek-V4-Flash
对这一段的评论会显示在这里

下载完成后检查分片数量和残留文件:

对这一段的评论会显示在这里
MODEL_PATH=/root/autodl-fs/models/DeepSeek-V4-Flash

ls "$MODEL_PATH"/model-*.safetensors | wc -l
ls "$MODEL_PATH"/*.incomplete 2>/dev/null || echo "no incomplete"
du -sh "$MODEL_PATH"
test -f "$MODEL_PATH/model.safetensors.index.json" && echo "index OK"
对这一段的评论会显示在这里

正常情况下应输出 46 个分片、没有 .incomplete 文件,模型目录约为 149GB。

对这一段的评论会显示在这里
模型权重检查
模型权重检查
对这一段的评论会显示在这里

1.2 检查硬件

对这一段的评论会显示在这里
nvidia-smi
nvidia-smi topo -m
对这一段的评论会显示在这里

H200 实测环境还应记录当前软件版本:

对这一段的评论会显示在这里
python - <<'PY'
from importlib.metadata import version
import torch

print("sglang:", version("sglang"))
print("sglang-kernel:", version("sglang-kernel"))
print("torch:", torch.__version__)
print("torch cuda:", torch.version.cuda)
print("cuda available:", torch.cuda.is_available())
print("gpu count:", torch.cuda.device_count())
for index in range(torch.cuda.device_count()):
    print(index, torch.cuda.get_device_name(index))
PY
对这一段的评论会显示在这里

提交前补充 images/02-01.png:同一张终端截图中保留 4 张 H200、驱动、CUDA、SGLang、SGLang Kernel 和 PyTorch 版本。

对这一段的评论会显示在这里

  1. RTX PRO 6000 官方 Docker 部署

对这一段的评论会显示在这里

SGLang 官方 Cookbook 已验证 RTX PRO 6000 的 DeepSeek-V4-Flash 低延迟配置。该配置使用两张 96GB RTX PRO 6000、TP=2 和 FlashInfer MXFP4 MoE 后端。RTX PRO 6000 只支持 Flash 模型;不要在该配置中启用 HiCache 或 MegaMoE。

对这一段的评论会显示在这里

注意:如果你在云服务器上部署,一般不提供 Docker 运行时或需要私有云部署Docker,建议直接跳转第三节。

对这一段的评论会显示在这里

2.1 拉取镜像

对这一段的评论会显示在这里
docker pull lmsysorg/sglang:latest
对这一段的评论会显示在这里

2.2 启动服务

对这一段的评论会显示在这里

假设模型位于 /data/models/DeepSeek-V4-Flash

对这一段的评论会显示在这里
docker run --rm \
  --gpus '"device=0,1"' \
  --shm-size 32g \
  --ipc=host \
  -p 30000:30000 \
  -v /data/models/DeepSeek-V4-Flash:/models/DeepSeek-V4-Flash:ro \
  lmsysorg/sglang:latest \
  sglang serve \
    --trust-remote-code \
    --model-path /models/DeepSeek-V4-Flash \
    --served-model-name deepseek-v4-flash \
    --tp 2 \
    --moe-runner-backend flashinfer_mxfp4 \
    --mem-fraction-static 0.92 \
    --cuda-graph-max-bs-decode 32 \
    --reasoning-parser deepseek-v4 \
    --tool-call-parser deepseekv4 \
    --host 0.0.0.0 \
    --port 30000
对这一段的评论会显示在这里

如果服务器有 4 张 RTX PRO 6000,上述命令仍只使用 GPU 0 和 GPU 1。需要换卡时修改 device=0,1

对这一段的评论会显示在这里

服务启动后可直接执行第 4 节的接口测试。

对这一段的评论会显示在这里

  1. 4×H200 Python 环境部署

对这一段的评论会显示在这里

3.1 创建环境

对这一段的评论会显示在这里

本文实测环境使用 Python 3.12、PyTorch 2.11.0+cu130 和 4 张 H200:

对这一段的评论会显示在这里
conda create -n sglang python=3.12 -y
conda activate sglang

export UV_CACHE_DIR=/dev/shm/uv-cache-sglang
export UV_DEFAULT_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple
export UV_LINK_MODE=copy
mkdir -p "$UV_CACHE_DIR"

python -m pip install --upgrade pip
python -m pip install uv
uv pip install --prerelease=allow sglang
uv pip install --force-reinstall sglang-kernel \
  --index-url https://docs.sglang.ai/whl/cu130/
对这一段的评论会显示在这里

安装后检查 CUDA 和 DeepSeek-V4 parser:

对这一段的评论会显示在这里
python -c "import torch; print(torch.__version__, torch.cuda.is_available(), torch.cuda.device_count())"
python -m sglang.launch_server --help | grep -o "deepseek-v4" | head
对这一段的评论会显示在这里
SGLang 环境检查
SGLang 环境检查
对这一段的评论会显示在这里

3.2 启动服务

对这一段的评论会显示在这里

H200 可以直接加载官方 FP4+FP8 混合权重。本文采用 TP=4 和 Marlin W4A16 MoE 后端,并将上下文长度先设为 128K:

对这一段的评论会显示在这里
conda activate sglang

export CUDA_VISIBLE_DEVICES=0,1,2,3
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True

sglang serve \
  --model-path /root/autodl-fs/models/DeepSeek-V4-Flash \
  --served-model-name deepseek-v4-flash \
  --trust-remote-code \
  --tp 4 \
  --host 0.0.0.0 \
  --port 30000 \
  --mem-fraction-static 0.85 \
  --context-length 131072 \
  --moe-runner-backend marlin \
  --reasoning-parser deepseek-v4 \
  --tool-call-parser deepseekv4 \
  --speculative-algorithm EAGLE \
  --speculative-num-steps 3 \
  --speculative-eagle-topk 1 \
  --speculative-num-draft-tokens 4
对这一段的评论会显示在这里

首次启动需要完成模型加载、内核预热和 CUDA Graph 捕获,耗时会明显长于后续启动。日志末尾出现以下内容表示服务已经就绪:

对这一段的评论会显示在这里
Application startup complete.
Uvicorn running on http://0.0.0.0:30000
The server is fired up and ready to roll!
对这一段的评论会显示在这里
H200 服务启动成功
H200 服务启动成功
对这一段的评论会显示在这里

另开一个终端检查 4 张卡的显存:

对这一段的评论会显示在这里
nvidia-smi
对这一段的评论会显示在这里

提交前补充 images/02-09.png:服务加载完成后的 4 张 H200 显存占用。

对这一段的评论会显示在这里

  1. 接口调用

对这一段的评论会显示在这里

SGLang 提供 OpenAI 兼容接口,默认地址为 http://127.0.0.1:30000/v1

对这一段的评论会显示在这里

4.1 普通对话

对这一段的评论会显示在这里
curl -sS http://127.0.0.1:30000/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model":"deepseek-v4-flash",
    "messages":[{"role":"user","content":"你好,请用一句话介绍自己。"}],
    "max_tokens":256,
    "temperature":1.0,
    "top_p":1.0
  }' | python -m json.tool --no-ensure-ascii
对这一段的评论会显示在这里
普通对话测试
普通对话测试
对这一段的评论会显示在这里

4.2 流式输出

对这一段的评论会显示在这里

先安装 OpenAI Python SDK:

对这一段的评论会显示在这里
uv pip install openai
对这一段的评论会显示在这里

创建 test_chat_stream.py

对这一段的评论会显示在这里
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:30000/v1", api_key="EMPTY")

response = client.chat.completions.create(
    model="deepseek-v4-flash",
    messages=[{"role": "user", "content": "请简要介绍 DeepSeek-V4-Flash 的 MoE 架构。"}],
    max_tokens=512,
    temperature=1.0,
    top_p=1.0,
    stream=True,
)

for chunk in response:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
print()
对这一段的评论会显示在这里
python test_chat_stream.py
对这一段的评论会显示在这里
流式输出测试
流式输出测试
对这一段的评论会显示在这里

4.3 思考模式

对这一段的评论会显示在这里

创建 test_reasoning.py

对这一段的评论会显示在这里
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:30000/v1", api_key="EMPTY")

response = client.chat.completions.create(
    model="deepseek-v4-flash",
    messages=[{"role": "user", "content": "9.9 和 9.11 哪个更大?请解释。"}],
    max_tokens=1024,
    temperature=1.0,
    top_p=1.0,
    extra_body={"chat_template_kwargs": {"thinking": True}},
    stream=True,
)

thinking_started = False
answer_started = False

for chunk in response:
    if not chunk.choices:
        continue
    delta = chunk.choices[0].delta
    if getattr(delta, "reasoning_content", None):
        if not thinking_started:
            print("\n===== 思考 =====")
            thinking_started = True
        print(delta.reasoning_content, end="", flush=True)
    if delta.content:
        if thinking_started and not answer_started:
            print("\n===== 回答 =====")
            answer_started = True
        print(delta.content, end="", flush=True)
print()
对这一段的评论会显示在这里
python test_reasoning.py
对这一段的评论会显示在这里
思考模式测试
思考模式测试
对这一段的评论会显示在这里

4.4 工具调用

对这一段的评论会显示在这里

创建 test_tool.py

对这一段的评论会显示在这里
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:30000/v1", api_key="EMPTY")

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "查询指定城市的天气",
        "parameters": {
            "type": "object",
            "properties": {"location": {"type": "string"}},
            "required": ["location"],
        },
    },
}]

response = client.chat.completions.create(
    model="deepseek-v4-flash",
    messages=[{"role": "user", "content": "北京今天天气如何?"}],
    tools=tools,
    extra_body={"chat_template_kwargs": {"thinking": True}},
)

print(response.choices[0].message.tool_calls)
对这一段的评论会显示在这里
python test_tool.py
对这一段的评论会显示在这里
工具调用测试
工具调用测试
对这一段的评论会显示在这里

  1. 实验验证

对这一段的评论会显示在这里

本教程在 4 张 NVIDIA H200 上完成了模型加载、普通对话、流式输出、思考模式和工具调用验证。模型加载后可以通过 nvidia-smi 查看各卡显存占用和请求期间的 GPU 利用率。

对这一段的评论会显示在这里

完成以上测试后,DeepSeek-V4-Flash 即可通过 SGLang 的 OpenAI 兼容接口对外提供服务。

对这一段的评论会显示在这里

03-DeepSeek-V4-Flash vLLM 部署

对这一段的评论会显示在这里

本节介绍如何在单机 4 张 NVIDIA RTX PRO 6000 Blackwell Server Edition 96GB 上,使用 vLLM 部署 DeepSeek-V4-Flash,并提供 OpenAI 兼容接口。教程包含两种部署方式:

对这一段的评论会显示在这里

本地工作站或裸金属服务器:使用 vLLM 官方 Docker 镜像部署;
不提供 Docker 运行时的云平台:在 Python 环境中编译 SM120 固定预览分支,本文已完成启动和接口验证。

对这一段的评论会显示在这里

RTX PRO 6000 的计算能力为 SM120。官方 Docker 路线使用已经包含 FlashInfer 0.6.14 的固定 nightly 镜像;Python 路线使用针对 SM120 的 vLLM 固定预览分支,完整编译 CUDA 扩展后直接加载 DeepSeek 官方 FP4+FP8 混合权重。

对这一段的评论会显示在这里

模型:deepseek-ai/DeepSeek-V4-Flash
官方镜像:vllm/vllm-openai
上游支持:vLLM PR #41834
固定源码:jasl/vllm@sm120-pr-41834-stable-preview-20260717
实测硬件:4 张 NVIDIA RTX PRO 6000 Blackwell Server Edition 96GB

对这一段的评论会显示在这里

Docker 路线使用 vLLM 官方 nightly 镜像,Python 路线采用社区预览分支,不是 vLLM 正式稳定版;复现实验时应使用文中固定的 tag 和 commit,不要直接替换为不断变化的 main 分支。

对这一段的评论会显示在这里

目录

对这一段的评论会显示在这里

  1. 环境与模型检查

对这一段的评论会显示在这里

1.1 磁盘规划

对这一段的评论会显示在这里

本文使用以下目录:

对这一段的评论会显示在这里

| 内容 | 路径 | 建议空间 |
| 模型权重 | /root/autodl-fs/models/DeepSeek-V4-Flash | 约 149GB |
| Python 环境、源码和编译产物 | /root/autodl-tmp/dsv4-vllm-sm120 | 至少 100GB |
| 下载和解压缓存 | /dev/shm/dsv4-vllm-sm120 | 至少 20GB |

对这一段的评论会显示在这里

不要把 Python 环境和源码编译目录放在 30GB 的系统盘中。/dev/shm 只存放缓存,不要把 conda 环境安装到其中。

对这一段的评论会显示在这里
df -h /root/autodl-tmp /root/autodl-fs /dev/shm
df -i /root/autodl-tmp /root/autodl-fs /dev/shm
对这一段的评论会显示在这里

1.2 下载模型

对这一段的评论会显示在这里

可以使用 Hugging Face CLI 下载模型:

对这一段的评论会显示在这里
python -m pip install -U huggingface_hub
hf download deepseek-ai/DeepSeek-V4-Flash \
  --local-dir /root/autodl-fs/models/DeepSeek-V4-Flash
对这一段的评论会显示在这里

国内网络环境也可以使用 ModelScope:

对这一段的评论会显示在这里
python -m pip install -U modelscope
modelscope download \
  --model deepseek-ai/DeepSeek-V4-Flash \
  --local_dir /root/autodl-fs/models/DeepSeek-V4-Flash
对这一段的评论会显示在这里

两种方式选择一种即可,不要重复下载。

对这一段的评论会显示在这里

1.3 检查 GPU、CUDA 和模型

对这一段的评论会显示在这里
nvidia-smi
nvcc --version
nvidia-smi topo -m

MODEL_PATH=/root/autodl-fs/models/DeepSeek-V4-Flash
ls "$MODEL_PATH"/model-*.safetensors | wc -l
ls "$MODEL_PATH"/*.incomplete 2>/dev/null || echo "no incomplete"
test -f "$MODEL_PATH/model.safetensors.index.json" && echo "index OK"
对这一段的评论会显示在这里

检查结果应满足:

对这一段的评论会显示在这里

4 张 RTX PRO 6000 Blackwell Server Edition,每张约 96GB;
nvcc 为 CUDA 13.0;
模型包含 46 个权重分片,且没有 .incomplete 文件。

对这一段的评论会显示在这里
CUDA、拓扑和模型检查
CUDA、拓扑和模型检查
对这一段的评论会显示在这里

  1. RTX PRO 6000 官方 Docker 部署

对这一段的评论会显示在这里

该方式适合已经安装 Docker、NVIDIA 驱动和 NVIDIA Container Toolkit 的本地工作站或裸金属服务器。

对这一段的评论会显示在这里

注意:云服务器一般不提供 Docker 运行时,或需要使用私有云部署 Docker。遇到这种情况请跳转到第 3 节。

对这一段的评论会显示在这里

先确认容器能够识别 4 张显卡:

对这一段的评论会显示在这里
docker version
docker run --rm --gpus all nvidia/cuda:13.0.1-base-ubuntu22.04 nvidia-smi
对这一段的评论会显示在这里

本文固定使用下面的官方 nightly 镜像。不要替换成 vllm/vllm-openai:v0.25.1:该稳定镜像固定使用 FlashInfer 0.6.13,不包含本配置所需的 FlashInfer 0.6.14。

对这一段的评论会显示在这里
export VLLM_IMAGE=vllm/vllm-openai:nightly-c71a583aa9f81400528e67e3d818f66b804e8340
docker pull "$VLLM_IMAGE"
对这一段的评论会显示在这里

假设模型位于 /data/models/DeepSeek-V4-Flash,执行:

对这一段的评论会显示在这里
export MODEL_PATH=/data/models/DeepSeek-V4-Flash

docker run --rm \
  --runtime nvidia \
  --gpus '"device=0,1,2,3"' \
  --ipc=host \
  -p 8000:8000 \
  -e NCCL_P2P_DISABLE=1 \
  -e VLLM_ENGINE_READY_TIMEOUT_S=3600 \
  -v "$MODEL_PATH":/models/DeepSeek-V4-Flash:ro \
  "$VLLM_IMAGE" \
    --model /models/DeepSeek-V4-Flash \
    --served-model-name deepseek-v4-flash \
    --trust-remote-code \
    --tensor-parallel-size 4 \
    --enable-expert-parallel \
    --kv-cache-dtype fp8 \
    --block-size 256 \
    --gpu-memory-utilization 0.85 \
    --max-model-len 131072 \
    --max-num-seqs 4 \
    --max-num-batched-tokens 4096 \
    --kernel-config '{"moe_backend":"marlin"}' \
    --tokenizer-mode deepseek_v4 \
    --reasoning-parser deepseek_v4 \
    --tool-call-parser deepseek_v4 \
    --enable-auto-tool-choice \
    --disable-custom-all-reduce \
    --enforce-eager \
    --host 0.0.0.0 \
    --port 8000
对这一段的评论会显示在这里

其中 marlin 用于避免 SM120 误选只支持 SM100 的 DeepGEMM MegaMoE 后端。该官方镜像路线先以 128K 上下文和 Eager 模式验证基础服务,不要启用 MTP 或 DSpark 推测解码。日志出现 Application startup complete 后,可以直接执行第 7 节的接口测试。

对这一段的评论会显示在这里

云平台若不提供 Docker 运行时,请继续使用下面的 Python 源码编译路线。

对这一段的评论会显示在这里

  1. 创建编译环境

对这一段的评论会显示在这里

3.1 设置目录

对这一段的评论会显示在这里
export VLLM_RUN=/root/autodl-tmp/dsv4-vllm-sm120
export VLLM_ENV="$VLLM_RUN/env"
export VLLM_SRC="$VLLM_RUN/vllm"
export VLLM_SHM=/dev/shm/dsv4-vllm-sm120
export WHEEL_DIR="$VLLM_RUN/wheels"

export CONDA_PKGS_DIRS="$VLLM_SHM/conda-pkgs"
export UV_CACHE_DIR="$VLLM_SHM/uv-cache"
export PIP_CACHE_DIR="$VLLM_SHM/pip-cache"
export TMPDIR="$VLLM_RUN/tmp"
export XDG_CACHE_HOME="$VLLM_RUN/cache/xdg"
export TORCHINDUCTOR_CACHE_DIR="$VLLM_RUN/cache/torchinductor"
export UV_LINK_MODE=copy

mkdir -p "$VLLM_RUN" "$CONDA_PKGS_DIRS" "$UV_CACHE_DIR" \
  "$PIP_CACHE_DIR" "$TMPDIR" "$XDG_CACHE_HOME" \
  "$TORCHINDUCTOR_CACHE_DIR" "$WHEEL_DIR"
对这一段的评论会显示在这里

3.2 创建 Python 环境

对这一段的评论会显示在这里
conda create -p "$VLLM_ENV" python=3.12 pip -y
conda activate "$VLLM_ENV"

python -m pip install --no-cache-dir --upgrade pip uv \
  -i https://pypi.tuna.tsinghua.edu.cn/simple
对这一段的评论会显示在这里

3.3 获取固定源码

对这一段的评论会显示在这里
git -c http.version=HTTP/1.1 clone --depth 1 \
  --branch sm120-pr-41834-stable-preview-20260717 \
  https://github.com/jasl/vllm.git "$VLLM_SRC"

cd "$VLLM_SRC"
git describe --tags --always
git rev-parse HEAD
对这一段的评论会显示在这里

预期输出为:

对这一段的评论会显示在这里
sm120-pr-41834-stable-preview-20260717
f63bfd3d7b425b10e0b5e0e2c130fe113a85d009
对这一段的评论会显示在这里
vLLM 固定源码版本
vLLM 固定源码版本
对这一段的评论会显示在这里

如果服务器无法访问 GitHub,可以在本地电脑下载并上传:

对这一段的评论会显示在这里
# 本地电脑
cd ~/Downloads
git -c http.version=HTTP/1.1 clone --depth 1 \
  --branch sm120-pr-41834-stable-preview-20260717 \
  https://github.com/jasl/vllm.git vllm
tar -czf vllm-sm120-20260717.tar.gz vllm
对这一段的评论会显示在这里

将压缩包上传到 /root/autodl-fs 后解压:

对这一段的评论会显示在这里
mkdir -p /root/autodl-tmp/dsv4-vllm-sm120
tar -xzf /root/autodl-fs/vllm-sm120-20260717.tar.gz \
  -C /root/autodl-tmp/dsv4-vllm-sm120
对这一段的评论会显示在这里

  1. 安装 CUDA 依赖

对这一段的评论会显示在这里

4.1 安装构建依赖

对这一段的评论会显示在这里
export VLLM_RUN=/root/autodl-tmp/dsv4-vllm-sm120
export VLLM_SRC="$VLLM_RUN/vllm"
export VLLM_SHM=/dev/shm/dsv4-vllm-sm120
export WHEEL_DIR="$VLLM_RUN/wheels"
export UV_CACHE_DIR="$VLLM_SHM/uv-cache"
export UV_LINK_MODE=copy
export UV_DEFAULT_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple
export UV_HTTP_TIMEOUT=600
export UV_HTTP_CONNECT_TIMEOUT=60
export UV_HTTP_RETRIES=10

conda activate "$VLLM_RUN/env"
cd "$VLLM_SRC"

uv pip install --verbose --no-progress \
  -r requirements/build/cuda.txt \
  --torch-backend=cu130
对这一段的评论会显示在这里

4.2 安装 FlashInfer

对这一段的评论会显示在这里

该分支固定使用:

对这一段的评论会显示在这里

flashinfer-cubin==0.6.14,约 436.7MiB;
flashinfer-python==0.6.14,约 13.9MiB。

对这一段的评论会显示在这里

如果云服务器不能访问 GitHub Release,先在本地电脑下载:

对这一段的评论会显示在这里
cd ~/Downloads

curl -L --fail -C - \
  -o flashinfer_cubin-0.6.14-py3-none-any.whl \
  https://github.com/flashinfer-ai/flashinfer/releases/download/v0.6.14/flashinfer_cubin-0.6.14-py3-none-any.whl

curl -L --fail -C - \
  -o flashinfer_python-0.6.14-py3-none-any.whl \
  https://pypi.tuna.tsinghua.edu.cn/packages/f2/8f/b101913cb2b3687654f56681cfe9836d447526be663c149966470ef70531/flashinfer_python-0.6.14-py3-none-any.whl

shasum -a 256 flashinfer_cubin-0.6.14-py3-none-any.whl
shasum -a 256 flashinfer_python-0.6.14-py3-none-any.whl
对这一段的评论会显示在这里

对应的 SHA256 为:

对这一段的评论会显示在这里
7bbed9f3851b59f3f6f6cb344810775bbce8a1c012ecf9a502bebd91cfb6433e  flashinfer_cubin-0.6.14-py3-none-any.whl
d124369346a3d48eac67e31c42f7a3c813bcc0abc10e2e36db413b7b3dfd97df  flashinfer_python-0.6.14-py3-none-any.whl
对这一段的评论会显示在这里

通过 JupyterLab 上传到 /root/autodl-fs 后复制到本地 SSD:

对这一段的评论会显示在这里
cp /root/autodl-fs/flashinfer_cubin-0.6.14-py3-none-any.whl "$WHEEL_DIR/"
cp /root/autodl-fs/flashinfer_python-0.6.14-py3-none-any.whl "$WHEEL_DIR/"

sha256sum "$WHEEL_DIR"/flashinfer_*.whl

uv pip install --no-deps \
  "$WHEEL_DIR/flashinfer_cubin-0.6.14-py3-none-any.whl" \
  "$WHEEL_DIR/flashinfer_python-0.6.14-py3-none-any.whl"
对这一段的评论会显示在这里

4.3 安装完整运行依赖

对这一段的评论会显示在这里

原始 requirements/cuda.txt 含有 FlashInfer 的额外下载源。前一步已经安装两个固定 wheel,因此生成一份不含该额外源的本地依赖文件:

对这一段的评论会显示在这里
cd "$VLLM_SRC"

FLASHINFER_INDEX_RE='^[[:space:]]*--extra-index-url[[:space:]]+https://flashinfer\.ai/whl/?[[:space:]]*$'
CUDA_REQ_LOCAL="$VLLM_SRC/requirements/cuda-autodl.txt"

test "$(grep -cE "$FLASHINFER_INDEX_RE" requirements/cuda.txt)" -eq 1
grep -vE "$FLASHINFER_INDEX_RE" requirements/cuda.txt > "$CUDA_REQ_LOCAL"

set -o pipefail
uv pip install --verbose --no-progress \
  "$WHEEL_DIR/flashinfer_cubin-0.6.14-py3-none-any.whl" \
  "$WHEEL_DIR/flashinfer_python-0.6.14-py3-none-any.whl" \
  -r "$CUDA_REQ_LOCAL" \
  --torch-backend=cu130 \
  2>&1 | tee "$VLLM_RUN/cuda-install.log"
对这一段的评论会显示在这里

安装完成后检查版本:

对这一段的评论会显示在这里
python -c "import torch; print('torch', torch.__version__, 'cuda', torch.version.cuda)"
python -c "from importlib.metadata import version; print('flashinfer-python', version('flashinfer-python')); print('flashinfer-cubin', version('flashinfer-cubin')); print('tilelang', version('tilelang'))"
flashinfer show-config
对这一段的评论会显示在这里
CUDA 依赖版本
CUDA 依赖版本
对这一段的评论会显示在这里

首次执行 flashinfer show-config 时显示 compiled: 0 属于正常的 JIT 初始状态,不代表 vLLM 编译失败。

对这一段的评论会显示在这里

  1. 编译 vLLM

对这一段的评论会显示在这里

设置 SM120 编译参数:

对这一段的评论会显示在这里
export VLLM_RUN=/root/autodl-tmp/dsv4-vllm-sm120
export VLLM_SRC="$VLLM_RUN/vllm"
export VLLM_SHM=/dev/shm/dsv4-vllm-sm120
export UV_CACHE_DIR="$VLLM_SHM/uv-cache"
export TMPDIR="$VLLM_RUN/tmp"
export XDG_CACHE_HOME="$VLLM_RUN/cache/xdg"
export UV_LINK_MODE=copy

conda activate "$VLLM_RUN/env"
cd "$VLLM_SRC"

export CUDA_HOME=/usr/local/cuda
export PATH="$CUDA_HOME/bin:$PATH"
export CUDA_ARCH_LIST=120a
export TORCH_CUDA_ARCH_LIST=12.0a
export VLLM_TARGET_DEVICE=cuda
export VLLM_USE_PRECOMPILED=0
unset VLLM_PRECOMPILED_WHEEL_LOCATION
export MAX_JOBS=8
export NVCC_THREADS=2

python - <<'PY'
from torch.utils.cpp_extension import _get_cuda_arch_flags
print("Torch CUDA arch flags:", _get_cuda_arch_flags())
PY

set -o pipefail
uv pip install --verbose --no-progress --no-build-isolation -e . \
  2>&1 | tee "$VLLM_RUN/vllm-build.log"
对这一段的评论会显示在这里

编译用时与 CPU 核数有关,本次实验约为 2 小时。日志末尾出现 Built vllmInstalled vllm 表示安装完成。

对这一段的评论会显示在这里
vLLM 编译完成
vLLM 编译完成
对这一段的评论会显示在这里

5.1 验证编译产物

对这一段的评论会显示在这里
export VLLM_RUN=/root/autodl-tmp/dsv4-vllm-sm120
conda activate "$VLLM_RUN/env"
cd "$VLLM_RUN/vllm"

test "$(git rev-parse HEAD)" = "f63bfd3d7b425b10e0b5e0e2c130fe113a85d009"
vllm --version

python - <<'PY'
import importlib.metadata as metadata
import torch
import vllm
import vllm._C_stable_libtorch as vllm_C
import vllm._moe_C_stable_libtorch as vllm_moe_C
from vllm import _custom_ops

caps = [torch.cuda.get_device_capability(i)
        for i in range(torch.cuda.device_count())]
fp4_registered = hasattr(torch.ops._C, "scaled_fp4_quant")

print("vLLM version:", metadata.version("vllm"))
print("vLLM Python:", vllm.__file__)
print("vLLM core extension:", vllm_C.__file__)
print("vLLM MoE extension:", vllm_moe_C.__file__)
print("custom ops: OK")
print("scaled_fp4_quant registered:", fp4_registered)
print("Torch:", torch.__version__)
print("CUDA runtime:", torch.version.cuda)
print("CUDA capabilities:", caps)

assert fp4_registered
assert len(caps) == 4
assert all(cap == (12, 0) for cap in caps)
PY
对这一段的评论会显示在这里

预期版本为 20260717,并输出两个原生扩展路径、scaled_fp4_quant registered: True 和 4 个 (12, 0)

对这一段的评论会显示在这里
vLLM 原生扩展与 FP4 算子检查
vLLM 原生扩展与 FP4 算子检查
对这一段的评论会显示在这里

  1. 启动服务

对这一段的评论会显示在这里

首次启动采用 Eager 模式和 NCCL 通信,先验证模型加载、FP4 算子、Sparse MLA 和 OpenAI 服务是否正常:

对这一段的评论会显示在这里
export VLLM_RUN=/root/autodl-tmp/dsv4-vllm-sm120
conda activate "$VLLM_RUN/env"

export MODEL_PATH=/root/autodl-fs/models/DeepSeek-V4-Flash
export CUDA_VISIBLE_DEVICES=0,1,2,3
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
export VLLM_ENGINE_READY_TIMEOUT_S=3600
export HF_HOME="$VLLM_RUN/huggingface"
export TORCHINDUCTOR_CACHE_DIR="$VLLM_RUN/cache/torchinductor"
export FLASHINFER_WORKSPACE_BASE="$VLLM_RUN/cache/flashinfer"
export TMPDIR="$VLLM_RUN/tmp"
export XDG_CACHE_HOME="$VLLM_RUN/xdg-cache"

unset VLLM_USE_DEEP_GEMM
unset VLLM_DISABLED_KERNELS
unset FLASHINFER_DISABLE_VERSION_CHECK

mkdir -p "$HF_HOME" "$TORCHINDUCTOR_CACHE_DIR" \
  "$FLASHINFER_WORKSPACE_BASE" "$TMPDIR" "$XDG_CACHE_HOME"

vllm serve "$MODEL_PATH" \
  --trust-remote-code \
  --tensor-parallel-size 4 \
  --kv-cache-dtype fp8 \
  --block-size 256 \
  --gpu-memory-utilization 0.85 \
  --max-model-len 131072 \
  --max-num-seqs 4 \
  --max-num-batched-tokens 4096 \
  --tokenizer-mode deepseek_v4 \
  --reasoning-parser deepseek_v4 \
  --tool-call-parser deepseek_v4 \
  --enable-auto-tool-choice \
  --served-model-name deepseek-v4-flash \
  --disable-custom-all-reduce \
  --enforce-eager \
  --host 0.0.0.0 \
  --port 8000 \
  2>&1 | tee "$VLLM_RUN/serve-eager.log"
对这一段的评论会显示在这里

日志出现 Application startup complete 后服务即可访问:

对这一段的评论会显示在这里
vLLM 服务启动成功
vLLM 服务启动成功
对这一段的评论会显示在这里

模型加载完成后,每张卡约占用 84GB 显存:

对这一段的评论会显示在这里
vLLM 四卡显存占用
vLLM 四卡显存占用
对这一段的评论会显示在这里

6.1 关键参数说明

对这一段的评论会显示在这里

| 参数 | 作用 |
| --tensor-parallel-size 4 | 将模型按张量并行方式部署到 4 张 GPU |
| --kv-cache-dtype fp8 | 使用 FP8 KV Cache,降低长上下文的显存占用 |
| --block-size 256 | 设置 KV Cache 的块大小,与 DeepSeek-V4 的稀疏注意力配置配合使用 |
| --gpu-memory-utilization 0.85 | 预留部分显存,避免首次编译或运行时显存不足 |
| --max-model-len 131072 | 将基础验证的最大上下文长度设为 128K |
| --max-num-seqs 4 | 限制同时处理的序列数量 |
| --max-num-batched-tokens 4096 | 限制单次调度的 token 数量,降低首次部署压力 |
| --tokenizer-mode deepseek_v4 | 使用 DeepSeek-V4 专用编码方式 |
| --reasoning-parser deepseek_v4 | 解析思考内容与最终回答 |
| --tool-call-parser deepseek_v4 | 解析 DeepSeek-V4 工具调用结果 |
| --disable-custom-all-reduce | 使用 NCCL 通信路径,避免当前 PCIe 多卡环境下的自定义 AllReduce 兼容问题 |
| --enforce-eager | 先关闭 CUDA Graph,以 Eager 模式完成基础正确性验证 |

对这一段的评论会显示在这里

  1. 接口调用

对这一段的评论会显示在这里

7.1 查询模型

对这一段的评论会显示在这里
curl -sS http://127.0.0.1:8000/v1/models \
  | python -m json.tool --no-ensure-ascii
对这一段的评论会显示在这里
查询模型信息
查询模型信息
对这一段的评论会显示在这里

7.2 普通中文输出

对这一段的评论会显示在这里

安装 OpenAI Python SDK:

对这一段的评论会显示在这里

另外开一个终端

对这一段的评论会显示在这里
export VLLM_RUN=/root/autodl-tmp/dsv4-vllm-sm120
conda activate "$VLLM_RUN/env"
uv pip install openai
对这一段的评论会显示在这里

创建 test_chat.py

对这一段的评论会显示在这里
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="EMPTY")

response = client.chat.completions.create(
    model="deepseek-v4-flash",
    messages=[{
        "role": "user",
        "content": "用一句话介绍 DeepSeek-V4-Flash 的架构亮点。",
    }],
    max_tokens=256,
    temperature=1.0,
    top_p=1.0,
    extra_body={"thinking": {"type": "disabled"}},
)

print(response.choices[0].message.content)
对这一段的评论会显示在这里
python test_chat.py
对这一段的评论会显示在这里
普通中文对话输出
普通中文对话输出
对这一段的评论会显示在这里

7.3 流式输出

对这一段的评论会显示在这里

创建 test_chat_stream.py

对这一段的评论会显示在这里
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="EMPTY")

response = client.chat.completions.create(
    model="deepseek-v4-flash",
    messages=[{
        "role": "user",
        "content": "请简要介绍 DeepSeek-V4-Flash 的 MoE 架构。",
    }],
    max_tokens=512,
    temperature=1.0,
    top_p=1.0,
    stream=True,
    extra_body={"thinking": {"type": "disabled"}},
)

for chunk in response:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
print()
对这一段的评论会显示在这里
python test_chat_stream.py
对这一段的评论会显示在这里
中文流式输出
中文流式输出
对这一段的评论会显示在这里

7.4 思考模式

对这一段的评论会显示在这里

创建 test_reasoning.py

对这一段的评论会显示在这里
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="EMPTY")

response = client.chat.completions.create(
    model="deepseek-v4-flash",
    messages=[{
        "role": "user",
        "content": "9.9 和 9.11 哪个更大?请解释。",
    }],
    max_tokens=1024,
    temperature=1.0,
    top_p=1.0,
    stream=True,
    extra_body={"thinking": {"type": "enabled"}},
)

thinking_started = False
answer_started = False

for chunk in response:
    if not chunk.choices:
        continue
    delta = chunk.choices[0].delta
    reasoning = getattr(delta, "reasoning_content", None)
    if reasoning:
        if not thinking_started:
            print("\n===== 思考 =====")
            thinking_started = True
        print(reasoning, end="", flush=True)
    if delta.content:
        if thinking_started and not answer_started:
            print("\n===== 回答 =====")
            answer_started = True
        print(delta.content, end="", flush=True)
print()
对这一段的评论会显示在这里
python test_reasoning.py
对这一段的评论会显示在这里
思考内容与最终回答
思考内容与最终回答
对这一段的评论会显示在这里

输出应分为“思考”和“回答”两部分。

对这一段的评论会显示在这里

7.5 工具调用

对这一段的评论会显示在这里

创建 test_tool.py

对这一段的评论会显示在这里
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="EMPTY")

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "查询指定城市的天气",
        "parameters": {
            "type": "object",
            "properties": {"location": {"type": "string"}},
            "required": ["location"],
        },
    },
}]

response = client.chat.completions.create(
    model="deepseek-v4-flash",
    messages=[{
        "role": "user",
        "content": "北京今天天气如何?请调用工具查询。",
    }],
    tools=tools,
    tool_choice="auto",
    temperature=0,
    max_tokens=256,
    extra_body={"thinking": {"type": "disabled"}},
)

for tool_call in response.choices[0].message.tool_calls or []:
    print("工具:", tool_call.function.name)
    print("参数:", tool_call.function.arguments)
对这一段的评论会显示在这里
python test_tool.py
对这一段的评论会显示在这里
工具调用结果
工具调用结果
对这一段的评论会显示在这里

预期输出包含 get_weather 和北京。

对这一段的评论会显示在这里

  1. 实验验证

对这一段的评论会显示在这里

本教程在 4 张 RTX PRO 6000 Blackwell Server Edition 上完成了源码编译、模型加载和 OpenAI 兼容接口验证。服务运行时 4 张显卡均参与推理,单卡显存占用约为 84GB。

对这一段的评论会显示在这里
请求期间 GPU 利用率
请求期间 GPU 利用率
对这一段的评论会显示在这里

  1. 可选优化

对这一段的评论会显示在这里

本节不作为基础部署的完成条件。只有 Eager 配置通过接口和稳定性测试后,再停止原服务并尝试 CUDA Graph、Prefix Cache 和 MTP2:

对这一段的评论会显示在这里

9.1 启动优化配置

对这一段的评论会显示在这里
vllm serve "$MODEL_PATH" \
  --trust-remote-code \
  --tensor-parallel-size 4 \
  --kv-cache-dtype fp8 \
  --block-size 256 \
  --gpu-memory-utilization 0.85 \
  --max-model-len 131072 \
  --max-num-seqs 4 \
  --max-num-batched-tokens 4096 \
  --tokenizer-mode deepseek_v4 \
  --reasoning-parser deepseek_v4 \
  --tool-call-parser deepseek_v4 \
  --enable-auto-tool-choice \
  --served-model-name deepseek-v4-flash \
  --enable-prefix-caching \
  --compilation-config '{"cudagraph_mode":"FULL_AND_PIECEWISE"}' \
  --speculative-config '{"method":"mtp","num_speculative_tokens":2}' \
  --disable-custom-all-reduce \
  --host 0.0.0.0 \
  --port 8000
对这一段的评论会显示在这里

首次启动会重新捕获 CUDA Graph。日志出现 Application startup complete,说明服务在同时启用 CUDA Graph、Prefix Cache 和 MTP2 后完成启动。

对这一段的评论会显示在这里
优化配置服务启动成功
优化配置服务启动成功
对这一段的评论会显示在这里

9.2 验证 MTP2 推测解码

对这一段的评论会显示在这里

执行第 7.3 节的流式输出脚本,产生一段长度足够的回复:

对这一段的评论会显示在这里
python test_chat_stream.py
对这一段的评论会显示在这里
使用流式请求触发 MTP2 解码
使用流式请求触发 MTP2 解码
对这一段的评论会显示在这里

随后查询 vLLM 的推测解码指标:

对这一段的评论会显示在这里
curl -sS http://127.0.0.1:8000/metrics \
  | grep -E '^vllm:spec_decode_num_(drafts|draft_tokens|accepted_tokens)'
对这一段的评论会显示在这里

本次测试共产生 444 个 draft token,其中 264 个被接受,draft token 接受率约为 59.5%。draft_tokensaccepted_tokens 均大于 0,说明 MTP2 已参与生成过程。

对这一段的评论会显示在这里
MTP2 推测解码指标
MTP2 推测解码指标
对这一段的评论会显示在这里

9.3 验证 Prefix Cache

对这一段的评论会显示在这里

创建 test_prefix_cache.py,连续发送两次具有相同长前缀的请求:

对这一段的评论会显示在这里
import time
from openai import OpenAI

client = OpenAI(
    base_url="http://127.0.0.1:8000/v1",
    api_key="EMPTY",
)

shared_text = (
    "DeepSeek-V4-Flash 采用混合专家架构,并使用稀疏注意力处理长上下文。"
    "以下内容用于测试相同长前缀的 KV Cache 复用。\n"
) * 800

messages = [{
    "role": "user",
    "content": shared_text + "\n请用一句话总结以上资料。",
}]

for index in range(1, 3):
    start = time.perf_counter()
    first_token_time = None
    answer = []

    stream = client.chat.completions.create(
        model="deepseek-v4-flash",
        messages=messages,
        max_tokens=64,
        temperature=1.0,
        stream=True,
        extra_body={"thinking": {"type": "disabled"}},
    )

    for chunk in stream:
        content = chunk.choices[0].delta.content
        if content:
            if first_token_time is None:
                first_token_time = time.perf_counter()
            answer.append(content)

    if first_token_time is None:
        print(f"第 {index} 次请求没有返回正文")
    else:
        print(f"第 {index} 次请求 TTFT:{first_token_time - start:.3f} 秒")
        print("回答:", "".join(answer))
对这一段的评论会显示在这里

运行脚本并查询 Prefix Cache 指标:

对这一段的评论会显示在这里
python test_prefix_cache.py

curl -sS http://127.0.0.1:8000/metrics \
  | grep -E '^vllm:prefix_cache_(queries|hits)'
对这一段的评论会显示在这里

本次测试中,首次请求的 TTFT 为 18.686 秒,第二次请求降至 0.470 秒,同时 prefix_cache_hits_total 大于 0,说明相同长前缀的 KV Cache 已被复用。

对这一段的评论会显示在这里
Prefix Cache 命中与 TTFT 对比
Prefix Cache 命中与 TTFT 对比
对这一段的评论会显示在这里

该配置成功后仍可重复第 7 节的思考模式和工具调用测试;如果优化配置启动失败,继续使用已经验证的 Eager 配置即可,不影响基础部署。

对这一段的评论会显示在这里