消费级显卡部署运行开源大模型

4/8/2023 ChatGLMBaichuanQwenLLaMA

# 1. 前言

# 1.1 大模型发展情况

# 1.1.1 大模型发展史

目前主流的大模型都是Transformer、MOE结构为基础进行构建,如果说Transformer结构使得模型突破到上亿参数量,MoE 稀疏混合专家结构使模型参数量产生进一步突破,达到数万亿规模。下图详细展示了AI大模型的发展历程:

大模型发展历程

可以说,Transformer 开创了继 MLP 、CNN和 RNN之后的第四大类模型。而基于Transformer结构的模型又可以分为Encoder-only、Decoder-only、Encoder-Decoder这三类。

  • 仅编码器架构(Encoder-only):自编码模型(破坏一个句子,然后让模型去预测或填补),更擅长理解类的任务,例如:文本分类、实体识别、关键信息抽取等。典型代表有:Bert、RoBERTa等。
  • 仅解码器架构(Decoder-only):自回归模型(将解码器自己当前步的输出加入下一步的输入,解码器融合所有已经输入的向量来输出下一个向量,所以越往后的输出考虑了更多输入),更擅长生成类的任务,例如:文本生成。典型代表有:GPT系列、LLaMA、OPT、Bloom等。
  • 编码器-解码器架构(Encoder-Decoder):序列到序列模型(编码器的输出作为解码器的输入),主要用于基于条件的生成任务,例如:翻译,概要等。典型代表有:T5、BART、GLM等。

# 1.1.2 LLM技术图谱

LLM 技术图谱(LLM Tech Map)从基础设施、大模型、Agent、AI 编程、工具和平台,以及算力几个方面,为开发者整理了当前 LLM 中最为热门和硬核的技术领域以及相关的软件产品和开源项目。

llm-tech-map

# 1.2 大模型量化与显存估算

# 1.2.1 大模型量化及压缩技术

近年来,随着Transformer、MOE架构的提出,使得深度学习模型轻松突破上万亿规模参数,从而导致模型变得越来越大,因此,我们需要一些大模型压缩技术来降低模型部署的成本,并提升模型的推理性能。大模型压缩主要分为如下几类:剪枝(Pruning)、知识蒸馏(Knowledge Distillation)、量化(Quantization)、低秩分解(Low-Rank Factorization)。

[1] 模型量化

模型量化是指以较低的推理精度损失将连续取值(通常为float32或者大量可能的离散值)的浮点型权重近似为有限多个离散值(通常为int8)的过程。通过以更少的位数表示浮点数据,模型量化可以减少模型尺寸,进而减少在推理时的内存消耗,并且在一些低精度运算较快的处理器上可以增加推理速度。下图中,[-T, T]是量化前的数据范围,[-127, 127]是量化后的数据范围。

模型量化

[2] 量化比特

计算机中不同数据类型的占用比特数及其表示的数据范围各不相同。可以根据实际业务需求将原模型量化成不同比特数的模型,一般深度神经网络的模型用单精度浮点数表示,如果能用有符号整数来近似原模型的参数,那么被量化的权重参数存储大小就可以降到原先的四分之一,用来量化的比特数越少,量化后的模型压缩率越高。

工业界目前最常用的量化位数是8比特,低于8比特的量化被称为低比特量化。1比特是模型压缩的极限,可以将模型压缩为1/32,在推理时也可以使用高效的XNOR和BitCount位运算来提升推理速度。

[3] 量化对象

模型量化的对象主要包括以下几个方面:

  • 权重(weight):weight的量化是最常规也是最常见的,量化weight可达到减少模型大小内存和占用空间。
  • 激活(activation):实际中activation往往是占内存使用的大头,因此量化activation不仅可以大大减少内存占用。更重要的是,结合weight的量化可以充分利用整数计算获得性能提升。
  • KV cache:量化 KV 缓存对于提高长序列生成的吞吐量至关重要。
  • 梯度(Gradients):相对小众一些,在训练深度学习模型时,梯度通常是浮点数,它主要作用是在分布式计算中减少通信开销,同时,也可以减少backward时的开销。

[4] 量化形式

根据量化数据表示的原始数据范围是否均匀,可以将量化方法分为线性量化和非线性量化。实际的深度神经网络的权重和激活值通常是不均匀的,因此理论上使用非线性量化导致的精度损失更小,但在实际推理中非线性量化的计算复杂度较高,通常使用线性量化。

[5] 量化分类

根据应用量化压缩模型的阶段,可以将模型量化分为:

  • 量化感知训练(Quantization Aware Training, QAT):在模型训练过程中加入伪量化算子,通过训练时统计输入输出的数据范围可以提升量化后模型的精度,适用于对模型精度要求较高的场景,其量化目标无缝地集成到模型的训练过程中。这种方法使LLM在训练过程中适应低精度表示,增强其处理由量化引起的精度损失的能力。这种适应旨在量化过程之后保持更高性能。
  • 量化感知微调(Quantization-Aware Fine-tuning,QAF):在微调过程中对LLM进行量化。主要目标是确保经过微调的LLM在量化为较低位宽后仍保持性能。通过将量化感知整合到微调中,以在模型压缩和保持性能之间取得平衡。
  • 训练后量化(Post Training Quantization, PTQ):在LLM训练完成后对其参数进行量化,只需要少量校准数据,适用于追求高易用性和缺乏训练资源的场景。主要目标是减少LLM的存储和计算复杂性,而无需对LLM架构进行修改或进行重新训练。PTQ的主要优势在于其简单性和高效性,但PTQ可能会在量化过程中引入一定程度的精度损失。

# 1.2.2 估算大模型显存占用

方式一:参数量估算法,按照 fp16 格式运行,需要 VRAM 大小(GB) = 参数量(billion) x 2

  • 例1:llama-2-70B, 70 billion * 2 bytes = 140 GB
  • 例2:llama-2-13B, 13 billion * 2 bytes = 26 GB
  • 例3:llama-2-7B, 7 billion * 2 bytes = 14 GB

方式二:使用 HuggingFace Accelerate 工具估算

$ pip3 install git+https://github.com/huggingface/accelerate.git
$ pip3 install jaxlib
$ pip3 install bitsandbytes
$ huggingface-cli login      // 填写Token值,从Huggingface官网的个人账号设置处生成即可
$ accelerate estimate-memory baichuan-inc/Baichuan2-13B-Chat --trust-remote-code

┌────────────────────────────────────────────────────────────┐
│ Memory Usage for loading `baichuan-inc/Baichuan2-13B-Chat` │
├───────┬─────────────┬──────────┬───────────────────────────┤
│ dtype │Largest Layer│Total Size│    Training using Adam    │
├───────┼─────────────┼──────────┼───────────────────────────┤
│float32│    2.4 GB   │ 51.77 GB │         207.08 GB         │
│float16│    1.2 GB   │ 25.88 GB │         103.54 GB         │
│  int8 │  613.75 MB  │ 12.94 GB │            N/A            │
│  int4 │  306.88 MB  │ 6.47 GB  │            N/A            │
└───────┴─────────────┴──────────┴───────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

