龙芯 3A4000 AI 应用篇 (一):部署本地大语言模型

羊驼

六月份的时候,我用了一块龙芯 3A4000 主板,组装了一台性能尚可的主机,并且移植了一些经典主机的模拟器。虽说折腾的过程很欢乐,不过只用这台机器打游戏有些浪费,做成 NAS 又不能满足我的需求。所以我准备在这台龙芯主机上,尝试做一些 AI 应用,这一篇文章就算是开个头。

首先我能想到的就是部署一个本地的大语言模型了。大模型一般通过 llama.cpp 部署在本地机器上,好处就是纯 C++ 实现,不需要依赖 pytorch 等复杂的框架环境。在 llama.cpp 的基础上有很多应用,比如支持语音输入的 whisper.cpp 等。最近比较流行的是 ollama 框架,ollama 也是在 llama.cpp 的基础上做的,使用起来更加方便。ollama 可以作为后端服务,给多种前端应用提供推理服务,也可以方便的下载、管理模型。我这次的本地大语言模型,就是基于 ollama 部署的,接下来就详细介绍部署过程。

一、安装 Ollama 推理框架

首先我们需要部署一个可以运行本地大语言模型的推理框架,这里我们选择使用 ollama,使用它可以让我们轻松地在本地部署和管理多种大型语言模型(LLMs),比如最新地 Llama 3.1、通义千问,也可以导入和定制自定义地语言模型。ollama 使用 golang 语言编写,底层依赖 llama.cpp 纯 C++ 实现,无需配置 pytorch 环境,可以使用 CPU 推理,也支持使用 NVIDIA CUDAAMD ROCm 显卡加速。

1. 基础编译环境

由于 ollama 目前官方尚没有支持龙芯的 mips64el 或者 loongarch64 架构的安装包,因此需要手动编译源码才能运行。编译需要以下基础环境:

  • cmake 版本 3.24 以上
  • gcc 版本 11.4.0 以上
  • go 版本 1.22 以上

其中 golang 对龙芯的支持目前已经比较完善,可以到官网寻找最新的安装包。比如目前最新的 go 1.22.5 版本的 mips64el 架构对应的安装包为:go1.22.5.linux-mips64le.tar.gzloongarch64 架构的安装包为:https://go.dev/dl/go1.22.5.linux-loong64.tar.gz

# 安装 cmake, gcc
$ apt install cmake gcc

# 查看 gcc 版本,要求大于 11.4.0
$ gcc --version
gcc (Debian 12.2.0-14) 12.2.0
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

# 查看 cmake 版本,要求大于 3.24 
$ cmake --version
cmake version 3.30.0

CMake suite maintained and supported by Kitware (kitware.com/cmake).

# 安装 golang
# golang 目前有编译好的 mips64le 版本,可以直接去官网下载安装
# ollama 要求版本 1.22 以上

# 下载解压 golang 安装包
$ wget https://go.dev/dl/go1.22.5.linux-mips64le.tar.gz
$ tar zxvf go1.22.5.linux-mips64le.tar.gz

# 添加 golang 环境变量
export PATH=/home1/opt/go-1.22.5/bin:${PATH}

# golang 包管理网址 proxy.golang.org 国内无法访问
# 切换国内代理地址
$ go env -w GOPROXY=https://goproxy.cn

这样就完成了准备工作,接下来正式开始编译 ollama 框架。

2. 编译 ollama

首先需要下载 ollama 的源代码镜像,使用递归下载子模块。

# 下载 ollama
$ git clone --recurse-submodules https://github.com/ollama/ollama.git
# 如果下载子模块失败,可以更新下载
$ git submodule update

下载完成后,就可以开始编译了。ollama 官网提供的教程非常的坑爹简单,只需要两段命令。

$ cd ollama

# 编译
$ go generate ./...
$ go build .

然而,直接编译在以下代码处报错,应该是不规范的省略写法,导致函数体判断为空。

