Skip to content

Commit

Permalink
[Bug fixes] update chatglm tokenizer (#7797)
Browse files Browse the repository at this point in the history
* update chatglm tokenizer

* update chatglm2 tokenizer

* update chatglm2 tokenizer

* update max & src slider

* add chatglm2 tokenizer
  • Loading branch information
wj-Mcat authored and JunnYu committed Jan 12, 2024
1 parent 331afb2 commit 0bd7b55
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 15 deletions.
236 changes: 236 additions & 0 deletions llm/docs/inference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
# 大模型推理教程

PaddleNLP除了提供常用模型推理外,还提供了高性能推理,内置动态插入和全环节算子融合策略,极大加快并行推理的速度。

git clone 代码到本地,即可开始。

```bash
git clone https://github.com/PaddlePaddle/PaddleNLP.git
# pip install ./PaddleNLP 使用develop版本
cd PaddleNLP/llm
# 到达运行目录
```

## 1. 常用模型推理
PaddleNLP 提供了动态图推理和静态图推理两种方式,方便用户快速验证模型推理效果(包含LoRA、PrefixTuning)

### 1.1 动态图推理
```shell
# 动态图模型推理命令参考
python predictor.py --model_name_or_path meta-llama/Llama-2-7b-chat --data_file ./data/dev.json --dtype float16
```
对于LoRA、PrefixTuning 模型只需额外传入相应的lora_path或prefix_path即可,如:`--lora_path ./checkpoints/llama_lora_ckpts``--prefix_path ./checkpoints/llama_prefix_ckpts`,详见推理参数减少。

### 1.2 静态图推理

```shell
# 静态图模型推理命令参考, LoRA需要先合并参数,Prefix Tuning暂不支持
# step1 : 静态图导出
python export_model.py --model_name_or_path meta-llama/Llama-2-7b-chat --output_path ./inference --dtype float16
# step2: 静态图推理
python predictor.py --model_name_or_path ./inference --data_file ./data/dev.json --dtype float16 --mode static
```

## 2. 高性能模型推理

高性能推理内置动态插入和全环节算子融合策略,隐藏了底层实现的细节,实现了开箱即用高性能并行推理能力。
<div align="center">
<img width="800" alt="llm" src="https://github.com/PaddlePaddle/PaddleNLP/assets/63761690/42174b47-f765-48d6-9fef-907b69bf6706">
</div>
<div align="center">
<font size ="1">
飞桨高性能推理算子融合示意图
</font>
</div>

<div align="center">
<img width="800" alt="llm" src="https://github.com/PaddlePaddle/PaddleNLP/assets/63761690/616b3fc5-b9b2-4b10-a5c8-2f892a65ae6b">
</div>
<div align="center">
<font size ="1">
动态插入图解 & 飞桨高性能模型推理性能图
</font>
</div>

PaddleNLP 中已经添加高性能推理模型相关实现,支持:

| Model | Inference Model | PTuning | WINT8 | PTQ-A8W8 |
|-----------------------------|-----------------|---------|-------|-----|
| [LLaMA1/2](../llama) |||||
| [ChatGLM](../chatglm) |||||
| [ChatGLM2](../chatglm2) |||||
| [Bloom](../bloom) |||||
| [GPT-3](../gpt-3) |||||
| [Qwen](../qwen) |||||
| [BaiChuan-7B](../llama) |||| 🚧 |
| [BaiChuan2-7B](../llama) |||| 🚧 |
| [BaiChuan2-13B](../llama) | 🚧 | 🚧 | 🚧 | 🚧 |

* ✅: Supported
* 🚧: In Progress
* ❌: Not Supported
* WINT8:指Weight-Only Quantization INT8,即对权重进行INT8量化的模型。
* PTQ-A8W8:指使用PTQ对线性层的激活和权重都量化为INT8的模型。

为了进一步提升推理的吞吐,我们基于PageAttention的思想设计并实现了BlockAttention,在保持高性能推理和动态插入的基础上可以动态地为cachekv分配存储空间,极大地节省显存,从而在同一时刻处理更多的query以获得吞吐的提升。下面分别给出关闭BlockAttention和打开BlockAttention进行高性能推理的命令参考。

### 2.2 环境准备

- PaddleNLP develop
- PaddlePaddle develop

PaddleNLP 针对于Transformer 系列编写了高性能自定义算子,提升模型在推理和解码过程中的性能,使用之前需要预先安装自定义算子库:

```shell
git clone https://github.com/PaddlePaddle/PaddleNLP
cd ./paddlenlp/csrc && python setup_cuda.py install
```

### 2.3 关闭BlockAttention的高性能推理

#### 2.3.1 动态图推理

```shell
# 动态图模型推理命令参考
python predictor.py --model_name_or_path meta-llama/Llama-2-7b-chat --inference_model --dtype float16

# PrefixTuning动态图推理参考
python predictor.py --model_name_or_path meta-llama/Llama-2-7b-chat --inference_model --dtype float16 --export_precache true --prefix_path ./checkpoints/llama_prefix_ckpts

# Weight Only Int8 动态图推理参考
python predictor.py --model_name_or_path meta-llama/Llama-2-7b-chat --inference_model --dtype float16 --quant_type weight_only_int8

# PTQ-A8W8推理命令参考
python predictor.py --model_name_or_path checkpoints/llama_ptq_ckpts --inference_model --dtype float16
```
**Note**
1. LoRA 模型在推理之前是需要合并参数,详细可见:[合并 LoRA 参数](https://github.com/PaddlePaddle/PaddleNLP/blob/develop/llm/merge_lora_params.py)
2. PrefixTuning推理需要传入相应的pre_cache,需要额外设置`export_precache``true`,并且传入对应的PrefixTuning参数保存路径`prefix_path`
3. 使用Weight Only Int8 推理需要额外传入 `quant_type`

#### 2.3.2 静态图推理
**step1:动转静**
```shell
# 动转静命令参考
python export_model.py --model_name_or_path meta-llama/Llama-2-7b-chat --inference_model --output_path ./inference --dtype float16

# PrefixTuning动转静命令参考
python export_model.py --model_name_or_path meta-llama/Llama-2-7b-chat --inference_model --output_path ./inference --dtype float16 --export_precache true

# Weight Only Int8 动转静命令参考
python export_model.py --model_name_or_path meta-llama/Llama-2-7b-chat --inference_model --output_path ./inference --dtype float16 --quant_type weight_only_int8

# PTQ-A8W8动转静命令参考
python export_model.py --model_name_or_path checkpoints/llama_ptq_ckpts --inference_model --output_path ./inference --dtype float16
```
**Note**
1. LoRA 模型在推理之前是需要合并参数,详细可见:[合并 LoRA 参数](https://github.com/PaddlePaddle/PaddleNLP/blob/develop/llm/merge_lora_params.py)
2. PrefixTuning推理需要传入相应的pre_cache,需要额外设置`export_precache``true`
3. 使用Weight Only Int8 推理需要额外传入 `quant_type`
4. A8W8推理传入的 `model_name_or_path` 为PTQ校准产出的量化模型。

**step2:静态图推理**
```shell
# 静态图推理命令参考
python predictor.py --model_name_or_path ./inference --inference_model --quant_type weight_only_int8 --dtype "float16" --mode "static"

# PrefixTuning静态图推理命令参考
python predictor.py --model_name_or_path ./inference --inference_model --quant_type weight_only_int8 --dtype "float16" --mode "static" --export_precache true --prefix_path ./checkpoints/llama_prefix_ckpts

# Weight Only Int8 静态图推理命令参考
python predictor.py --model_name_or_path ./inference --inference_model --quant_type weight_only_int8 --dtype "float16" --mode "static" --quant_type weight_only_int8

# PTQ-A8W8静态图推理命令参考
# 以下环境变量用于开启int8矩阵乘的算法选择以获得更快的推理速度,打开之后第一次执行会执行算法选择从而导致速度较慢。
export FLAGS_use_autotune=1
export FLAGS_cublaslt_exhaustive_search_times=10
export FLAGS_cache_inference_while_scope=1

python predictor.py --model_name_or_path ./inference --inference_model --quant_type weight_only_int8 --dtype "float16" --mode "static"
```
**Note**
1. LoRA 模型在推理之前是需要合并参数,详细可见:[合并 LoRA 参数](https://github.com/PaddlePaddle/PaddleNLP/blob/develop/llm/merge_lora_params.py)
2. PrefixTuning推理需要传入相应的pre_cache,需要额外设置`export_precache``true`,并且传入对应的PrefixTuning参数保存路径`prefix_path`
3. 使用Weight Only Int8 推理需要额外传入 `quant_type`
4. A8W8推理传入的 `model_name_or_path` 为PTQ校准产出的量化模型。


### 2.4 打开BlockAttention的高性能推理

#### 2.4.1 动态图推理

```shell
# 动态图模型推理命令参考
python predictor.py --model_name_or_path meta-llama/Llama-2-7b-chat --inference_model --dtype float16 --block_attn

# Weight Only Int8 动态图推理参考
python predictor.py --model_name_or_path meta-llama/Llama-2-7b-chat --inference_model --dtype float16 --quant_type weight_only_int8 --block_attn

# PTQ-A8W8推理命令参考
python predictor.py --model_name_or_path checkpoints/llama_ptq_ckpts --inference_model --dtype float16 --block_attn

# CacheKV 动态量化推理命令参考
python predictor.py --model_name_or_path meta-llama/Llama-2-7b-chat --inference_model --dtype float16 --block_attn --cachekv_int8
```

#### 2.4.2 静态图推理
**step1:动转静**
```shell
# 动转静命令参考
python export_model.py --model_name_or_path meta-llama/Llama-2-7b-chat --inference_model --output_path ./inference --dtype float16 --block_attn

# Weight Only Int8 动转静命令参考
python export_model.py --model_name_or_path meta-llama/Llama-2-7b-chat --inference_model --output_path ./inference --dtype float16 --quant_type weight_only_int8 --block_attn

# PTQ-A8W8动转静命令参考
python export_model.py --model_name_or_path checkpoints/llama_ptq_ckpts --inference_model --output_path ./inference --dtype float16 --block_attn

# CacheKV 动态量化动转静命令参考
python export_model.py --model_name_or_path meta-llama/Llama-2-7b-chat --inference_model --output_path ./inference --dtype float16 --block_attn --cachekv_int8
```

**step2:静态图推理**
```shell
# 静态图推理命令参考
python predictor.py --model_name_or_path ./inference --inference_model --dtype "float16" --mode "static" --block_attn

# Weight Only Int8 静态图推理命令参考
python predictor.py --model_name_or_path ./inference --inference_model --dtype "float16" --mode "static" --quant_type weight_only_int8 --block_attn

# PTQ-A8W8静态图推理命令参考
# 以下环境变量用于开启int8矩阵乘的算法选择以获得更快的推理速度,打开之后第一次执行会执行算法选择从而导致速度较慢。
export FLAGS_use_autotune=1
export FLAGS_cublaslt_exhaustive_search_times=10
export FLAGS_cache_inference_while_scope=1

python predictor.py --model_name_or_path ./inference --inference_model --dtype "float16" --mode "static" --block_attn

# CacheKV 动态量化8静态图推理命令参考
python predictor.py --model_name_or_path ./inference --inference_model --dtype "float16" --mode "static" --cachekv_int8 --block_attn
```
**Note**
1. 使用Weight Only Int8 推理需要额外传入 `quant_type`
2. A8W8推理传入的 `model_name_or_path` 为PTQ校准产出的量化模型。


## 3. 推理参数介绍

- `model_name_or_path`: 必须,预训练模型名称或者本地的模型路径,用于热启模型和分词器,默认为None。
- `batch_size`: 批处理大小,默认为8。该参数越大,占用显存越高;该参数越小,占用显存越低。
- `src_length`: 模型输入上下文最大token长度,默认为1024。
- `max_length`:模型输入(上下文+生成内容)的最大token长度, 默认为2048。
- `lora_path`: LoRA参数和配置路径,对LoRA参数进行初始化,默认为None。
- `prefix_path`: Prefix Tuning参数和配置路径,对Prefix Tuning参数进行初始化,默认为None。
- `top_k`: “采样”策略中为 top-k 过滤保留的最高概率标记的数量。默认为1,等价于贪心策略。
- `top_p`:“采样”策略中 top-p 过滤的累积概率。默认为1.0,表示不起作用。
- `temperature`:“采样”策略中会对输出logit除以temperature。默认为1.0,表示不起作用。
- `data_file`:必须,待推理json文件,默认为None。
- `output_file`:保存推理结果文件名,默认为output.json。
- `device`: 运行环境,默认为gpu。
- `dtype`: 模型参数dtype,默认为None。如果没有传入`lora_path``prefix_path`则必须传入
- `model_type`: 初始化不同类型模型,gpt-3: GPTForCausalLM; ernie-3.5-se: Ernie35ForCausalLM; 默认为 None。
- `mode`: 使用动态图或者静态图推理,值为:[dynamic, static],默认为 dynamic。
- `inference_model`: 是否使用Inference Model 推理,默认值为 False。
- `block_attn`: 是否使用Block Attention 推理, 默认值为False。
- `block_size`: 如果使用Block Attention 推理,指定一个Block可以存储的token数量,默认值为64。
- `cachekv_int8`: 是否使用cachekv int8量化用于节省显存,默认值为False。
4 changes: 2 additions & 2 deletions llm/gradio_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def get_shown_context(context):
with gr.Column(scale=1):
top_k = gr.Slider(
minimum=0,
maximum=default_params.get("top_k", 20),
maximum=100,
value=0,
step=1,
label="Top-k",
Expand Down Expand Up @@ -222,7 +222,7 @@ def get_shown_context(context):
default_src_length = default_params["src_length"]
total_length = default_params["src_length"] + default_params["max_length"]
src_length = create_src_slider(default_src_length, total_length)
max_length = create_max_slider(50, total_length)
max_length = create_max_slider(min(total_length - default_src_length, 50), total_length)

def src_length_change_event(src_length_value, max_length_value):
return create_max_slider(
Expand Down
28 changes: 23 additions & 5 deletions llm/predictor.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,18 @@ def get_eos_token_id(
Returns:
int | List[int]: eos_token_id to stop the generation
"""
if generation_config is None or generation_config.eos_token_id is None:
return tokenizer.eos_token_id
eos_token_ids = []
if tokenizer.eos_token_id is not None:
eos_token_ids.append(tokenizer.eos_token_id)

return generation_config.eos_token_id
if generation_config is not None and generation_config.eos_token_id is not None:
if isinstance(generation_config.eos_token_id, int):
eos_token_ids.append(generation_config.eos_token_id)
else:
eos_token_ids.extend(generation_config.eos_token_id)

eos_token_ids_dict = {str(item): item for item in eos_token_ids}
return list(eos_token_ids_dict.values())


class BasePredictor:
Expand Down Expand Up @@ -991,8 +999,18 @@ def predict():
with open(model_args.data_file, "r", encoding="utf-8") as f:
for line in f:
example = json.loads(line)
source_texts.append(example["src"])
target_texts.append(example["tgt"])
if isinstance(example["src"], str) or predictor.tokenizer.chat_template is None:
if isinstance(example["src"], str):
source_texts.append(example["src"])
target_texts.append(example["tgt"])
else:
# load multi-rounds dataset
source_texts.append(example["src"][0])
target_texts.append(example["tgt"][0])
else:
source_texts.append(list(zip(example["src"], example["tgt"])))
target_texts.append("")

else:
source_texts = ["解释一下“温故而知新”", "你好,请问你是谁?"]
target_texts = ["", ""]
Expand Down
43 changes: 37 additions & 6 deletions paddlenlp/transformers/chatglm_v2/tokenizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
from __future__ import annotations

import os
from typing import Dict, List, Optional, Union
import re
from typing import Any, Dict, List, Optional, Union

import numpy as np
from sentencepiece import SentencePieceProcessor
Expand All @@ -36,16 +37,43 @@ def __init__(self, model_path: str):
self.pad_id: int = self.sp_model.unk_id()
assert self.sp_model.vocab_size() == self.sp_model.get_piece_size()

special_tokens = ["[MASK]", "[gMASK]", "[sMASK]", "sop", "eop"]
special_tokens = [
"[MASK]",
"[gMASK]",
"[sMASK]",
"sop",
"eop",
"<|system|>",
"<|user|>",
"<|assistant|>",
"<|observation|>",
]

self.special_tokens = {}
self.index_special_tokens = {}
for token in special_tokens:
self.special_tokens[token] = self.n_words
self.index_special_tokens[self.n_words] = token
self.n_words += 1

def tokenize(self, s: str):
return self.sp_model.EncodeAsPieces(s)
# add eos/pad/unk token to special_token_expression
all_special_tokens = list(self.special_tokens.keys()) + ["</s>", "<unk>"]
self.special_token_expression = "|".join([re.escape(token) for token in all_special_tokens])

def tokenize(self, s: str, encode_special_tokens=False):
if encode_special_tokens:
last_index = 0
t = []
for match in re.finditer(self.special_token_expression, s):
if last_index < match.start():
t.extend(self.sp_model.EncodeAsPieces(s[last_index : match.start()]))
t.append(s[match.start() : match.end()])
last_index = match.end()
if last_index < len(s):
t.extend(self.sp_model.EncodeAsPieces(s[last_index:]))
return t
else:
return self.sp_model.EncodeAsPieces(s)

def encode(self, s: str, bos: bool = False, eos: bool = False) -> List[int]:
assert type(s) is str
Expand Down Expand Up @@ -85,7 +113,8 @@ class ChatGLMv2Tokenizer(PretrainedTokenizer):
}
}

def __init__(self, vocab_file, padding_side="left", **kwargs):
# always encode special tokens, eg: </s>, [gMASK], [MASK] ...
def __init__(self, vocab_file, padding_side="left", encode_special_tokens=True, **kwargs):
super().__init__(padding_side=padding_side, **kwargs)
self.name = "ChatGLMv2Tokenizer"

Expand All @@ -94,8 +123,10 @@ def __init__(self, vocab_file, padding_side="left", **kwargs):
self.special_tokens = {
"<bos>": self.tokenizer.bos_id,
"<eos>": self.tokenizer.eos_id,
"<unk>": self.tokenizer.pad_id,
"<pad>": self.tokenizer.pad_id,
}
self.encode_special_tokens = encode_special_tokens

def get_command(self, token):
if token in self.special_tokens:
Expand Down Expand Up @@ -130,7 +161,7 @@ def get_vocab(self):
return vocab

def _tokenize(self, text, **kwargs):
return self.tokenizer.tokenize(text)
return self.tokenizer.tokenize(text, encode_special_tokens=self.encode_special_tokens)

def _convert_token_to_id(self, token):
"""Converts a token (str) in an id using the vocab."""
Expand Down
Loading

0 comments on commit 0bd7b55

Please sign in to comment.