注:现在LLM的参数大小为什么都设计成6/7B、13/14B、72B、130B等规模?——答案:就是为了匹配显存。

  • 6B模型可以在在12/16/24G显存的消费级显卡部署和训练。如果一个公司的模型不打算在消费级显卡部署,通常不会训6B这个规模。而且通常还会有一个1.4B或者2.8B,这个是比较适合在手机、车载端量化部署的尺寸。
  • 13B模型按照4k长度组织数据,数据并行=2,刚好占满一个8卡机,并且可以量化部署在A10甚至4090。目前更大模型有16B、34B、52B、56B、65B、70B、100B、130B、170B、220B这几个规模,基本都是刚好占满某种规格的算力,要么是训练要么是推理。如果需要加快训练速度,只需要倍增卡数即可。

# 1.3 大模型推理过程可视化

一个大模型推理过程可视化的开源项目。左侧是模型结构总览图,包括模型的整体架构以及构成模型的组件。选择模型整体或某个组件时,右侧可通过鼠标进行交互,并且显示对应详细信息。

$ yarn
$ yarn dev
1
2

大模型推理过程可视化llm-viz

# 2. ChatGLM大模型

实验环境:Macbook Pro 2021,M1 pro芯片,16G内存,macOS Ventura13.2.1系统,Python3.9环境

# 2.1 ChatGLM项目简介

# 2.1.1 ChatGLM-6B

ChatGLM-6B 是清华大学开源的一个支持中英双语的对话语言模型,基于 General Language Model (GLM) (opens new window) 架构,具有 62 亿参数。结合模型量化技术,用户可以在消费级的显卡上进行本地部署(INT4 量化级别下最低只需 6GB 显存)。 ChatGLM-6B 使用了和 ChatGPT 相似的技术,针对中文问答和对话进行了优化。经过约 1T 标识符的中英双语训练,辅以监督微调、反馈自助、人类反馈强化学习等技术的加持,62 亿参数的 ChatGLM-6B 已经能生成相当符合人类偏好的回答。

不过,由于 ChatGLM-6B 的规模较小,目前已知其具有相当多的局限性,如事实性/数学逻辑错误,可能生成有害/有偏见内容,较弱的上下文能力,自我认知混乱,以及对英文指示生成与中文指示完全矛盾的内容。

项目地址:https://github.com/THUDM/ChatGLM-6B (opens new window)

量化等级 最低 GPU 显存(推理) 最低 GPU 显存(高效参数微调)
FP16(无量化) 13 GB 14 GB
INT8 8 GB 9 GB
INT4 6 GB 7 GB

# 2.1.2 ChatGLM2-6B

ChatGLM2-6B 是开源中英双语对话模型 ChatGLM-6B 的第二代版本,在保留了初代模型对话流畅、部署门槛较低等众多优秀特性的基础之上,ChatGLM2-6B 引入了如下新特性:

  • 更强大的性能:相比于初代模型,ChatGLM2-6B 在 MMLU(+23%)、CEval(+33%)、GSM8K(+571%) 、BBH(+60%)等数据集上取得了大幅度的提升。
  • 更长的上下文:基于 FlashAttention 技术,我们将基座模型的上下文长度(Context Length)由 ChatGLM-6B 的 2K 扩展到了 32K。
  • 更高效的推理:基于 Multi-Query Attention 技术,推理速度相比初代提升了 42%,INT4 量化下,6G 显存支持的对话长度由 1K 提升到了 8K。

项目地址:https://github.com/THUDM/ChatGLM2-6B (opens new window)

ChatGLM2-6B 在其他平台要求如下:

量化等级 编码 2048 长度的最小显存 生成 8192 长度的最小显存
FP16 / BF16 13.1 GB 12.8 GB
INT8 8.2 GB 8.1 GB
INT4 5.5 GB 5.1 GB

但在 Mac 平台上,情况更复杂一些:

  • Mac 上只支持本地运行,也就是项目代码和模型分开下载,然后修改 web_demo.py 中模型地址运行。
  • 搭载了 Apple Silicon 或者 AMD GPU 的 Mac,需使用 MPS 后端在 GPU 上运行,修改 web_demo.py 中运行方式。
  • 加载需要 13G 内存,使用过程会不断上涨至 20G 以上,建议使用 32G 以上内存设备。
  • 内存不足设备,可使用量化后的 INT4 模型,但量化后只能使用 CPU 推理,为了充分使用 CPU 并行,还需要 单独安装 OpenMP (opens new window)

Mac部署ChatGLM2-6B

总结: 因为这些限制的存在,对小内存设备及 GPU 较差的设备极不友好,所以这里只推荐 32G 内存以上的 m1 pro/max/ultra 与 m2 pro/max/ultra 设备来进行测试,24G 内存的 m2 也可以尝试。不满足上述条件的Macbook,运行原始模型和量化后的模型速度都太慢,这种情况可以使用chatglm.cpp,运行它就很快了。

# 2.1.3 ChatGLM3-6B

ChatGLM3-6B 是 ChatGLM 系列最新一代的开源模型,在保留了前两代模型对话流畅、部署门槛低等众多优秀特性的基础上,ChatGLM3-6B 引入了如下特性:

  • 更强大的基础模型: ChatGLM3-6B 的基础模型 ChatGLM3-6B-Base 采用了更多样的训练数据、更充分的训练步数和更合理的训练策略。在语义、数学、推理、代码、知识等不同角度的数据集上测评显示,ChatGLM3-6B-Base 具有在 10B 以下的基础模型中最强的性能。
  • 更完整的功能支持: ChatGLM3-6B 采用了全新设计的 Prompt格式 (opens new window),除正常的多轮对话外。同时原生支持工具调用 (opens new window)(Function Call)、代码执行(Code Interpreter)和 Agent 任务等复杂场景。
  • 更全面的开源序列: 除了对话模型 ChatGLM3-6B (opens new window) 外,还开源了基础模型 ChatGLM3-6B-Base (opens new window)、长文本对话模型 ChatGLM3-6B-32K (opens new window)。以上所有权重对学术研究完全开放,在填写问卷进行登记后亦允许免费商业使用。

项目地址:https://github.com/THUDM/ChatGLM3 (opens new window)

模型地址:https://huggingface.co/THUDM/chatglm3-6b/tree/main (opens new window)

ChatGLM3-6B

在 MacOS 上部署详见 低成本部署 (opens new window) 官方文档

对于搭载了 Apple Silicon 或者 AMD GPU 的 Mac,可以使用 MPS 后端来在 GPU 上运行 ChatGLM3-6B。需要参考 Apple 的 官方说明 (opens new window) 安装 PyTorch-Nightly。

$ conda install pytorch torchvision torchaudio -c pytorch-nightly        // 使用conda安装
$ pip3 install --pre torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/nightly/cpu  // 使用pip安装
1
2

目前在 MacOS 上只支持从本地加载模型,将代码中的模型加载改为从本地加载,并使用 mps 后端:

model = AutoModel.from_pretrained("your local path", trust_remote_code=True).to('mps')
1

加载半精度的 ChatGLM3-6B 模型需要大概 13GB 内存。内存较小的机器(比如 16GB 内存的 MacBook Pro),在空余内存不足的情况下会使用硬盘上的虚拟内存,导致推理速度严重变慢。

# 2.2 ChatGLM2-6B运行及使用

# 2.2.1 运行非量化的原始模型

Step1:拉取项目代码及模型

$ git clone https://github.com/THUDM/ChatGLM2-6B
$ cd ChatGLM2-6B
1
2

ChatGLM2-6B模型下载地址:

这里将下载好的模型及代码放置到./models/chatglm2-6b文件夹。

ChatGLM2-6B模型

Step2:拉取项目依赖并修改代码

$ pip3 install -r requirements.txt
1

修改web_demo.py文件

tokenizer = AutoTokenizer.from_pretrained("models/chatglm2-6b", trust_remote_code=True)
model = AutoModel.from_pretrained("models/chatglm2-6b", trust_remote_code=True).to('mps')
1
2

注:目前在 MacOS 上只支持从本地加载模型,将代码中的模型加载改为从本地加载,并使用 mps 后端:

model = AutoModel.from_pretrained("your local path", trust_remote_code=True).to('mps')
1

Step3:启动Web程序并使用

启动 web_demo.py 文件,待其加载完毕后会自动弹出 http://127.0.0.1:7860的界面,在下方输入框输入问题,点击“Submit“按钮即可。

ChatGLM2-6B内存不足时速度很慢

注:由于我使用的是 16GB 内存的 MacBook Pro,在空余内存不足的情况下会使用硬盘上的虚拟内存,导致推理速度严重变慢,这里就提前终止了。

# 2.2.2 运行量化后的模型

整体流程与之前类似,不再赘述。换成官方的int4量化模型(量化后的模型仅有3.9GB,比原始模型小了很多),改一下代码的模型加载方式即可。

ChatGLM2-6B-int4 模型下载地址:

修改web_demo.py文件:

tokenizer = AutoTokenizer.from_pretrained("models/chatglm2-6b-int4", trust_remote_code=True)
model = AutoModel.from_pretrained("models/chatglm2-6b-int4",trust_remote_code=True).float()
1
2

可以看到,使用CPU加载量化模型的推理速度要比之前内存不足时使用GPU加载原始模型要快不少,但这个速度依然无法实际投入使用。

ChatGLM2-6B使用CPU加载量化模型

这个速度勉强可以拿来测试API了,同样的当时修改api.py文件。默认部署在本地的 8000 端口,通过 POST 方法进行调用:

curl -X POST "http://127.0.0.1:8000" \
     -H 'Content-Type: application/json' \
     -d '{"prompt": "你好", "history": []}'
1
2
3

得到的返回值如下:

ChatGLM2-6B的API

# 2.3 chatglm.cpp运行GGML量化模型

# 2.3.1 chatglm.cpp项目简介

项目简介:ChatGLM-6B、ChatGLM2-6B、ChatGLM3-6B 及更多大模型的 C++ 实现。

项目地址:https://github.com/li-plus/chatglm.cpp (opens new window)

# 2.3.2 安装chatglm.cpp及相关依赖

需要编译 chatglm.cpp 并安装相关依赖。

$ git clone https://github.com/li-plus/chatglm.cpp.git
$ git submodule update --init --recursive
$ pip3 install torch tabulate tqdm transformers accelerate sentencepiece
$ pip3 install .
1
2
3
4

注意事项:

[1] 不要漏掉 git submodule update --init --recursive,不然在 pip3 install .时会遇到如下报错:

编译chatglm.cpp存在的坑

[2] 如果启动 ./examples/web_demo.py 时报错httpx._exceptions.InvalidURL: No scheme included in URL,安装以下依赖即可解决问题。

$ pip3 install httpx==0.25.0
1

# 2.3.3 将模型转换为量化GGML格式

以 ChatGLM2-6B 为例(ChatGLM3-6B 与之相同),使用 convert.py将 ChatGLM2-6B 转换为量化 GGML 格式。

$ python3 ./chatglm_cpp/convert.py -i /your_path/chatglm2-6b -t q4_0 -o chatglm2-ggml.bin
1

输出以下内容,即为量化成功,得到 chatglm2-ggml.bin 模型文件。

将ChatGLM2-6B模型转换为量化GGML格式

# 2.3.4 ChatGLM大模型的运行及使用

以 ChatGLM2-6B 为例(ChatGLM3-6B 与之相同),启动 web_demo.py 项目,访问 http://127.0.0.1:7860/ 地址即可访问,现在生成速度很快了。

$ python3 ./examples/web_demo.py -m ./chatglm2-ggml.bin
1

使用chatglm.cpp运行ChatGLM2-6B

除此之外,还可以使用如下命令以交互终端的形式启动:

$ python3 ./examples/cli_chat.py -m ./chatglm2-ggml.bin -i
1

使用chatglm.cpp以交互终端形式运行ChatGLM2-6B

# 3. Baichuan大模型

# 3.1 Baichuan项目

实验环境:租用的揽睿星舟的GPU服务器,NVIDIA RTX 4090 / 24GB,Python 3.10.6, CUDA 11.7

# 3.1.1 Baichuan项目简介

Baichuan-13B 是由百川智能继 Baichuan-7B (opens new window) 之后开发的包含 130 亿参数的开源可商用的大规模语言模型,在权威的中文和英文 benchmark 上均取得同尺寸最好的效果。本次发布包含有预训练 (Baichuan-13B-Base (opens new window)) 和对齐 (Baichuan-13B-Chat (opens new window)) 两个版本。Baichuan-13B 有如下几个特点:

  • 更大尺寸、更多数据:Baichuan-13B 在 Baichuan-7B的基础上进一步扩大参数量到 130 亿,并且在高质量的语料上训练了 1.4 万亿 tokens,超过 LLaMA-13B 40%,是当前开源 13B 尺寸下训练数据量最多的模型。支持中英双语,使用 ALiBi 位置编码,上下文窗口长度为 4096。
  • 同时开源预训练和对齐模型:预训练模型是适用开发者的『 基座 』,而广大普通用户对有对话功能的对齐模型具有更强的需求。因此本次开源我们同时发布了对齐模型(Baichuan-13B-Chat),具有很强的对话能力,开箱即用,几行代码即可简单的部署。
  • 更高效的推理:为了支持更广大用户的使用,我们本次同时开源了 int8 和 int4 的量化版本,相对非量化版本在几乎没有效果损失的情况下大大降低了部署的机器资源门槛,可以部署在如 Nvidia 3090 这样的消费级显卡上。
  • 开源免费可商用:Baichuan-13B 不仅对学术研究完全开放,开发者也仅需邮件申请并获得官方商用许可后,即可以免费商用。

GPU资源占用情况:

Precision GPU Mem (GB)
bf16 / fp16 26.0
int8 15.8
int4 9.7

# 3.1.2 运行Baichuan-13B-Chat

拉取项目代码并安装项目依赖。

$ git clone https://github.com/baichuan-inc/Baichuan-13B.git
$ cd Baichuan-13B 
$ pip3 config set global.index-url http://mirrors.aliyun.com/pypi/simple/
$ pip3 config set install.trusted-host mirrors.aliyun.com
$ pip3 install -r requirements.txt
1
2
3
4
5

推理所需的模型权重、源码、配置已发布在HuggingFace:Baichuan-13B-Base (opens new window)Baichuan-13B-Chat (opens new window)

Baichuan-13B-Chat模型资源

程序首次执行时会自动从 HuggingFace 下载所需的模型资源,总共约26.5GB。

$ python3 cli_demo.py
1

原始精度在我这个服务器上都跑不起来,所以这里修改代码使用了int8量化精度。

