Self-directed Learning

在应用中使用小模型

2024/02/06

Untitled1.png

一直以来我都在尝试利用 Microsoft Promptflow 来实现大模型和小模型的协同工作,但在云端利用昂贵的GPU来运行小模型并不是我最终的想法,于是便开始尝试使用本地GPU,甚至是CPU来进行小模型的推理。很有意思的是有很多人和我的想法一致,于是我整理了以下一些资料。

WasmEdge + WASI-NN plug-in

Untitled2.png

WasmEdge 是一个轻量级、高性能、可扩展的WebAssembly运行时,适用于云原生、边缘和去中心化应用。它支持无服务器应用、嵌入式函数、微服务、用户定义函数、智能合约和物联网设备。WasmEdge目前是CNCF(云原生计算基金会)的沙盒项目。WasmEdge运行时为其包含的WebAssembly字节码程序提供了一个明确定义的执行沙箱。运行时为操作系统资源(例如文件系统、套接字、环境变量、进程)和内存空间提供隔离和保护。

WASI-NN 插件是用于机器学习的WebAssembly系统接口(WASI)API,它允许WebAssembly程序访问主机提供的机器学习功能。

GGML&llama.cpp 是一套专注于机器学习的C/C++库。两者都是由Georgi Gerganov创建,这就是“GG”这个缩写的含义。llama.cpp库用于高效推理Llama模型。它可以加载GGML模型并在CPU上运行。最初,这是与GPTQ模型的主要区别,后者是在GPU上加载和运行的。然而,现在你可以使用llama.cpp将一些层次的LLM转移到GPU上。

GGUF 也是由Georgi Gerganov提供的,这种新格式被设计成可扩展的,以便新功能不会破坏与现有模型的兼容性。它还将所有元数据集中在一个文件中,例如特殊标记、RoPE缩放参数等。简而言之,它解决了一些历史上的痛点,并且应该具备未来的兼容性。也可以称之为“GGML模型”,这些模型要么使用GGUF格式,要么使用之前的格式。

Rust

实际上如今大多数LLM应用程序都是用Python编写的,但Python太慢、太臃肿。事实上,LLVM、Clang和Swift的发明者Chris Lattner曾经证明Python比编译语言慢35,000倍,这就是他为什么发明了Mojo语言作为Python的替代品。这迫使开发者将越来越多的应用逻辑推向本地编译的代码,比如C、C++和Rust。例如:像llama.cpp、whisper.cpp和llama2.c这样非常受欢迎的项目,都是不依赖Python的。换句话说,Python不仅非常慢,而且难以用于开发LLM应用程序。

Python的挑战为高性能编译语言创造了机会。随着C和C++在开发者社区中失去对Rust的竞争力,Rust便脱颖而出。然而,直接将Rust编译为本机机器代码还存在其他问题:

  1. 安全性:本地二进制文件可能会导致整个系统崩溃
  2. 可移植性:本地二进制文件特定于底层操作系统和硬件。
  3. 最重要的性能:由于安全性和可移植性要求,通常需要在Linux容器中运行本机二进制文件。这些容器会给程序增加启动和运行时的开销,从而大大降低其速度。

因此,Wasm已成为Rust应用程序的主要安全运行时,以解决这些问题。借助面向云优化的Wasm运行时WasmEdge,开发人员现在可以选择在LLM应用程序堆栈的每一层中使用高性能的Rust,作为Python的高性能替代方案。

针对以上问题,使用Rust + Wasm不仅代替Python以提升性能,还从根本上减少占用空间,并提高安全性。

Untitled3.png

  1. Agent layer:用于接收互联网事件、连接数据库和调用其他网络服务的网络密集型任务。Rust和WasmEdge为高密度和高性能的代理应用程序提供异步和非阻塞的I/O。
  2. Inference layer:将数据(例如单词和句子)预处理为数字,并将数字后处理为句子或结构化的JSON数据的CPU密集型任务。这些功能可以使用Rust编写,以实现最佳性能,并在WasmEdge中运行以实现安全性和可移植性。
  3. Tensor layer:将需要大量GPU计算的任务从Wasm传递给本地张量库,如llama.cpp、PyTorch和Tensorflow,通过WasmEdge的WASI-NN插件。