# 直接编译会在以下代码处报错,主要是由于函数体没写的缘故
/root/go/pkg/mod/github.com/chewxy/math32@v1.10.1/sqrt.go:3:6: missing function body
/root/go/pkg/mod/github.com/chewxy/math32@v1.10.1/log.go:76:6: missing function body
/root/go/pkg/mod/github.com/chewxy/math32@v1.10.1/exp.go:3:6: missing function body
/root/go/pkg/mod/github.com/chewxy/math32@v1.10.1/exp.go:57:6: missing function body
/root/go/pkg/mod/github.com/chewxy/math32@v1.10.1/remainder.go:33:6: missing function body

查看了源代码后,发现是函数体的重命名问题。比如 sqrt.go 中,出错的第 3 行的 Sqrt 空函数体,应该是对第 6 行具体的 sqrt 函数的引用。我们依次修改这 4 个文件的 5 处错误,把完整的函数体引用补全即可。

# 可以到文件的对应行,增加函数体的返回写法
# 示例修改 sqrt.go
$ vim /root/go/pkg/mod/github.com/chewxy/math32@v1.10.1/sqrt.go
1 package math32
2
3 func Sqrt(x float32) float32
4 
5 // TODO: add assembly for !build noasm
6 func sqrt(x float32) float32 {
...

# 将第三行改为
3 func Sqrt(x float32) float32 {
4 	return sqrt(x)
5 }

# 其他位置修改方法类似

继续尝试编译,又会报以下错误。这次则是由于库文件不全导致的。

# 如果没有设置库文件,会报找不到 llama 或 ggml 库文件的错误
# github.com/ollama/ollama
/home1/opt/go-1.22.5/pkg/tool/linux_mips64le/link: running gcc failed: exit status 1
/usr/bin/ld: 找不到 -lllama: 没有那个文件或目录
/usr/bin/ld: 找不到 -lggml: 没有那个文件或目录
collect2: error: ld returned 1 exit status

观察缺失的这两个库文件,对应 libllama.so 和 libggml.so 两个文件,是 llama.cpp 中提供的。因此需要首先编译一下 llama.cpp,然后再添加到链接库目录中。

# 另外编译之前,需要首先编译依赖 llama.cpp
$ cd ./llm/llama.cpp
$ cmake -B build
$ cmake --build build --config Release -j4

# 查询编译好的库文件位置
$ find . -type f -name "*.so"
./llm/llama.cpp/build/src/libllama.so
./llm/llama.cpp/build/ggml/src/libggml.so
./llm/llama.cpp/build/examples/llava/libllava_shared.so

# 复制库文件到系统目录下
cp ./llm/llama.cpp/build/src/libllama.so /usr/local/lib
cp ./llm/llama.cpp/build/ggml/src/libggml.so /usr/local/lib

# 使用 LD_LIBRARY_PATH 环境变量设置库路径
$ export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH

踩完这些坑之后,再次尝试编译 ollama,顺利通过。

# 生成编译文件
$ cd llm/generate
$ bash gen_linux.sh

# 编译
$ go generate ./...
$ go build .

此时就可以在命令行端使用 ollama 进行简单的 LLM 推理会话了。由于使用 CPU 推理速度有限,结合内存大小(16GB),最好选择参数量为 8B 以下的模型加载。以下是一些常用命令,常见模型的推理速度和效果:

# 开启 ollama 服务
$ ollama serve

# 拉取 llama3.1 模型
$ ollama pull llama3.1

# 运行通义千问2 0.5b 参数模型(会自动下载镜像)
$ ollama run qwen2:0.5b

# 查看已下载模型列表
$ ollama list

# 查看模型加载情况
$ ollama ps

80 亿参数规模的 llama 3.1 模型:

模型 llama3.1:8b 的推理速度很慢,约为 0.279 tokens/s。

llama3.1_8b.gif

15 亿参数规模的通义千问 2 模型:

模型 qwen2:1.5b 的推理速度稍微快点,约为 2.626 tokens/s

qwen2_1.5b.gif

5 亿参数规模的通义千问 2 模型:

模型 qwen2:0.5b 的推理速度约为 6 tokens/s

qwen2_0.5b.gif

3. 部署 systemctl 服务

直接使用 ollama 服务需要每次使用 ollama serve 命令服务。为了方便起见,我们为 ollama 制作开机自启动服务,同时允许网络中的其他主机访问。编写 ollama.service 服务文件

# 编写`ollama.service`服务文件
vim ollama.service

[Unit]
Description=Ollama Service
After=network.target

[Service]
ExecStart=/home/opt/ollama/ollama serve
WorkingDirectory=/home/opt/ollama
Environment=OLLAMA_MODELS=/home/opt/llm_models

Environment=HOME=/root:$HOME
Environment=PATH=/usr/bin:/usr/local/bin:$PATH
Environment=LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
Environment=OLLAMA_HOST=0.0.0.0:11434
Environment=OLLAMA_ORIGINS=*


[Install]
WantedBy=multi-user.target

为了方便查看龙芯主机运行 ollama 服务时的工作状态,我们可以利用 tmux 实现分屏监控 htop, dstat, fastfetch 等各项系统指标。以下是分屏监控脚本:

#!/bin/bash
# ollama/monitor.sh

# 更新包列表并安装所需的监控命令
# sudo apt-get update
# sudo apt-get install -y tmux htop dstat

# 检查是否存在名为 monitor 的 tmux 会话,如果存在则将其删除
if tmux ls | grep -q "^monitor:"; then
  tmux kill-session -t monitor
fi

# 创建新的 tmux 会话
tmux new-session -d -s monitor

# 水平分割第1个窗格
tmux split-window -v -t 0
# 垂直分割第1个窗格
tmux split-window -h -t 0
# 垂直分割第3个窗格
tmux split-window -h -t 2

# 在第一个窗格中运行 htop 查看 CPU, 进程, 内存
tmux send-keys -t 0 'htop' C-m

# 在第二个窗格中运行 dstat 查看实时网络
tmux send-keys -t 1 'dstat -tdn --tcp --udp --color' C-m

# 在第四个窗格中运行 fastfetch 查看内核信息
tmux send-keys -t 3 'sleep 1 && fastfetch' C-m

# 第七秒后执行自定义命令: 
tmux send-keys -t 3 'sleep 7 && watch -n 1 ollama ps' C-m

# 选择第三个窗格待命
tmux select-pane -t 2
# tmux send-keys -t 2 'clear' C-m

# 设置窗格布局为 tiled
# tmux select-layout -t monitor tiled

echo "Tmux session 'monitor' created with four panes. Use 'tmux attach -t monitor' to attach. Use ctrl+b d to detach. "

tmux attach -t monitor

实际执行的效果如下:

image-20240807160207566.png

二、安装对话前端框架

部署完 ollama 之后,我们就可以在命令行端加载大语言模型了。但是命令行使用还是很不方便,最好能部署到 Web 端,可以通过网页访问。Web 端的框架选择很多,实现方式也多种多样。有的是基于浏览器插件实现,有的则是独立前端服务,大部分都是通过 Docker 部署。

1. 使用 Chrome 插件 Page Assist

基于浏览器插件的 Page Assist 使用非常方便,只需要在 Chrome 扩展商店页面点击安装即可(火狐商店也有对应的插件,安装没有问题,但是奇怪的是无法在聊天框输入中文)。插件页面如下:

image-20240807162105228.png

使用界面非常干净整洁,功能也很强大,支持图片输入(但是大部分 llama 模型不支持解析图片)、语音驶入和网络搜索功能。

image-20240807163506604.png

2. 使用 Lobe-Chat 对话框架

如果追求更丰富的前端功能,可以使用 Lobe Chat 前端框架。Lobe Chat 既支持访问网络主机的 ollama 服务,也可以通过 API 访问各种大语言模型服务,比如 OpenAI ChatGPT。我这里先配置好龙芯主机的服务端口,用另一台主机上部署好的 Lobe Chat 访问龙芯主机的 ollama 服务,效果如下:

外部主机访问 ollama.gif

效果还不错,可以看出中间存在的卡顿,大概率是网络传输造成的。

好的,那么这次的本地大语言模型部署就初步完成了。整体过程还算是顺利,中间的过程我还尝试编译 AMD ROCm,试图利用显卡的 GPU 进行推理加速。不过龙芯 MIPS 架构的工具链缺失的太多,编译过程会非常麻烦。而且之前买的亮机卡 RX460 显存也只有 4GB,即使成功用上了,折腾半天换来的提速实在有限,所以也就作罢了。

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...