def init_model():
    print("init model ...")
    # 加载原始精度模型到CPU
    model = AutoModelForCausalLM.from_pretrained(
        "baichuan-inc/Baichuan-13B-Chat",
        torch_dtype=torch.float16,
        trust_remote_code=True
    )
    # 量化模型为int4精度
    model = model.quantize(8).cuda()

    model.generation_config = GenerationConfig.from_pretrained(
        "baichuan-inc/Baichuan-13B-Chat"
    )
    
    tokenizer = AutoTokenizer.from_pretrained(
        "baichuan-inc/Baichuan-13B-Chat",
        use_fast=False,
        trust_remote_code=True
    )
    return model, tokenizer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

运行效果:

运行Baichuan-13B项目

# 3.2 Baichuan2项目

实验环境:租用的AutoDL的GPU服务器,NVIDIA RTX 4090 / 24GB,Ubuntu20.04,Python 3.8, CUDA 11.3

# 3.2.1 Baichuan2项目简介

选用当下效果比较好的Baichuan2-13B-Chat大模型

显存要求如下表所示,由于租用的显卡只有24GB显存,因此只能跑8bits量化模型。如果你的显卡资源够,可以跑全精度,代码改成model = model.cuda(),以下将会提供普通服务和流式服务两种调用方式。

Precision Baichuan2-7B Baichuan2-13B
bf16 / fp16 15.3 27.5
8bits 8.0 16.1
4bits 5.1 8.6

# 3.2.2 准备服务部署代码及环境

[1] 准备部署代码

普通服务:baichuan_api_server.py

# -*- coding: utf-8 -*-

from flask import Flask, request
from flask_cors import cross_origin
import json
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.generation.utils import GenerationConfig
import datetime

model_path = '/Path/Baichuan2-13B-Chat'
model = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype=torch.float16,
                                             trust_remote_code=True)
torch.cuda.set_device(0)  # 指定显卡
# model = model.cuda()
model = model.quantize(8).cuda()
model.generation_config = GenerationConfig.from_pretrained(
    model_path
)
tokenizer = AutoTokenizer.from_pretrained(
    model_path,
    use_fast=False,
    trust_remote_code=True
)
model.eval()

app = Flask(__name__)


@app.route('/', methods=['POST'])
@cross_origin()
def batch_chat():
    global model, tokenizer

    data = json.loads(request.get_data())
    now = datetime.datetime.now()
    time_format = now.strftime("%Y-%m-%d %H:%M:%S")
    try:
        messages = data.get("messages")
        response = model.chat(tokenizer, messages)
        answer = {"response": response, "history": [], "status": 200, "time": time_format}
        return answer
    except Exception as e:
        return {"response": f"大模型预测出错:{repr(e)}", "history": [('', '')], "status": 444, "time": time_format}


if __name__ == '__main__':
    with torch.no_grad():
        app.run(host='0.0.0.0', port=1707)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

后台启动服务:

$ nohup python3 baichuan_api_server.py > baichuan_api_server.log 2>&1 &           
1

流式服务:baichuan_stream_api_server.py

# -*- coding: utf-8 -*-

import argparse
from flask import Flask, request, Response
from flask_cors import cross_origin
import json
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.generation.utils import GenerationConfig

model_path = '/Path/Baichuan2-13B-Chat'
model = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype=torch.float16, device_map="auto",
                                             trust_remote_code=True)
torch.cuda.set_device(0)  # 指定显卡
# model = model.cuda()
model = model.quantize(8).cuda()
model.generation_config = GenerationConfig.from_pretrained(
    model_path
)
tokenizer = AutoTokenizer.from_pretrained(
    model_path,
    use_fast=False,
    trust_remote_code=True
)
model.eval()

app = Flask(__name__)


def solve(messages):
    position = 0
    for response in model.chat(tokenizer, messages, stream=True):
        chunk = response[position:]
        yield chunk
        position = len(response)


@app.route('/', methods=['POST'])
@cross_origin()
def batch_chat():
    global model, tokenizer

    data = json.loads(request.get_data())
    messages = data.get("messages")
    return Response(solve(messages), content_type='text/plain; charset=utf-8')


parser = argparse.ArgumentParser(description='')
parser.add_argument('--port', default=1708, type=int, help='服务端口')
args = parser.parse_args()

if __name__ == '__main__':
    with torch.no_grad():
        app.run(host='0.0.0.0', port=args.port)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

后台启动服务:

$ nohup python3 baichuan_stream_api_server.py > baichuan_stream_api_server.log 2>&1 & 
1

[2] 下载模型并安装依赖

Step1:下载模型文件

模型地址:https://huggingface.co/baichuan-inc/Baichuan2-13B-Chat/tree/main (opens new window)

Baichuan2-13B-Chat模型

可以使用 HuggingFace Hub 下载模型文件,首先,我们需要安装huggingface_hub依赖。

$ pip3 install huggingface_hub
1

之后执行该脚本即可。

# -*- coding: utf-8 -*-

import os
from huggingface_hub import snapshot_download

# 模型仓库的标识
repo_id = "baichuan-inc/Baichuan2-13B-Chat"

# 下载模型到指定目录
local_dir = "./{}".format(repo_id)

# 检查目录是否存在,如果不存在则创建
if not os.path.exists(local_dir):
    os.makedirs(local_dir)

snapshot_download(repo_id=repo_id, local_dir=local_dir)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Step2:安装依赖环境

torch环境使用服务器镜像自带的(没有的话 pip3 install torch 安装一下)。

$ pip3 install flask 
$ pip3 install flask_cors
$ pip3 install accelerate 
$ pip3 install sentencepiece
$ pip3 install scipy
$ pip3 install transformers==4.33.2  
$ pip3 install xformers
$ pip3 install bitsandbytes
1
2
3
4
5
6
7
8

# 3.2.3 大模型服务的使用效果

[1] 使用普通服务 baichuan_api_test.py

# -*- coding: utf-8 -*-


import requests
import json


class Baichuan:
    def __init__(self, url):
        self.url = url

    def __call__(self, messages: list) -> str:
        data = {"messages": messages}
        response = requests.post(self.url, json=data)
        response = json.loads(response.content)
        return response["response"]


if __name__ == '__main__':
    llm = Baichuan("http://127.0.0.1:1707/")
    messages = [{
        "role": "user",
        "content": "解释一下量子计算"
    }]
    response = llm(messages)
    print(response)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

[2] 使用流式服务 baichuan_stream_api_test.py

# -*- coding: utf-8 -*-

import requests
import json


class Baichuan:
    def __init__(self, url):
        self.url = url

    def stream_request(self, messages: list):
        data = json.dumps({"messages": messages})
        try:
            with requests.post(self.url, data=data, headers={'Content-Type': 'application/json'}, stream=True) as response:
                response.raise_for_status()
                for line in response.iter_lines():
                    if line:
                        decoded_chunk = line.decode('utf-8')
                        yield decoded_chunk
        except requests.RequestException as e:
            print(f"请求错误: {e}")


if __name__ == '__main__':
    llm = Baichuan("http://127.0.0.1:1708")
    messages = [{
        "role": "user",
        "content": "解释一下量子计算"
    }]
    for response in llm.stream_request(messages):
        print(response)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

注:使用流式服务,可以让结果随着推理过程,一点儿一点儿的往外输出,用户体验更好,但使用流式服务会比普通服务更耗资源。

[3] 运行出的效果