由此可见,Rust和Wasm可能是今天对Python来说高性能且对开发者友好的替代品

安装 WasmEdge

安装WasmEdge有两种方式:

1、直接安装编译好的二进制文件

通过命令行可以直接安装WasmEdge以及WASI-NN插件。

1
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- --plugins wasi_nn-ggml

这样的好处就是非常简单,安装完成即可使用。当然也有不好的地方,因为插件的后端是依赖GGML&llama.cpp的C/C++库,如果C/C++库更新并支持了新模型,但插件并没有及时更新的话,就会导致无法使用C/C++库所兼容的新模型,需要等待开源项目更新插件。

2、手动构建WasmEdge

通过源代码手动构建WasmEdge可以解决上一个问题。具体构建过程可以在WasmEdge的官方文档找到。但如果使用一台全新的虚拟机来构建编译环境,还是非常复杂的一件事。

首先要创建一台带有GPU的虚拟机,因为构建过程需要用到 CUDA,但并不需要高性能的GPU型号,我直接选择了T4。另外,如果是跑一些像是 llama2-7b,或者是microsoft Phi2这种的小模型,32G内存就可以满足,如果内存太小会影响到 ctx-size 所需的内存空间,导致推理失败。最后还是建议将虚拟机创建在海外,这样便于获取资源。我在Azure的日本区域创建了一台NC8as_T4_v3型号的8核56G内存,带有一块NVIDIA T4 GPU的虚拟机。

虚拟机创建好之后的第一件事还是安装GPU驱动

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
# (如果查找命令失败,需要运行这行命令)
sudo apt-get install alsa-base alsa-utils

# 可以查到当前可安装的Nvidia GPU的驱动
sudo apt install ubuntu-drivers-common

# 查找可按装的Nvidia GPU驱动
ubuntu-drivers devices

== /sys/devices/pci0000:00/0000:00:0d.0 ==
modalias : pci:v000010DEd00001EB8sv000010DEsd000012A2bc03sc02i00
vendor : NVIDIA Corporation
model : TU104GL [Tesla T4]
driver : nvidia-driver-470-server - distro non-free
driver : nvidia-driver-418-server - distro non-free
driver : nvidia-driver-535 - distro non-free recommended
driver : nvidia-driver-535-server - distro non-free
driver : nvidia-driver-470 - distro non-free
driver : nvidia-driver-525 - distro non-free
driver : nvidia-driver-525-server - distro non-free
driver : nvidia-driver-450-server - distro non-free
driver : xserver-xorg-video-nouveau - distro free builtin

# 安装驱动
sudo apt install nvidia-driver-535

# 安装驱动后建议重启系统
sudo reboot

# 重启后检查下驱动是否安装成功,这部分显示的CUDA Version是此显卡最大支持的CUDA版本
nvidia-smi

Mon Feb 5 10:09:15 2024
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.154.05 Driver Version: 535.154.05 CUDA Version: 12.2 |
|-----------------------------------------+----------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+======================+======================|
| 0 Tesla T4 Off | 00000000:00:0D.0 Off | 0 |
| N/A 44C P8 11W / 70W | 6MiB / 15360MiB | 0% Default |
| | | N/A |
+-----------------------------------------+----------------------+----------------------+

+---------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=======================================================================================|
| 0 N/A N/A 1322 G /usr/lib/xorg/Xorg 4MiB |
+---------------------------------------------------------------------------------------+

驱动装好之后,还需要安装CUDA

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
92
93
94
95
96
97
98
99
100
101
102
103
104
# 下载cuda安装文件,比较大需要耐心等待
wget https://developer.download.nvidia.com/compute/cuda/12.2.2/local_installers/cuda_12.2.2_535.104.05_linux.run

# 安装cuda
sudo sh cuda_12.2.2_535.104.05_linux.run

# 选择Continue
┌──────────────────────────────────────────────────────────────────────────────┐
│ Existing package manager installation of the driver found. It is strongly │
│ recommended that you remove this before continuing. │
│ Abort │
│ Continue │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ Up/Down: Move | 'Enter': Select │
└──────────────────────────────────────────────────────────────────────────────┘

