01-DeepSeek-V4-Flash-LoRA 及 SwanLab 可视化记录
本节介绍如何基于 transformers、peft 等框架,使用开源 Chat-甄嬛 项目中的嬛嬛数据集对 DeepSeek-V4-Flash 的 BF16 权重进行 LoRA 微调,以构建能够模拟甄嬛对话风格的个性化 LLM。数据集路径为 ../../dataset/huanhuan.jsonl,训练过程使用 SwanLab 记录和可视化指标。
训练代码: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 显卡
目录
01-DeepSeek-V4-Flash-LoRA 及 SwanLab 可视化记录
目录
1. 环境配置
2. 模型下载
3. 指令集构建
4. 数据格式化
5. 加载模型和 tokenizer
6. 定义 LoRA 配置
7. 自定义 TrainingArguments 参数
8. SwanLab 可视化
SwanLab 简介
实例化 SwanLabCallback
9. 使用 Trainer 训练
10. 训练结果演示
11. 加载 LoRA 权重推理
- 环境配置
实验所依赖的基础开发环境如下:
----------------
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 推理算子
- 模型下载
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_id 和 local_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。
- 指令集构建
LLM 的微调一般指指令微调过程。所谓指令微调,是说我们使用的微调数据形如:
{
"instruction": "回答以下用户问题,仅输出答案。",
"input": "1+1等于几?",
"output": "2"
}
其中,instruction 是用户指令,告知模型其需要完成的任务;input 是用户输入,是完成用户指令所必须的输入内容;output 是模型应该给出的输出。
即我们的核心训练目标是让模型具有理解并遵循用户指令的能力。因此,在指令集构建时,我们应针对我们的目标任务,针对性构建任务指令集。
{
"instruction": "你是谁?",
"input": "",
"output": "家父是大理寺少卿甄远道。"
}
- 数据格式化
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")
- 加载模型和 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_proj、q_b_proj、kv_proj、o_a_proj、o_b_proj。因此本文不会使用 q_proj、k_proj、v_proj、o_proj 作为 LoRA target。
- 定义 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_proj。o_a_proj在 DeepSeek-V4-Flash 中是 grouped linear 结构,PEFT 会将其按普通 Linear 处理,训练时可能触发 grouped shape mismatch。因此本文只选择q_a_proj、q_b_proj、kv_proj、o_b_proj。
本教程配置下,可训练参数量约为 46,149,632。
- 自定义 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})
- SwanLab 可视化
SwanLab 简介
SwanLab 是一个开源的模型训练记录工具,面向 AI 研究者,提供了训练可视化、自动日志记录、超参数记录、实验对比、多人协同等功能。在 SwanLab 上,研究者能基于直观的可视化图表发现训练问题,对比多个实验找到研究灵感,并通过在线链接的分享与基于组织的多人协同训练,打破团队沟通的壁垒。
为什么要记录训练
相较于软件开发,模型训练更像一个实验科学。一个品质优秀的模型背后,往往是成千上万次实验。研究者需要不断尝试、记录、对比,积累经验,才能找到最佳的模型结构、超参数与数据配比。在这之中,如何高效进行记录与对比,对于研究效率的提升至关重要。
实例化 SwanLabCallback
(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=[])。
- 使用 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)
python 01-DeepSeek-V4-Flash-LoRA.py \
--swanlab-project deepseek-v4-flash \
--swanlab-experiment huanhuan-r16-a32-200step
- 训练结果演示
训练完成后,打开 SwanLab,可以查看训练过程中记录的参数和训练指标。
在 SwanLab 上查看训练过程中的 loss、grad_norm、learning_rate 和 epoch 变化:
本次实验完成 200 step,训练耗时约 74 分钟,最终 train_loss 为 2.605。
训练完成后的 LoRA 权重会保存在:
./output/DeepSeek-V4-Flash-LoRA
至此,DeepSeek-V4-Flash 的 LoRA 微调训练已完成。如果需要进一步改善效果,可以增加训练步数或根据任务构建更高质量的数据集。
- 加载 LoRA 权重推理
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
目录
02-DeepSeek-V4-Flash SGLang 部署
目录
1. 模型与硬件准备
1.1 下载模型
1.2 检查硬件
2. RTX PRO 6000 官方 Docker 部署
2.1 拉取镜像
2.2 启动服务
3. 4×H200 Python 环境部署
3.1 创建环境
3.2 启动服务
4. 接口调用
4.1 普通对话
4.2 流式输出
4.3 思考模式
4.4 工具调用
5. 实验验证
- 模型与硬件准备
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 版本。
- 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 节的接口测试。
- 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
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!
另开一个终端检查 4 张卡的显存:
nvidia-smi
提交前补充
images/02-09.png:服务加载完成后的 4 张 H200 显存占用。
- 接口调用
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
- 实验验证
本教程在 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分支。
目录
03-DeepSeek-V4-Flash vLLM 部署
目录
1. 环境与模型检查
1.1 磁盘规划
1.2 下载模型
1.3 检查 GPU、CUDA 和模型
2. RTX PRO 6000 官方 Docker 部署
3. 创建编译环境
3.1 设置目录
3.2 创建 Python 环境
3.3 获取固定源码
4. 安装 CUDA 依赖
4.1 安装构建依赖
4.2 安装 FlashInfer
4.3 安装完整运行依赖
5. 编译 vLLM
5.1 验证编译产物
6. 启动服务
6.1 关键参数说明
7. 接口调用
7.1 查询模型
7.2 普通中文输出
7.3 流式输出
7.4 思考模式
7.5 工具调用
8. 实验验证
9. 可选优化
9.1 启动优化配置
9.2 验证 MTP2 推测解码
9.3 验证 Prefix Cache
- 环境与模型检查
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 文件。
- 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 源码编译路线。
- 创建编译环境
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
如果服务器无法访问 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
- 安装 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
首次执行 flashinfer show-config 时显示 compiled: 0 属于正常的 JIT 初始状态,不代表 vLLM 编译失败。
- 编译 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 vllm 和 Installed 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)。
- 启动服务
首次启动采用 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 后服务即可访问:
模型加载完成后,每张卡约占用 84GB 显存:
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 模式完成基础正确性验证 |
- 接口调用
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 和北京。
- 实验验证
本教程在 4 张 RTX PRO 6000 Blackwell Server Edition 上完成了源码编译、模型加载和 OpenAI 兼容接口验证。服务运行时 4 张显卡均参与推理,单卡显存占用约为 84GB。
- 可选优化
本节不作为基础部署的完成条件。只有 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
随后查询 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_tokens 和 accepted_tokens 均大于 0,说明 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 已被复用。
该配置成功后仍可重复第 7 节的思考模式和工具调用测试;如果优化配置启动失败,继续使用已经验证的 Eager 配置即可,不影响基础部署。