以下是 Baichuan2-13B-Chat 模型在 8bits 量化的运行效果。

Baichuan2-13B-Chat-8bits量化的运行效果

# 3.2.4 对大模型服务进行压测

可使用如下脚本对普通服务进行压测。

# -*- coding: utf-8 -*-

from typing import Union, Any, List, Tuple
import requests
import json
import threading
import time


class Baichuan:
    def __init__(self, url):
        self.url = url

    def send_request(self, messages: List[dict]) -> Tuple[bool, Union[str, Any], float]:
        start_time = time.time()
        try:
            data = {"messages": messages}
            response = requests.post(self.url, json=data)
            response = json.loads(response.content)
            success = True
        except Exception as e:
            response = str(e)
            success = False
        end_time = time.time()
        return success, response, end_time - start_time


def worker(url, messages, index, stats):
    bc = Baichuan(url)
    success, response, duration = bc.send_request(messages)
    with stats['lock']:
        if success:
            stats['success_count'] += 1
        else:
            stats['failure_count'] += 1

        stats['total_duration'] += duration
        if duration < stats['min_duration']:
            stats['min_duration'] = duration
        if duration > stats['max_duration']:
            stats['max_duration'] = duration

    print(f"Thread {index}: {'Success' if success else 'Failure'}, Response: {response}, Duration: {duration}s")


if __name__ == '__main__':
    url = "http://127.0.0.1:1707/"
    messages = [{
        "role": "user",
        "content": "解释一下量子计算"
    }]

    num_threads = 10  # 测试并发线程数
    num_rounds = 3  # 测试轮数

    stats = {
        'success_count': 0,
        'failure_count': 0,
        'total_duration': 0.0,
        'min_duration': float('inf'),
        'max_duration': float('-inf'),
        'lock': threading.Lock()
    }

    for round in range(num_rounds):
        print(f"开始测试轮数: {round + 1}")
        threads = []

        for i in range(num_threads):
            thread = threading.Thread(target=worker, args=(url, messages, i, stats))
            threads.append(thread)
            thread.start()

        for thread in threads:
            thread.join()

    # 输出总体统计结果
    avg_duration = stats['total_duration'] / (num_threads * num_rounds) if num_threads > 0 else 0
    print(f"总成功次数: {stats['success_count']}, 总失败次数: {stats['failure_count']}")
    print(f"整体最短耗时: {stats['min_duration']:.2f}s, 整体最长耗时: {stats['max_duration']:.2f}s, 整体平均耗时: {avg_duration:.2f}s")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

# 3.2.5 将大模型服务接入RAG

[1] 构建ES检索库

BM25为ES默认的相关性排序算法,因此我们使用ES检索库本质上是利用了BM25算法。以下将会搭建ES环境、安装ik分词器,并将知识库数据导入检索库。

用于构建检索库的数据这里是处理成JSON格式了,示例如下:

数据预处理后的格式-用于构建检索库

Step1:搭建Docker环境

$ apt-get update -y && apt-get install curl -y  # 安装curl
$ curl https://get.docker.com | sh -   # 安装docker
$ sudo systemctl start docker  # 启动docker服务(改成restart即为重启服务)
$ docker version # 查看docker版本(客户端要与服务端一致)
1
2
3
4

Step2:使用Docker搭建ElasticSearch

$ docker pull elasticsearch:7.16.2
$ docker run -d --name es \
-p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms1g -Xmx1g" \
elasticsearch:7.16.2
$ docker update es --restart=always
1
2
3
4
5
6

Step3:进入容器给ElasticSearch配置密码

$ docker exec -it es /bin/bash 
$ cd config
$ chmod o+w elasticsearch.yml
$ vi elasticsearch.yml
1
2
3
4

其中,在 elasticsearch.yml 文件的末尾添加以下配置,代表开启xpack安全认证)

xpack.security.enabled: true    
1

然后把权限修改回来,重启容器,设置账号密码,浏览器访问http://IP:9200地址即可(用 elastic账号 和自己设置的密码登录即可)

$ chmod o-w elasticsearch.yml
$ exit
$ docker restart es
$ docker exec -it es /bin/bash 
$ ./bin/elasticsearch-setup-passwords interactive   // 然后设置一大堆账号密码
1
2
3
4
5

Step4:安装ik分词器插件

$ docker exec -it es /bin/bash
$ apt-get install -y wget   
$ wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.16.2/elasticsearch-analysis-ik-7.16.2.zip
$ unzip -o -d /usr/share/elasticsearch/elasticsearch-analysis-ik-7.16.2 /usr/share/elasticsearch/elasticsearch-analysis-ik-7.16.2.zip
$ rm –f elasticsearch-analysis-ik-7.16.2.zip
$ mv /usr/share/elasticsearch/elasticsearch-analysis-ik-7.16.2 /usr/share/elasticsearch/plugins/ik
$ cd /usr/share/elasticsearch/bin
$ elasticsearch-plugin list
$ exit
$ docker restart es
1
2
3
4
5
6
7
8
9
10

Step5:构建ES索引并写入数据

安装 elasticsearch 依赖

$ pip3 install elasticsearch
1

es_index.py

# -*- coding: utf-8 -*-

import json
from elasticsearch import Elasticsearch
from elasticsearch import helpers

index_name = "policy_qa"
es = Elasticsearch(
    hosts=["http://127.0.0.1:9200"],
    basic_auth=("elastic", "your_password"),
    request_timeout=60
)
CREATE_BODY = {
    "settings": {
        "number_of_replicas": 0
    },
    "mappings": {
        "properties": {
            "content": {
                "type": "text",
                "analyzer": "ik_max_word"
            }
        }
    }
}

es.indices.create(index=index_name, body=CREATE_BODY)
contents = []

with open("./preprocess_data/preprocess_data.json", "r", encoding="utf-8") as file:
    temp = json.load(file)
contents = contents + temp

action = (
    {
        "_index": index_name,
        "_type": "_doc",
        "_id": i,
        "_source": {
            "content": contents[i]
        }
    } for i in range(0, len(contents))
)
helpers.bulk(es, action)

print("export es finish")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

执行该文件,将预处理的数据导入ES索引库。

将预处理的数据导入ES索引库

[2] 构建ES文档检索服务

es_search.py

# -*- coding: utf-8 -*-

import json
from flask import Flask, request
from flask_cors import cross_origin
from elasticsearch import Elasticsearch

index_name = "policy_qa"
es = Elasticsearch(
    hosts=["http://127.0.0.1:9200"],
    basic_auth=("elastic", "your_password"),
    request_timeout=60
)

app = Flask(__name__)


@app.route('/', methods=['POST'])
@cross_origin()
def retrieval():
    data = json.loads(request.get_data())
    question = data.get("question")
    top_k = data.get("top_k")
    query_body = {
        "query": {
            "match": {
                "content": question
            }
        },
        "size": top_k
    }
    res = es.search(index=index_name, body=query_body)
    docs = []
    for hit in res['hits']['hits']:
        docs.append(hit["_source"]["content"])
    return {"docs": docs}


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=1709)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

启动ES检索服务,下面会用到。

[3] 基于ES检索增强生成回答

solve.py

# -*- coding: utf-8 -*-

import os
import requests
import json

# Global Parameters
RETRIEVAL_TOP_K = 2
LLM_HISTORY_LEN = 30