# 输入 accept
┌──────────────────────────────────────────────────────────────────────────────┐
│ End User License Agreement │
│ -------------------------- │
│ │
│ NVIDIA Software License Agreement and CUDA Supplement to │
│ Software License Agreement. Last updated: October 8, 2021 │
│ │
│ The CUDA Toolkit End User License Agreement applies to the │
│ NVIDIA CUDA Toolkit, the NVIDIA CUDA Samples, the NVIDIA │
│ Display Driver, NVIDIA Nsight tools (Visual Studio Edition), │
│ and the associated documentation on CUDA APIs, programming │
│ model and development tools. If you do not agree with the │
│ terms and conditions of the license agreement, then do not │
│ download or use the software. │
│ │
│ Last updated: October 8, 2021. │
│ │
│ │
│ Preface │
│ ------- │
│ │
│──────────────────────────────────────────────────────────────────────────────│
│ Do you accept the above EULA? (accept/decline/quit): │
│ accept │
└──────────────────────────────────────────────────────────────────────────────┘

# 因为已经安装GPU驱动了,所以就不用重复安装了,取消 Driver 后选择 Install
┌──────────────────────────────────────────────────────────────────────────────┐
│ CUDA Installer │
│ - [ ] Driver │
│ [ ] 535.104.05 │
│ + [X] CUDA Toolkit 12.2 │
│ [X] CUDA Demo Suite 12.2 │
│ [X] CUDA Documentation 12.2 │
│ - [ ] Kernel Objects │
│ [ ] nvidia-fs │
│ Options │
│ Install │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ Up/Down: Move | Left/Right: Expand | 'Enter': Select | 'A': Advanced options │
└──────────────────────────────────────────────────────────────────────────────┘

# 安装完成之后会看到
===========
= Summary =
===========

Driver: Not Selected
Toolkit: Installed in /usr/local/cuda-12.2/

Please make sure that
- PATH includes /usr/local/cuda-12.2/bin
- LD_LIBRARY_PATH includes /usr/local/cuda-12.2/lib64, or, add /usr/local/cuda-12.2/lib64 to /etc/ld.so.conf and run ldconfig as root

To uninstall the CUDA Toolkit, run cuda-uninstaller in /usr/local/cuda-12.2/bin
***WARNING: Incomplete installation! This installation did not install the CUDA Driver. A driver of version at least 535.00 is required for CUDA 12.2 functionality to work.
To install the driver using this installer, run the following command, replacing <CudaInstaller> with the name of this run file:
sudo <CudaInstaller>.run --silent --driver

修改环境变量验证CUDA是否安装成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 修改环境变量
sudo vim ~/.bashrc

# 加入GPU驱动所在路径
export PATH=/usr/local/cuda-12.2/bin${PATH:+:${PATH}}
export LD_LIBRARY_PATH=/usr/local/cuda-12.2/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}

# 重新加载环境变量
source ~/.bashrc

# 检查安装是否正确
nvcc --version

nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2023 NVIDIA Corporation
Built on Tue_Aug_15_22:02:13_PDT_2023
Cuda compilation tools, release 12.2, V12.2.140
Build cuda_12.2.r12.2/compiler.33191640_0

到这里就可以直接选择 方式1 来安装WasmEdge,如果选择 方式2 还需要再做一些准备工作。

首先要更新cmake,WasmEdge对cmake有版本要求,系统自带的cmake通常版本都很低,所以要手动更新到高版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
sudo apt-get install libssl-dev

# 如果原系统已经安装了cmake且版本很低,需要卸载之后重新安装,但如果已经符合版本要求就不用卸载了
sudo apt remove cmake

wget https://github.com/Kitware/CMake/releases/download/v3.28.1/cmake-3.28.1.tar.gz

tar xf ./cmake-3.28.1.tar.gz

cd cmake-3.28.1/

./bootstrap

sudo make

sudo make install

# 安装之后因为环境变量所在的路径没有cmake命令,所以需要手动创建一个软连接
sudo ln -s /usr/local/bin/cmake /usr/bin

# 最后检查一下是否安装成功
sudo cmake --version

除了cmake之外,WasmEdge还依赖 LLVM 和 LLD 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sudo git clone https://github.com/llvm/llvm-project llvm-project

