409 lines
16 KiB
Markdown
409 lines
16 KiB
Markdown
# 投机解码教程
|
||
|
||
投机解码是一个通过投机性地一次性猜测多个 token 然后进行验证和接收的算法,通过投机解码可以极大地减小推理时延。PaddleNLP 提供了简单、高效的投机解码推理流程。下面提供 PaddleNLP 中各种投机解码算法的使用说明。
|
||
## 高效投机解码框架
|
||
传统的 Draft Token 验证以及 MTP(Eagle/Draft Model) 推理方法,batch_size 一般会膨胀 Draft Token 数量的倍数,我们高效的注意力机制则可以保持原始批次大小,减少计算量,如下图所示:
|
||

|
||
参考 [Vllm batch_extension](https://docs.google.com/document/d/1T-JaS2T1NRfdP51qzqpyakoCXxSXTtORppiwaj5asxA/edit?tab=t.0#heading=h.kk7dq05lc6q8)
|
||
## 参数说明
|
||
- `speculate_method`: 推理解码算法,默认值为`None`,可选的数值有`None`、`inference_with_reference`、 `mtp`、 `eagle`。为`None`时为正常自回归解码,为`inference_with_reference`时为基于上下文的投机解码[论文地址](https://arxiv.org/pdf/2304.04487)。
|
||
|
||
- `speculate_max_draft_token_num`: 投机解码算法中每轮产生的最大 draft tokens 数目,默认值为 1,最大支持 5。
|
||
|
||
- `speculate_max_ngram_size`: n-gram 匹配 draft tokens 时的最大窗口大小,默认值为`1`。inference_with_reference 算法中会先从 prompt 中使用 ngram 窗口滑动匹配 draft tokens,窗口大小和输入输出重叠程度共同决定了产生 draft tokens 的开销从而影响 inference_with_reference 算法的加速效果。
|
||
|
||
- `speculate_verify_window`(暂时废弃): 投机解码 verify 策略默认采用 TopP + window verify 中的 window 大小,默认值为`2`。更多有关 TopP + window verify 的详细介绍参考[投机解码教程](./speculative_decoding.md)。
|
||
|
||
- `speculate_max_candidate_len`(暂时废弃): 产生的最大候选 tokens 数目,根据候选 tokens 与 draft tokens 比较来进行 verify(仅在 TopP + window verify 时生效),默认值为`5`。
|
||
|
||
- `draft_model_name_or_path`: 在`MTP`或者`EAGLE`模式下,`Draft Model`的路径。
|
||
|
||
- `draft_model_quant_type`: 在`MTP`或者`EAGLE`模式下,`Draft Model`的推理量化精度,参考`--quant_type`。
|
||
|
||
- `return_full_hidden_states`: 在`MTP`或者`EAGLE`模式下,是否返回全部的隐藏层状态,默认为`False`。
|
||
|
||
Others:
|
||
1. 投机解码目前支持的最大 batch_size 为 128
|
||
## Inference with reference
|
||
|
||
该算法通过 n-gram 窗口从 prompt 中匹配 draft tokens,适合输入和输出有很大 overlap 的场景如代码编辑、文档查询等,更多信息查看查看[论文地址](https://arxiv.org/pdf/2304.04487)。
|
||
|
||
### 使用命令
|
||
|
||
```shell
|
||
# 动态图模型推理命令参考
|
||
python predictor.py --model_name_or_path meta-llama/Llama-2-7b-chat --inference_model --dtype float16 --speculate_method inference_with_reference --speculate_max_draft_token_num 5 --speculate_max_ngram_size 2
|
||
```
|
||
|
||
**Note:**
|
||
|
||
1. 该算法目前只支持 llama/DeepSeek-V3/R1 系列模型。
|
||
2. 投机解码同时支持量化推理,具体命令参考[推理示例](./inference.md),将 speculate_method 等投机解码参数加上即可。
|
||
|
||
## Multi-Token Prediction(MTP)
|
||
|
||
Paper:https://github.com/deepseek-ai/DeepSeek-V3/blob/main/DeepSeek_V3.pdf
|
||

|
||
|
||
支持 DeepSeek-V3/R1 & MTP 的推理功能,具体原理请查阅论文。
|
||
|
||
特色功能:
|
||
1. Base Model 验证阶段使用优化的 Attention,可一次性产出所有 Draft Token 的 logits,无需使用 batch_extension 扩大 batch_size,解决了大批次下投机解码无法加速推理性能的问题。
|
||
2. 在 MTP 推理阶段,统一对上轮 Draft Token 接受情况的处理,例如全部拒绝、接受部分、接受全部,同样只需保持原始的批次大小,一次性处理所有输入请求。
|
||
3. 使用分离框架,即 Base Model 和 MTP 分别加载权重,导出后兼容多种解码方式。
|
||
|
||
### MTP 支持量化精度
|
||
|
||
| 基础模型 | 部署机器 | 基础模型量化类型 | MTP 量化类型 |
|
||
| --- | --- | --- | --- |
|
||
| DeepSeek-R1 | TP8 | a8w8_fp8_wint4 | a8w8_fp8 |
|
||
| DeepSeek-R1 | TP8 | weight_only_int4 | weight_only_int8 |
|
||
| DeepSeek-R1 | TP16(2*TP8) | a8w8_fp8 | a8w8_fp8 |
|
||
| DeepSeek-R1 | TP16(2*TP8) | weight_only_int8 |weight_only_int8 |
|
||
|
||
支持 DeepSeek-R1 与 MTP 的多种推理混合精度,可通过容器部署或脚本的方式进行推理,以下为部分示例
|
||
|
||
### 方法一:使用容器一键部署
|
||
**DeepSeek-R1(a8w8_fp8_wint4) + MTP(a8w8_fp8), 单机 TP8 部署**
|
||
|
||
```shell
|
||
export MODEL_PATH=${MODEL_PATH:-/PATH_TO_MODEL/}
|
||
export MODEL_MTP_PATH=${MODEL_PATH:-/PATH_TO_MTP/}
|
||
export model_name="deepseek-ai/DeepSeek-R1-MTP/a8w8_fp8_wint4"
|
||
|
||
docker run --gpus all --shm-size 32G --network=host --privileged --cap-add=SYS_PTRACE \
|
||
-v $MODEL_PATH:/models -v $MODEL_MTP_PATH:/models-mtp -v /ssd2/paddle_example:/work -e "model_name=${model_name}" \
|
||
-dit ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddlenlp:llm-serving-cuda124-cudnn9-v2.3 /bin/bash \
|
||
-c -ex 'export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 && export MP_NUM=8 && export SPECULATE_MODEL_QUANT_TYPE="a8w8_fp8" && export SPECULATE_METHOD="mtp" && export SPECULATE_MODEL_PATH="/models-mtp" && export SPECULATE_MAX_DRAFT_TOKEN_NUM=1 && export BLOCK_BS=32 && export BLOCK_RATIO=0.25 && export BATCH_SIZE="128" && start_server $model_name && tail -f /dev/null'
|
||
```
|
||
**DeepSeek-R1(weight_only_int8) + MTP(weight_only_int8), 双机 TP16 划分**
|
||
|
||
1. 一键容器启动 MTP 推理服务
|
||
|
||
```shell
|
||
需要保证2机器节点可以互相ping通
|
||
# 第一个节点(master)
|
||
ping 192.168.0.1
|
||
# 第二个节点(slave)
|
||
ping 192.168.0.2
|
||
model_name=${model_name:-"deepseek-ai/DeepSeek-R1-MTP-2nodes/weight_only_int8"}
|
||
export POD_0_IP=master_ip
|
||
export POD_IPS=master_ip,slave_ip # 该环境变量在2机上都需保持一致
|
||
# 服务化默认启动端口,如果冲突可以通过export进行修改
|
||
export SERVICE_HTTP_PORT=${PUSH_MODE_HTTP_PORT:-${SERVICE_HTTP_PORT:-"9965"}}
|
||
#注意 SPECULATE_MODEL_QUANT_TYPE应与MTP支持量化精度表格一致
|
||
export SPECULATE_MODEL_QUANT_TYPE="weight_only_int8"
|
||
# /PATH_TO_MODEL # 模型挂载路径
|
||
# /PATH_TO_MTP # MTP 挂载路径
|
||
```
|
||
|
||
```shell
|
||
# node1
|
||
export model_name=${model_name:-"deepseek-ai/DeepSeek-R1-MTP-2nodes/weight_only_int8"}
|
||
export MODEL_PATH=${MODEL_PATH:-/PATH_TO_MODEL/}
|
||
export MODEL_MTP_PATH=${MODEL_PATH:-/PATH_TO_MTP/}
|
||
docker run --gpus all --shm-size 32G --network=host --privileged --cap-add=SYS_PTRACE \
|
||
-v $MODEL_PATH:/models -v $MODEL_MTP_PATH:/models-mtp -e "model_name=${model_name}" \
|
||
-dit ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddlenlp:llm-serving-cuda124-cudnn9-v2.3 /bin/bash \
|
||
-c -ex 'export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 && export MP_NUM=16 && export MP_NNODE=2 && export POD_0_IP=192.168.0.1 && export POD_IPS=192.168.0.1,192.168.0.2 && export SPECULATE_MODEL_QUANT_TYPE="weight_only_int8" && export SPECULATE_METHOD="mtp" && export SPECULATE_MODEL_PATH="/models-mtp" && export SPECULATE_MAX_DRAFT_TOKEN_NUM=1 && start_server $model_name && tail -f /dev/null'
|
||
|
||
# node2
|
||
export model_name=${model_name:-"deepseek-ai/DeepSeek-R1-MTP-2nodes/weight_only_int8"}
|
||
export MODEL_PATH=${MODEL_PATH:-/PATH_TO_MODEL/}
|
||
export MODEL_MTP_PATH=${MODEL_PATH:-/PATH_TO_MTP/}
|
||
docker run --gpus all --shm-size 32G --network=host --privileged --cap-add=SYS_PTRACE \
|
||
-v $MODEL_PATH:/models -v $MODEL_MTP_PATH:/models-mtp -e "model_name=${model_name}" \
|
||
-dit ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddlenlp:llm-serving-cuda124-cudnn9-v2.3 /bin/bash \
|
||
-c -ex 'export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 && export MP_NUM=16 && export MP_NNODE=2 && export POD_0_IP=192.168.0.1 && export POD_IPS=192.168.0.1,192.168.0.2 &&export SPECULATE_MODEL_QUANT_TYPE="weight_only_int8" && export SPECULATE_METHOD="mtp" && export SPECULATE_MODEL_PATH="/models-mtp" && export SPECULATE_MAX_DRAFT_TOKEN_NUM=1 && start_server $model_name && tail -f /dev/null'
|
||
```
|
||
|
||
**请求服务化**
|
||
|
||
curl 请求
|
||
```shell
|
||
curl ${ip}:9965/v1/chat/completions \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{
|
||
"model":"default",
|
||
"text":"Hello, how are you?"
|
||
}'
|
||
```
|
||
OpenAI 请求
|
||
```python
|
||
import openai
|
||
client = openai.Client(base_url=f"http://127.0.0.1:9965/v1/chat/completions", api_key="EMPTY_API_KEY")
|
||
# 非流式返回
|
||
response = client.completions.create(
|
||
model="default",
|
||
prompt="Hello, how are you?",
|
||
max_tokens=50,
|
||
stream=False,
|
||
)
|
||
|
||
print(response)
|
||
print("\n")
|
||
|
||
# 流式返回
|
||
response = client.completions.create(
|
||
model="default",
|
||
prompt="Hello, how are you?",
|
||
max_tokens=100,
|
||
stream=True,
|
||
)
|
||
|
||
for chunk in response:
|
||
if chunk.choices[0] is not None:
|
||
print(chunk.choices[0].text, end='')
|
||
print("\n")
|
||
```
|
||
|
||
### 方法二:使用脚本推理测试
|
||
#### DeepSeek_R1 动态图 + MTP 动态图, 双机 TP16
|
||
1. DeepSeek_R1 使用 weight_only_int8, MTP 使用 weight_only_int8
|
||
|
||
```shell
|
||
启动2机推理 需要保证2机器节点可以互相ping通
|
||
# 第一个节点(master)
|
||
ping 192.168.0.1
|
||
# 第二个节点(slave)
|
||
ping 192.168.0.2
|
||
```
|
||
|
||
node1 和 node2 脚本相同
|
||
```shell
|
||
$ cat run_dynamic_mtp.sh
|
||
|
||
export MODEL_TAG=deepseek-ai/DeepSeek-R1
|
||
export DRAFT_MODEL_TAG=deepseek-ai/DeepSeek-R1-MTP
|
||
export QUANT_MODE=weight_only_int8
|
||
export DRAFT_MODEL_QUANT_MODE=weight_only_int8
|
||
export TOTAL_MAX_LENGTH=8192
|
||
export MAX_DEC_LEN=2048
|
||
# 算子加速策略
|
||
export CUDA_VISIBLE_DEVICES="0,1,2,3,4,5,6,7"
|
||
python -m paddle.distributed.launch --ips "192.168.0.1,192.168.0.2"\
|
||
--gpus ${CUDA_VISIBLE_DEVICES} \
|
||
predictor.py \
|
||
--model_name_or_path ${MODEL_TAG} \
|
||
--dtype bfloat16 \
|
||
--mode dynamic \
|
||
--inference_model 1 \
|
||
--append_attn 1 \
|
||
--total_max_length ${TOTAL_MAX_LENGTH} \
|
||
--quant_type ${QUANT_MODE} \
|
||
--max_length ${MAX_DEC_LEN} \
|
||
--speculate_method mtp \
|
||
--draft_model_name_or_path ${DRAFT_MODEL_TAG} \
|
||
--draft_model_quant_type ${DRAFT_MODEL_QUANT_MODE} \
|
||
--speculate_max_draft_token_num 1 \
|
||
--return_full_hidden_states 1 \
|
||
--mla_use_matrix_absorption 1
|
||
|
||
$ bash run_dynamic_mtp.sh
|
||
```
|
||
|
||
2. DeepSeek_R1 使用 FP8, MTP 使用 FP8
|
||
|
||
node1 和 node2 脚本相同
|
||
```shell
|
||
$ cat run_dynamic_mtp.sh
|
||
|
||
export MODEL_TAG=deepseek-ai/DeepSeek-R1-FP8
|
||
export DRAFT_MODEL_TAG=deepseek-ai/DeepSeek-R1-MTP-FP8
|
||
export QUANT_MODE=a8w8_fp8
|
||
export DRAFT_MODEL_QUANT_MODE=a8w8_fp8
|
||
export TOTAL_MAX_LENGTH=8192
|
||
export MAX_DEC_LEN=2048
|
||
# 算子加速策略
|
||
export CUDA_VISIBLE_DEVICES="0,1,2,3,4,5,6,7"
|
||
python -m paddle.distributed.launch --ips "192.168.0.1,192.168.0.2"\
|
||
--gpus ${CUDA_VISIBLE_DEVICES} \
|
||
predictor.py \
|
||
--model_name_or_path ${MODEL_TAG} \
|
||
--dtype bfloat16 \
|
||
--mode dynamic \
|
||
--inference_model 1 \
|
||
--append_attn 1 \
|
||
--total_max_length ${TOTAL_MAX_LENGTH} \
|
||
--quant_type ${QUANT_MODE} \
|
||
--max_length ${MAX_DEC_LEN} \
|
||
--speculate_method mtp \
|
||
--draft_model_name_or_path ${DRAFT_MODEL_TAG} \
|
||
--draft_model_quant_type ${DRAFT_MODEL_QUANT_MODE} \
|
||
--speculate_max_draft_token_num 1 \
|
||
--return_full_hidden_states 1 \
|
||
--mla_use_matrix_absorption 1
|
||
|
||
$ bash run_dynamic_mtp.sh
|
||
```
|
||
#### DeepSeek_R1 动态图 + MTP 动态图, 单机 TP8
|
||
1. DeepSeek_R1 使用 weight_only_int8, MTP 使用 weight_only_int8
|
||
```shell
|
||
$ cat run_dynamic_mtp.sh
|
||
|
||
export MODEL_TAG=deepseek-ai/DeepSeek-R1
|
||
export DRAFT_MODEL_TAG=deepseek-ai/DeepSeek-R1-MTP
|
||
export QUANT_MODE=weight_only_int4
|
||
export DRAFT_MODEL_QUANT_MODE=weight_only_int8
|
||
export TOTAL_MAX_LENGTH=8192
|
||
export MAX_DEC_LEN=2048
|
||
# 算子加速策略
|
||
export CUDA_VISIBLE_DEVICES="0,1,2,3,4,5,6,7"
|
||
python -m paddle.distributed.launch \
|
||
--gpus ${CUDA_VISIBLE_DEVICES} \
|
||
predictor.py \
|
||
--model_name_or_path ${MODEL_TAG} \
|
||
--dtype bfloat16 \
|
||
--mode dynamic \
|
||
--inference_model 1 \
|
||
--append_attn 1 \
|
||
--total_max_length ${TOTAL_MAX_LENGTH} \
|
||
--quant_type ${QUANT_MODE} \
|
||
--max_length ${MAX_DEC_LEN} \
|
||
--speculate_method mtp \
|
||
--draft_model_name_or_path ${DRAFT_MODEL_TAG} \
|
||
--draft_model_quant_type ${DRAFT_MODEL_QUANT_MODE} \
|
||
--speculate_max_draft_token_num 1 \
|
||
--return_full_hidden_states 1 \
|
||
--mla_use_matrix_absorption 1
|
||
|
||
$ bash run_dynamic_mtp.sh
|
||
```
|
||
|
||
#### 【**推荐**】Base Model 静态图 + MTP 动态图, 双机 TP16
|
||
> 注:
|
||
1.投机解码导出的模型支持所有方法,因此这里 speculate_method 设为默认的 inference_with_reference 即可.
|
||
2.静态图模型可从 DeepSeek-R1 导出,或直接下载已上传模型
|
||
|
||
```shell
|
||
启动2机推理 需要保证2机器节点可以互相ping通
|
||
# 第一个节点(master)
|
||
ping 192.168.0.1
|
||
# 第二个节点(slave)
|
||
ping 192.168.0.2
|
||
```
|
||
|
||
1. DeepSeek-R1 使用 weight_only_int8,MTP 使用 weight_only_int8
|
||
|
||
node1 和 node2 脚本相同
|
||
```shell
|
||
# 导出脚本
|
||
$ cat export.sh
|
||
|
||
export MODEL_TAG=deepseek-ai/DeepSeek-R1
|
||
export OUTPUT_PATH=/path/to/exported_model
|
||
export QUANT_MODE=weight_only_int8
|
||
export TOTAL_MAX_LENGTH=8192
|
||
export CUDA_VISIBLE_DEVICES="0,1,2,3,4,5,6,7"
|
||
python -m paddle.distributed.launch --ips "192.168.0.1,192.168.0.2"\
|
||
--gpus ${CUDA_VISIBLE_DEVICES} \
|
||
export_model.py \
|
||
--model_name_or_path ${MODEL_TAG} \
|
||
--output_path ${OUTPUT_PATH} \
|
||
--dtype bfloat16 \
|
||
--inference_model 1 \
|
||
--append_attn 1 \
|
||
--block_attn 1 \
|
||
--total_max_length ${TOTAL_MAX_LENGTH} \
|
||
--quant_type ${QUANT_MODE} \
|
||
--speculate_method inference_with_reference \
|
||
--return_full_hidden_states 1 \
|
||
--mla_use_matrix_absorption 1
|
||
|
||
$ bash export.sh
|
||
```
|
||
|
||
node1 和 node2 脚本相同
|
||
```shell
|
||
# 推理脚本
|
||
$ cat run_mtp_infer.sh
|
||
|
||
export OUTPUT_PATH=/path/to/exported_model
|
||
export DRAFT_MODEL_TAG=deepseek-ai/DeepSeek-R1-MTP
|
||
export TOTAL_MAX_LENGTH=8192
|
||
export MAX_DEC_LEN=2048
|
||
export QUANT_MODE=weight_only_int8
|
||
export DRAFT_MODEL_QUANT_MODE=weight_only_int8
|
||
export CUDA_VISIBLE_DEVICES="0,1,2,3,4,5,6,7"
|
||
python -m paddle.distributed.launch --ips "192.168.0.1,192.168.0.2"\
|
||
--gpus ${CUDA_VISIBLE_DEVICES} \
|
||
predictor.py \
|
||
--model_name_or_path ${OUTPUT_PATH} \
|
||
--dtype bfloat16 \
|
||
--mode static \
|
||
--inference_model 1 \
|
||
--append_attn 1 \
|
||
--total_max_length ${TOTAL_MAX_LENGTH} \
|
||
--quant_type ${QUANT_MODE} \
|
||
--max_length 1024 \
|
||
--speculate_method mtp \
|
||
--draft_model_name_or_path ${DRAFT_MODEL_TAG} \
|
||
--draft_model_quant_type ${DRAFT_MODEL_QUANT_MODE} \
|
||
--speculate_max_draft_token_num 1 \
|
||
--return_full_hidden_states 1 \
|
||
--mla_use_matrix_absorption 1
|
||
|
||
$ bash run_mtp_infer.sh
|
||
```
|
||
|
||
2. DeepSeek-R1 使用 FP8, MTP 使用 FP8, 双机 TP16
|
||
|
||
node1 和 node2 脚本相同
|
||
```shell
|
||
# 导出脚本
|
||
$ cat export.sh
|
||
|
||
export MODEL_TAG=deepseek-ai/DeepSeek-R1-FP8
|
||
export OUTPUT_PATH=/path/to/exported_model
|
||
export QUANT_MODE=a8w8_fp8
|
||
export TOTAL_MAX_LENGTH=8192
|
||
export CUDA_VISIBLE_DEVICES="0,1,2,3,4,5,6,7"
|
||
python -m paddle.distributed.launch --ips "192.168.0.1,192.168.0.2"\
|
||
--gpus ${CUDA_VISIBLE_DEVICES} \
|
||
export_model.py \
|
||
--model_name_or_path ${MODEL_TAG} \
|
||
--output_path ${OUTPUT_PATH} \
|
||
--dtype bfloat16 \
|
||
--inference_model 1 \
|
||
--append_attn 1 \
|
||
--total_max_length ${TOTAL_MAX_LENGTH} \
|
||
--quant_type ${QUANT_MODE} \
|
||
--speculate_method inference_with_reference \
|
||
--return_full_hidden_states 1 \
|
||
--mla_use_matrix_absorption 1
|
||
|
||
$ bash export.sh
|
||
```
|
||
|
||
node1 和 node2 脚本相同
|
||
```shell
|
||
# 推理脚本
|
||
$ cat run_mtp_infer.sh
|
||
|
||
export OUTPUT_PATH=/path/to/exported_model
|
||
export DRAFT_MODEL_TAG=deepseek-ai/DeepSeek-R1-MTP-FP8
|
||
export TOTAL_MAX_LENGTH=8192
|
||
export MAX_DEC_LEN=2048
|
||
export QUANT_MODE=a8w8_fp8
|
||
export CUDA_VISIBLE_DEVICES="0,1,2,3,4,5,6,7"
|
||
python -m paddle.distributed.launch --ips "192.168.0.1,192.168.0.2"\
|
||
--gpus ${CUDA_VISIBLE_DEVICES} \
|
||
predictor.py \
|
||
--model_name_or_path ${OUTPUT_PATH} \
|
||
--dtype bfloat16 \
|
||
--mode static \
|
||
--inference_model 1 \
|
||
--append_attn 1 \
|
||
--total_max_length ${TOTAL_MAX_LENGTH} \
|
||
--quant_type ${QUANT_MODE} \
|
||
--max_length ${MAX_DEC_LEN} \
|
||
--speculate_method mtp \
|
||
--draft_model_name_or_path ${DRAFT_MODEL_TAG} \
|
||
--draft_model_quant_type ${QUANT_MODE} \
|
||
--speculate_max_draft_token_num 1 \
|
||
--return_full_hidden_states 1 \
|
||
--mla_use_matrix_absorption 1
|
||
|
||
$ bash run_mtp_infer.sh
|
||
```
|