class Baichuan:
    def __init__(self, url):
        self.url = url

    def __call__(self, messages: list) -> str:
        data = {"messages": messages}
        response = requests.post(self.url, json=data)
        response = json.loads(response.content)
        return response["response"]


def init_cfg(url_llm):
    global llm
    llm = Baichuan(url=url_llm)


def get_docs(question: str, url: str, top_k=RETRIEVAL_TOP_K):
    data = {"question": question, "top_k": top_k}
    docs = requests.post(url, json=data)
    docs = json.loads(docs.content)
    return docs["docs"]


def get_knowledge_based_answer(query, history_obj, url_retrieval):
    global llm, RETRIEVAL_TOP_K

    if len(history_obj.history) > LLM_HISTORY_LEN:
        history_obj.history = history_obj.history[-LLM_HISTORY_LEN:]

    # Rewrite question
    if len(history_obj.history):
        rewrite_question_input = history_obj.history.copy()
        rewrite_question_input.append(
            {
                "role": "user",
                "content": f"""请基于对话历史,对后续问题进行补全重构,如果后续问题与历史相关,你必须结合语境将代词替换为相应的指代内容,让它的提问更加明确;否则直接返回原始的后续问题。
                注意:请不要对后续问题做任何回答和解释。
                
                后续问题:{query}
                
                修改后的后续问题:"""
            }
        )
        new_query = llm(rewrite_question_input)
    else:
        new_query = query

    # 获取相关文档
    docs = get_docs(new_query, url_retrieval, RETRIEVAL_TOP_K)
    doc_string = ""
    for i, doc in enumerate(docs):
        doc_string = doc_string + doc + "\n"
    history_obj.history.append(
        {
            "role": "user",
            "content": f"请基于参考,回答问题,并给出参考依据:\n问题:\n{query}\n参考:\n{doc_string}\n答案:"
        }
    )

    # 调用大模型获取回复
    response = llm(history_obj.history)

    # 修改history,将之前的参考资料从history删除,避免history太长
    history_obj.history[-1] = {"role": "user", "content": query}
    history_obj.history.append({"role": "assistant", "content": response})

    # 检查history.json是否存在,如果不存在则创建
    if not os.path.exists("./history.json"):
        with open("./history.json", "w", encoding="utf-8") as file:
            json.dump([], file, ensure_ascii=False, indent=2)

    # 读取现有数据,追加新数据,并写回文件
    with open("./history.json", "r", encoding="utf-8") as file:
        data = json.load(file)
    data.append({"query": query, "new_query": new_query, "docs": docs, "response": response,
                 "retrieval": "ES"})
    with open("./history.json", "w", encoding="utf-8") as file:
        json.dump(data, file, ensure_ascii=False, indent=2)

    return {"response": response, "docs": docs}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

server.py

# -*- coding: utf-8 -*-

from flask import Flask, request
from flask_cors import cross_origin
import time
from solve import *

app = Flask(__name__)


class History:
    def __init__(self):
        self.history = []


session_histories = {}


@app.route("/get_bot_response", methods=["POST"])
@cross_origin()
def get_bot_response():
    global session_histories
    data = json.loads(request.get_data())
    userText = data["content"]  # 用户输入
    session_id = data["id"]  # 用户id,用于保存对话历史

    # 获取对话历史,如果有的话
    if session_id in session_histories:
        history_obj = session_histories[session_id]["history"]
        session_histories[session_id]["last_access_time"] = time.time()
    else:
        history_obj = History()
        session_histories[session_id] = {
            "history": history_obj,
            "last_access_time": time.time(),
        }

    # 如果用户超过一个小时没有交互,则删除该用户的对话历史
    max_idle_time = 60 * 60
    for session_id, session_data in session_histories.copy().items():
        idle_time = time.time() - session_data["last_access_time"]
        if idle_time > max_idle_time:
            del session_histories[session_id]

    if userText == "清空对话历史":
        history_obj.history = []
        return str("已清空")

    response = get_knowledge_based_answer(
        query=userText, history_obj=history_obj, url_retrieval="http://127.0.0.1:1709/"
    )
    return response


if __name__ == "__main__":
    init_cfg("http://127.0.0.1:1707/")
    app.run(host="0.0.0.0", port=1710)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

rag_test.py

# -*- coding: utf-8 -*-

import json
import requests
import random
from tqdm import trange


if __name__ == '__main__':
    url = "http://127.0.0.1:1710/get_bot_response"
    question = ["什么是政府专项债务?", "专项债收入可以用于经常性支出吗?", "政府专项债务应当通过什么偿还?"]
    for i in trange(len(question)):
        data = {"id": random.randint(0, 9999999), "content": question[i]}
        res = requests.post(url, json=data)
        res = json.loads(res.content)
        print("\nQuestion: " + question[i])
        print("\nAnswer: " + res["response"])
        print("\n-------------------------------------------------")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

先执行 server.py 启动 ES 检索增强大模型生成服务,再执行 rag_test.py 进行测试。输出里会有个 history.json 文件,记录中间过程及结果。

基于ES检索增强生成回答的效果

# 4. LLaMA大模型

# 4.1 Chinese-Llama-2项目

实验环境:租用的揽睿星舟的GPU服务器,NVIDIA RTX 4090 / 24GB,Python 3.10.6, CUDA 11.7

# 4.1.1 Chinese-Llama-2简介

完全可商用的 中文版Llama2模型 (opens new window)中英文SFT数据集 (opens new window),输入格式严格遵循 llama-2-chat 格式,兼容适配所有针对原版 llama-2-chat 模型的优化。

Chinese-Llama-2-7b在线体验

# 4.1.2 Chinese-Llama-2的运行与使用

拉取项目代码并安装项目依赖。

$ git clone https://github.com/LinkSoul-AI/Chinese-Llama-2-7b
$ cd Chinese-Llama-2-7b
$ pip3 config set global.index-url http://mirrors.aliyun.com/pypi/simple/
$ pip3 config set install.trusted-host mirrors.aliyun.com
$ pip3 install accelerate bitsandbytes gradio protobuf scipy sentencepiece transformers  // 这里参照官方Dockerfile写,但不要带版本号
1
2
3
4
5

注:使用官方Dockerfile上面指定版本号的依赖,会导致CUDA版本与之不匹配,导致不可用的情况。

CUDA Setup failed despite GPU being available. Please run the following command to get more information:
    python -m bitsandbytes
    Inspect the output of the command and see if you can locate CUDA libraries. You might need to add them
    to your LD_LIBRARY_PATH. If you suspect a bug, please take the information from python -m bitsandbytes
    and open an issue at: https://github.com/TimDettmers/bitsandbytes/issues
1
2
3
4
5

可以新建个test.py测试一下功能,程序首次执行时会自动从 HuggingFace (opens new window) 下载所需的模型资源,共计约26.95GB。

from transformers import AutoTokenizer, AutoModelForCausalLM, TextStreamer

model_path = "LinkSoul/Chinese-Llama-2-7b"

tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False)
model = AutoModelForCausalLM.from_pretrained(model_path).half().cuda()
streamer = TextStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)

instruction = """[INST] <<SYS>>\nYou are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe.  Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.

            If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information.\n<</SYS>>\n\n{} [/INST]"""

