这篇文章由两个事件驱动:

  1. 前段时间,本届人工智能的赛季里最大的人工智能供应商、在社交和 VR 领域都被黒成狗、在 AI 领域却被称为活菩萨的 Meta 发布了 Llama 2。据说不但能和 OpenAI 的 GPT 系列强行五五开,还可以自己随便 fine-tuning。
  2. 大概一个月前 llama.cpp 做了 CLBlast 支持。

于是我的 AMD 的 Radeon 显卡不用太折腾也能玩了。下面就写一下在 Ubuntu 22.04 Jammy Jellyfish 下跑 llama.cpp + llama2。

下载模型

好心的 TheBloke 提供了转换好的 Llama2 模型供下载:

TheBloke/Llama-2-70B-GGML
TheBloke/Llama-2-70B-Chat-GGML
TheBloke/Llama-2-13B-GGML
TheBloke/Llama-2-13B-chat-GGML
TheBloke/Llama-2-7B-GGML
TheBloke/Llama-2-7B-Chat-GGML

根据你的内存,选择合适的版本下载。比如 70b 的版本大概需要 31GB~70GB 的内存。我下了llama-2-13b-chat.ggmlv3.q4_K_M.bin

其中 q4 表示 4bit 版本。下好的模型是一个 .bin 结尾的文件,存好,放到 ~/Downloads/llama-2-13b-chat.ggmlv3.q4_K_M.bin

编译 llama.cpp

Ubuntu 下载好相关工具和库:

sudo apt install git make cmake vim

先 clone 一下 Llama.cpp 的代码:

git clone https://github.com/ggerganov/llama.cpp

官网上说直接 make 就好,但是我这里有点问题,所以换成了 cmake:

mkdir build
cd build
cmake ..
cmake --build . --config Release

构建好的程序会放在 llama.cpp/build/bin,其中 main 是命令程序入口,server 是 Web 服务器入口。

把它复制出来换个名字:

cp ./bin/main/main ../llama-cpu
cd ..

测试一下

llama.cpp/examples 下面有几个测试脚本,复制一个改成我们自己的:

cp examples/chat-13B.sh examples/chat-llama2-13B.sh
vim examples/chat-llama2-13B.sh

examples/chat-llama2-13B.sh 里面的 MODEL 里模型的路径换成自己的路径,例如

MODEL="/home/lyric/Downloads/llama-2-13b-chat.ggmlv3.q4_K_M.bin"

再把里面的 ./main 换成自己的名字 ./llama-cpu

然后运行:

./examples/chat-llama2-13B.sh

根据你的机器配置的情况,要等一会儿,然后就可以开始聊天了,大概是下面图的效果,其中绿色的字是我输入的,白色的字是 Llama 2 返回的。

An image to describe post

启用 GPU 加速

去 AMD 下载驱动: https://repo.radeon.com/amdgpu-install/

TIP

注意一下,5.* 版本才是新版,2*.*.* 这种反而是旧版本。旧版本是不能用的。
我安装的 5.5 版本: https://repo.radeon.com/amdgpu-install/5.5/ubuntu/jammy/

装好以后,运行一下:

amdgpu-install --usecase=opencl,rocm

Ubuntu 下载好相关的库:

sudo apt install ocl-icd-dev ocl-icd-opencl-dev \
	opencl-headers libclblast-dev

重新去编译一下,加 -DLLAMA_CLBLAST=ON 参数

cd build
cmake .. -DLLAMA_CLBLAST=ON -DCLBlast_dir=/usr/local
cmake --build . --config Release

把它复制出来换个名字:

cp ./bin/main/main ../llama-cl
cd ..

然后改改启动脚本:

vim examples/chat-llama2-13B.sh

把里面的 ./llama-cpu 换成自己的名字 ./llama-cl

然后在倒数第二行加上 --n-gpu-layers 40,例如我的是:

./llama-cl $GEN_OPTIONS \
  --model "$MODEL" \
  --threads "$N_THREAD" \
  --n_predict "$N_PREDICTS" \
  --color --interactive \
  --file ${PROMPT_FILE} \
  --reverse-prompt "${USER_NAME}:" \
  --in-prefix ' ' \
  --n-gpu-layers 40
  "$@"
TIP

这里的 --n-gpu-layers 会使用显存来加速 token 生成,我的显卡设置的 40,你可以随便设置一个很大的数字,比如 100000,llama.cpp 会选择显卡最大能用的层数。

然后运行:

./examples/chat-llama2-13B.sh

理论上使用 GPU 加速以后,这次等待时间会短很多,并且应该能在输出里看到类似下面的字样:

ggml_opencl: selecting platform: 'AMD Accelerated Parallel Processing'
...
ama_model_load_internal: using OpenCL for GPU acceleration
llama_model_load_internal: mem required  =  710.19 MB (+ 1600.00 MB per state)
llama_model_load_internal: offloading 40 repeating layers to GPU
llama_model_load_internal: offloaded 40/41 layers to GPU
llama_model_load_internal: total VRAM used: 7285 MB
...

就说明有在用 GPU 加速了,在我的电脑上,生成速度大概能到 600+token 每秒,感觉非常快了。

跑个服务

llama.cpp 也提供了 server,可以参考官方文档

我的话,就直接运行:

./server -m ~/Download/llama-2-13b-chat.ggmlv3.q4_K_M.bin \
	-c 2048 -ngl 40 --port 10081

然后打开 http://localhost:10081 就能用 Web UI 了。

这个 Web server 是支持 API 请求的,比如说:

curl --request POST \
    --url http://localhost:10081/completion \
    --header "Content-Type: application/json" \
    --data \
		'{"prompt": "Build a website can be done in 10 steps:","n_predict": 128}'

这样就很方便自己拿模型搞事情。

故障排除

OpenCL 的权限问题

有一种情况是只有在 root 权限下才能访问 opencl 相关的函数。比如执行 clinfo 时提示找不到 opencl,但是 sudo clinfo 则正常显示。

那么可以执行一下(把 LOGIN_NAME 换成自己的用户名):

sudo usermod -a -G video LOGIN_NAME
sudo usermod -a -G render LOGIN_NAME

给自己当前用户权限就行。

关于 Llama2 傻傻的问题

OpenAI 的 ChatGPT 是经过了很多 prompt 工程和优化的,但是自己跑起来的 Llama2 没有做过这些事情。所以如果发现 Llama2 傻傻的话,请加大 Prompt 的投喂力度,否则 Llama2 可能很难让您满意。

举个例子,如果你想要 llama2 输出 JSON,需要在 prompt 里提供几个生成 JSON 的例子,类似下面这个意图识别的例子:

You read the following text and recognize user's intent.
Possible intents are:

1. "吃饭"
2. "睡觉"
3. "打豆豆"
9999. "unknown intent"

You must return the intent with the highest confidence.
You must return the result as JSON format. 
Here is the template: 
{ "id": id, "intent": "USER'S INTENT", "confidence": 0.9 }

**instructions: 我饿了**
{ "id": 1, "intent": "吃饭", "confidence": 0.9 }

**instructions: 困了,想睡觉**
{ "id": 2, "intent": "睡觉", "confidence": 0.9 }

**instructions: 豆豆在哪?我要揍它**
{ "id": 3, "intent": "打豆豆", "confidence": 0.7 }

**instructions: 现在几点**
{ "id": 9999, "intent": "unknown intent", "confidence": 0.9 }

配置到 Web UI 大概是这样:

An image to describe post

运行效果如下:

An image to describe post

还不错~