mkdir build && cd build

# 构建过程极其漫长,需要耐心等待,我注释的第一行并没有生成LLDConfig.cmake文件,所以我使用了第二行命令
#sudo cmake -DLLVM_ENABLE_BINDINGS=Off -DLLVM_BUILD_DOCS=Off -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" ../llvm-project/llvm
# 构建的过程会有一些警告,但不会导致编译失败,可以不用理会
sudo cmake -DLLVM_ENABLE_BINDINGS=Off -DLLVM_BUILD_DOCS=Off -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=lld -DCMAKE_INSTALL_PREFIX=/usr/local -G "Unix Makefiles" ../llvm-project/llvm

sudo make install

# 安装完成之后,应该可以找到以下两个文件
# /usr/local/lib/cmake/lld/LLDConfig.cmake
# /usr/local/lib/cmake/llvm/LLVMConfig.cmake

接下来就可以开始构建安装WasmEdge了

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
sudo apt-get install -y ninja-build

git clone https://github.com/WasmEdge/WasmEdge.git

cd WasmEdge/

# 由于cuda相关的文件,它会产生一些警告。将警告作为错误禁用,以避免出现故障
export CXXFLAGS="-Wno-error"
# 设置CUDAARCHS的值,根据官方文档给出的值,以便获得最大兼容性
export CUDAARCHS="60;61;70"

sudo cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CUDA_ARCHITECTURES="60;61;70" \
-DCMAKE_CUDA_COMPILER=/usr/local/cuda/bin/nvcc \
-DWASMEDGE_PLUGIN_WASI_NN_BACKEND="GGML" \
-DWASMEDGE_PLUGIN_WASI_NN_GGML_LLAMA_BLAS=OFF \
-DWASMEDGE_PLUGIN_WASI_NN_GGML_LLAMA_CUBLAS=ON \
.

sudo cmake --build build

sudo cmake --install build

-- Install configuration: "Release"
-- Installing: /usr/local/lib/libwasmedge.so.0.1.0
-- Installing: /usr/local/lib/libwasmedge.so.0
-- Set non-toolchain portion of runtime path of "/usr/local/lib/libwasmedge.so.0.1.0" to "$ORIGIN"
-- Installing: /usr/local/lib/libwasmedge.so
-- Installing: /usr/local/include/wasmedge/wasmedge.h
-- Installing: /usr/local/include/wasmedge/version.h
-- Installing: /usr/local/include/wasmedge/enum.inc
-- Installing: /usr/local/include/wasmedge/enum_configure.h
-- Installing: /usr/local/include/wasmedge/enum_errcode.h
-- Installing: /usr/local/include/wasmedge/enum_types.h
-- Installing: /usr/local/include/wasmedge/int128.h
-- Installing: /usr/local/lib/cmake/Llama/LlamaConfig.cmake
-- Installing: /usr/local/lib/cmake/Llama/LlamaConfigVersion.cmake
-- Installing: /usr/local/include/ggml.h
-- Installing: /usr/local/include/ggml-alloc.h
-- Installing: /usr/local/include/ggml-backend.h
-- Installing: /usr/local/include/ggml-cuda.h
-- Installing: /usr/local/lib/libllama.a
-- Installing: /usr/local/include/llama.h
-- Installing: /usr/local/bin/convert.py
-- Installing: /usr/local/bin/convert-lora-to-ggml.py
-- Installing: /usr/local/include/simdjson.h
-- Installing: /usr/local/lib/libsimdjson.a
-- Installing: /usr/local/lib/cmake/simdjson/simdjson-config.cmake
-- Installing: /usr/local/lib/cmake/simdjson/simdjson-config-version.cmake
-- Installing: /usr/local/lib/cmake/simdjson/simdjsonTargets.cmake
-- Installing: /usr/local/lib/cmake/simdjson/simdjsonTargets-release.cmake
-- Installing: /usr/local/lib/pkgconfig/simdjson.pc
-- Installing: /usr/local/lib/wasmedge/libwasmedgePluginWasiNN.so
-- Set non-toolchain portion of runtime path of "/usr/local/lib/wasmedge/libwasmedgePluginWasiNN.so" to "$ORIGIN"
-- Installing: /usr/local/bin/wasmedgec
-- Set non-toolchain portion of runtime path of "/usr/local/bin/wasmedgec" to "$ORIGIN/../lib"
-- Installing: /usr/local/bin/wasmedge
-- Set non-toolchain portion of runtime path of "/usr/local/bin/wasmedge" to "$ORIGIN/../lib"