prompt = instruction.format("用中文回答,When is the best time to visit Beijing, and do you have any suggestions for me?")
generate_ids = model.generate(tokenizer(prompt, return_tensors='pt').input_ids.cuda(), max_new_tokens=4096, streamer=streamer)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Chinese-Llama-2-7b测试功能

除此之外,还可将其部署成API服务。

$ nohup python3 api.py > api.log 2>&1 &
$ curl -X POST "http://127.0.0.1:8000" \
     -H 'Content-Type: application/json' \
     -d '{"prompt": "解释一下量子计算", "history": []}'
1
2
3
4

Chinese-Llama-2-7b运行效果

# 4.2 Alpaca项目

实验环境:Macbook Pro 2021,M1 pro芯片,16G内存,macOS Ventura13.2.1系统,Python3.9环境

# 4.2.1 alpaca.cpp项目简介

项目简介:笔记本本地运行体验Alpaca-7B/13B大模型。

  • 该模型基于斯坦福Alpaca-7B、13B大模型进行量化后进行封装,实现常见笔记本配置下体验大模型的效果。
  • 加载模型仅需4G左右内存(7B版本),且语句生成速度很快。

项目地址:https://github.com/antimatter15/alpaca.cpp (opens new window)(原作者的)、https://github.com/ymcui/alpaca.cpp (opens new window)(Fork版,给出了量化后的权重模型)

# 4.2.2 alpaca.cpp运行及使用

Step1:拉取项目代码

$ git clone https://github.com/antimatter15/alpaca.cpp
$ cd alpaca.cpp
1
2

注意:一定要使用最新的代码去编译,同时后续有新模型放出之后也建议检查一下代码是否是最新的,要重新编译./chat二进制文件。

Step2:下载权重

下载权重文件(.zip压缩包),解压之后把相应模型放到clone下来的根目录里,我这里下载的是 ggml-alpaca-7b-q4.bin 模型。

文件名 文件大小 参数量 占用显存 网盘地址
ggml-alpaca-7b-q4.bin 4.2G 7B 4G+ Alpaca-7B (opens new window)
ggml-alpaca-13b-q4.bin 8.1G 13B 8G+ Alpaca-13B (opens new window)

Step3:编译并启动

首先使用以下命令进行编译

$ make chat
1

默认以下面的命令启动7B版本。

$ ./chat
1

注:如需加载13B版本,用 -m 命令指定模型文件。

$ ./chat -m ggml-alpaca-13b-q4.bin
1

Step4:效果体验

在提示符 > 之后输入你的prompt,command+c中断输出。

alpaca.cpp效果

# 5. Qwen大模型

实验环境:租用的AutoDL的GPU服务器,NVIDIA RTX 4090 / 24GB,Ubuntu20.04,Python 3.10, CUDA 11.8

# 5.1 Qwen1.5项目简介

Qwen1.5 是 Qwen2 的测试版,Qwen1.5 是基于 transformer 的 decoder-only 语言模型,已在大量数据上进行了预训练。与之前发布的 Qwen 相比,Qwen1.5 的改进包括 6 种模型大小,包括 0.5B、1.8B、4B、7B、14B、32B和 72B;Chat模型在人类偏好方面的性能显著提高;基础模型和聊天模型均支持多种语言;所有大小的模型均稳定支持 32K 上下文长度,无需 trust_remote_code。

# 5.2 运行Qwen1.5-7B项目

# 5.2.1 安装依赖并下载模型

先安装运行项目所需要的依赖,然后执行 download_model.py 脚本,从 HuggingFace 下载模型到指定目录。

$ python -m pip install --upgrade pip
$ pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

$ pip install fastapi==0.104.1
$ pip install uvicorn==0.24.0.post1
$ pip install requests==2.25.1
$ pip install modelscope==1.11.0
$ pip install transformers==4.37.0
$ pip install streamlit==1.24.0
$ pip install sentencepiece==0.1.99
$ pip install accelerate==0.24.1
$ pip install transformers_stream_generator==0.0.4
1
2
3
4
5
6
7
8
9
10
11
12

download_model.py

# -*- coding: utf-8 -*-

import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os

model_dir = snapshot_download('qwen/Qwen1.5-7B-Chat', cache_dir='/root/autodl-tmp', revision='master')
1
2
3
4
5
6
7

# 5.2.2 以API的形式使用

api_server.py

# -*- coding: utf-8 -*-

from fastapi import FastAPI, Request
from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig
import uvicorn
import json
import datetime
import torch

# 设置设备参数
DEVICE = "cuda"  # 使用CUDA
DEVICE_ID = "0"  # CUDA设备ID,如果未设置则为空
CUDA_DEVICE = f"{DEVICE}:{DEVICE_ID}" if DEVICE_ID else DEVICE  # 组合CUDA设备信息

# 清理GPU内存函数
def torch_gc():
    if torch.cuda.is_available():  # 检查是否可用CUDA
        with torch.cuda.device(CUDA_DEVICE):  # 指定CUDA设备
            torch.cuda.empty_cache()  # 清空CUDA缓存
            torch.cuda.ipc_collect()  # 收集CUDA内存碎片

# 创建FastAPI应用
app = FastAPI()

# 处理POST请求的端点
@app.post("/")
async def create_item(request: Request):
    global model, tokenizer  # 声明全局变量以便在函数内部使用模型和分词器
    json_post_raw = await request.json()  # 获取POST请求的JSON数据
    json_post = json.dumps(json_post_raw)  # 将JSON数据转换为字符串
    json_post_list = json.loads(json_post)  # 将字符串转换为Python对象
    prompt = json_post_list.get('prompt')  # 获取请求中的提示

    messages = [
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": prompt}
    ]

    # 调用模型进行对话生成
    input_ids = tokenizer.apply_chat_template(messages,tokenize=False,add_generation_prompt=True)
    model_inputs = tokenizer([input_ids], return_tensors="pt").to('cuda')
    generated_ids = model.generate(model_inputs.input_ids,max_new_tokens=512)
    generated_ids = [
        output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
    ]
    response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
    now = datetime.datetime.now()  # 获取当前时间
    time = now.strftime("%Y-%m-%d %H:%M:%S")  # 格式化时间为字符串
    # 构建响应JSON
    answer = {
        "response": response,
        "status": 200,
        "time": time
    }
    # 构建日志信息
    log = "[" + time + "] " + '", prompt:"' + prompt + '", response:"' + repr(response) + '"'
    print(log)  # 打印日志
    torch_gc()  # 执行GPU内存清理
    return answer  # 返回响应

# 主函数入口
if __name__ == '__main__':
    # 加载预训练的分词器和模型
    model_name_or_path = '/root/autodl-tmp/qwen/Qwen1.5-7B-Chat'
    tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, use_fast=False)
    model = AutoModelForCausalLM.from_pretrained(model_name_or_path, device_map="auto", torch_dtype=torch.bfloat16)

    # 启动FastAPI应用
    uvicorn.run(app, host='0.0.0.0', port=6005, workers=1)  # 在指定端口和主机上启动应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

默认部署在 6005 端口,通过 POST 方法进行调用,可以使用 curl 调用。

$ curl -X POST "http://127.0.0.1:6005" \
      -H 'Content-Type: application/json' \
      -d '{"prompt": "解释一下量子计算"}'
1
2
3

返回格式如下:

{
    "response": "量子计算是一种利用量子力学原理来处理信息的计算方式,它与传统的基于二进制位(比特)的计算方式有所不同。在传统计算机中,信息以比特为基础,每个比特只能处于0或1的状态,而量子计算使用量子比特,也称为qubits,它们可以同时存在于0和1的叠加态,这就是所谓的“量子叠加”。\n\n量子比特的另一个重要特性是量子纠缠,当两个或多个量子比特处于纠缠状态时,它们之间存在着一种非局域性的相互依赖关系,即使它们相隔很远,对一个量子比特的操作会立即影响到其他纠缠的量子比特,这种现象被称为“量子纠缠态”。这使得量子计算机在某些特定任务上具有巨大的计算优势,例如在分解大整数、搜索数据库以及模拟量子系统等方面。\n\n然而,量子计算也面临着许多挑战,如量子位的稳定性问题、量子错误纠正和量子计算算法的设计等。尽管如此,近年来量子计算已经在实验室中取得了一些重要的进展,许多科技巨头和研究机构都在积极投入研发,未来有可能引领一场信息技术的革命。",
    "status": 200,
    "time": "2024-03-16 16:10:37"
}
1
2
3
4
5

也可以使用 Python 脚本进行调用。

# -*- coding: utf-8 -*-

import requests
import json

def get_completion(prompt):
    headers = {'Content-Type': 'application/json'}
    data = {"prompt": prompt}
    response = requests.post(url='http://127.0.0.1:6005', headers=headers, data=json.dumps(data))
    return response.json()['response']

if __name__ == '__main__':
    print(get_completion('解释一下量子计算'))
1
2
3
4
5
6
7
8
9
10
11
12
13

# 5.2.3 以Web的形式使用

web_demo.py

# -*- coding: utf-8 -*-

from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig
import torch
import streamlit as st

# 在侧边栏中创建一个标题
with st.sidebar:
    st.markdown("## Qwen1.5 LLM")
    # 创建一个滑块,用于选择最大长度,范围在0到1024之间,默认值为512
    max_length = st.slider("max_length", 0, 1024, 512, step=1)

# 创建一个标题
st.title("💬 Qwen1.5 Chatbot")

# 定义模型路径
mode_name_or_path = '/root/autodl-tmp/qwen/Qwen1.5-7B-Chat'

# 定义一个函数,用于获取模型和tokenizer
@st.cache_resource
def get_model():
    # 从预训练的模型中获取tokenizer
    tokenizer = AutoTokenizer.from_pretrained(mode_name_or_path, use_fast=False)
    # 从预训练的模型中获取模型,并设置模型参数
    model = AutoModelForCausalLM.from_pretrained(mode_name_or_path, torch_dtype=torch.bfloat16,  device_map="auto")
  
    return tokenizer, model

# 加载Qwen1.5-7B-Chat的model和tokenizer
tokenizer, model = get_model()

# 如果session_state中没有"messages",则创建一个包含默认消息的列表
if "messages" not in st.session_state:
    st.session_state["messages"] = [{"role": "assistant", "content": "有什么可以帮您的?"}]

# 遍历session_state中的所有消息,并显示在聊天界面上
for msg in st.session_state.messages:
    st.chat_message(msg["role"]).write(msg["content"])

# 如果用户在聊天输入框中输入了内容,则执行以下操作
if prompt := st.chat_input():
    # 将用户的输入添加到session_state中的messages列表中
    st.session_state.messages.append({"role": "user", "content": prompt})
    # 在聊天界面上显示用户的输入
    st.chat_message("user").write(prompt)
    
    # 构建输入     
    input_ids = tokenizer.apply_chat_template(st.session_state.messages,tokenize=False,add_generation_prompt=True)
    model_inputs = tokenizer([input_ids], return_tensors="pt").to('cuda')
    generated_ids = model.generate(model_inputs.input_ids, max_new_tokens=512)
    generated_ids = [
        output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
    ]
    response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
    # 将模型的输出添加到session_state中的messages列表中
    st.session_state.messages.append({"role": "assistant", "content": response})
    # 在聊天界面上显示模型的输出
    st.chat_message("assistant").write(response)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

使用如下命令启动Web服务:

$ streamlit run /root/autodl-tmp/chatBot.py --server.address 127.0.0.1 --server.port 6006
1

Qwen1.5的Web示例

# 6. 参考资料

[1] 不用A100,开源Alpaca-LoRA+RTX 4090就能搞定 from 机器学习算法与自然语言处理 (opens new window)

[2] 大模型ChatGLM-6B安装Mac M1本地部署 from Bilibili (opens new window)

[3] 最新版的MacBook Pro Apple M2 跑不了 from Github issues (opens new window)

[4] LLM 技术图谱 from Gitee (opens new window)

[5] Alpaca-cpp(羊驼-cpp): 可以本地运行的 Alpaca 大语言模型 from 51CTO (opens new window)

[6] huggingface基本使用教程 from 兼一书虫 (opens new window)

[7] LLMs学习从零开始-大模型加速 from 知乎 (opens new window)

[8] ChatGLM3-6B部署和微调 (Function Call、 Code Interpreter、 Agent) from Bilibili (opens new window)

[9] 大模型量化感知训练开山之作:LLM-QAT from 吃果冻不吐果冻皮 (opens new window)

[10] 大模型量化概述 from 吃果冻不吐果冻皮 (opens new window)

[11] NLP(十一):大语言模型的模型量化(INT8/INT4)技术 from 知乎 (opens new window)

[12] 模型量化(int8)系统知识导读 from CSDN (opens new window)

[13] ChatGLM的int8量化以及由此对量化的梳理总结 from CSDN (opens new window)

[14] 大规模语言模型:从理论到实践 from 复旦大学团队 (opens new window)

[15] 自然语言处理导论 from 复旦大学团队 (opens new window)

[16] 终于有人将大模型可视化了 from 人工智能大讲堂 (opens new window)

[17] Qwen1.5-7B-Chat FastApi 部署调用 from Github (opens new window)

[18] Qwen1.5-7B-Chat WebDemo 部署 from Github (opens new window)

[19] 基于大模型的检索增强的问答系统框架 from Github (opens new window)

[20] nvcc fatal : Unsupported gpu architecture 'compute_86' from CSDN (opens new window)

[21] ‘BaichuanTokenizer' object has no attribute 'sp_model' 问题 from CSDN (opens new window)

[22] Huggingface Transformers+Accelerate多卡推理实践(指定GPU和最大显存)from 知乎 (opens new window)

[23] Handling big models for inference from HuggingFace官方文档 (opens new window)

[24] 编码器与解码器LLM全解析:掌握NLP核心技术的关键 from CSDN (opens new window)

[25] 如何估计大模型所需要的显存大小 from AI魔法学院 (opens new window)

[26] 智源连甩近20项王炸研究进展!语言、多模态、具身、生物计算+大模型“操作系统” from 微信公众号 (opens new window)

[27] 大模型量化性能评价指标 from 吃果冻不吐果冻皮 (opens new window)

[28] Qwen2大模型效率评估 from Qwen官方文档 (opens new window)

[29] 现在LLM 的大小为什么都设计成6/7B、13B和130B几个档次? from 微信公众号 (opens new window)

[30] Llama 3.1 405B 中文基准评测出炉!推理总分80.44,略超GPT-4 Turbo,不敌GPT-4o from 微信公众号 (opens new window)

Last Updated: 7/6/2024, 4:48:31 PM