# 验证wasmedge是否安装正确
wasmedge --version

wasmedge version 0.14.0-alpha.1-49-g92e93cdb
/usr/local/lib/wasmedge/libwasmedgePluginWasiNN.so (plugin "wasi_nn") version 0.10.1.0

至此WasmEdge的安装就结束了

使用WasmEdge

项目组提供了一个开源项目,此项目基于Rust语言开发,目的是让开发者熟悉如何使用 WasmEdge & WASI-NN 开发应用。

开源项目中提供了simple、api-server、chat三种应用方式。simple暂且不说,它只是简单说明了如何调用 wasi-nn 接口。但 api-server 和 chat 在设计方面,因两者针对的使用场景不同,内部的整体设计也截然不同,但是底层的推理接口均使用的 wasi-nn 接口。只是在设计上,api-server 根据应用场景的需要,对wasi_nn::Graph 和wasi_nn::GraphExecutionContext 进行了封装,封装后的对象实例是以全局变量的形态存在,主要目的是在server启动时,就可以加载模型,从而避免用户发起对话请求时,冷启动造成的不必要等待。

为了简单测试,我直接下载项目组已经编译好的 api-server。

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
# 下载llama2-7b的模型
curl -LO https://huggingface.co/second-state/Llama-2-7B-Chat-GGUF/resolve/main/llama-2-7b-chat.Q5_K_M.gguf

# 为了更简单的测试,我们直接使用开源项目组提供的api-server
wget https://github.com/second-state/LlamaEdge/releases/download/0.2.14/llama-api-server.wasm

# 使用WasmEdge启动服务加载llama2-7b模型,会看到服务启动,并在8080端口监听
wasmedge --dir .:. --nn-preload default:GGML:AUTO:llama-2-7b-chat.Q5_K_M.gguf llama-api-server.wasm -p llama-2-chat

[INFO] Socket address: 0.0.0.0:8080
[INFO] Model name: default
[INFO] Model alias: default
[INFO] Prompt context size: 512
[INFO] Number of tokens to predict: 1024
[INFO] Number of layers to run on the GPU: 100
[INFO] Batch size for prompt processing: 512
[INFO] Temperature for sampling: 1
[INFO] Top-p sampling (1.0 = disabled): 1
[INFO] Penalize repeat sequence of tokens: 1.1
[INFO] Presence penalty (0.0 = disabled): 0
[INFO] Frequency penalty (0.0 = disabled): 0
[INFO] Prompt template: Llama2Chat
[INFO] Log prompts: false
[INFO] Log statistics: false
[INFO] Log all information: false
[INFO] Starting server ...
ggml_init_cublas: GGML_CUDA_FORCE_MMQ: no
ggml_init_cublas: CUDA_USE_TENSOR_CORES: yes
ggml_init_cublas: found 1 CUDA devices:
Device 0: Tesla T4, compute capability 7.5, VMM: yes
[INFO] Plugin version: b2037 (commit 1cfb5372)
[INFO] Listening on http://0.0.0.0:8080

启动之后我们用postman测一下,总体来说效果不错,模型的加载速度很快,推理速度也很快。

Untitled4.png

WasmEdge的官方文档中还提供了一部分在 Nvidia Jetson 中构建WasmEdge的说明,其实这部分才是我真正需要的内容,而且已经在我的 Jetson 环境中测试成功了。现在我可以重新修改应用架构,并将SLM(Small Language Model)加入到应用环境中。但如果要想让小模型发挥真正的作用,还需要做更多的工作。例如:选择更优秀的基础模型、重新调整prompt并针对prompt指令微调模型、针对RAG对需要检索的数据进行分类存储等等。

Untitled5.png

CATALOG
  1. 1. WasmEdge + WASI-NN plug-in
  2. 2. Rust
  3. 3. 安装 WasmEdge
  4. 4. 使用WasmEdge