拿到这个任务时,我刚结束在海光CPU+DCU那套环境里的加班调试。领导只丢给我一句话:PaddleSpeech必须跑起来。这里的“环境”不是常见的NVIDIA GPU,而是海光7000系列CPU、海光DCU加速卡,系统是银河麒麟V10,容器选了Docker。这套组合在网上的参考案例很少,一开始我也心里打鼓,但真把每一步踩完之后发现,核心问题其实就那么几个:设备透传、驱动库挂载、飞桨DCU版的适配。这篇文章就把我这次部署的完整过程和踩坑记录整理出来,给同样要在国产化平台上跑语音任务的朋友当一份参考。
1. 部署前必须理清的三件事
部署之前,我花了不少时间做方案选型。很多人一上来就想在麒麟OS裸机上直接装PaddleSpeech,但考虑到后面要迁移、要复现、要脱离物理机环境,Docker几乎是唯一靠谱的选择。海光DCU在驱动层对外暴露为类似GPU的计算设备,但它的运行库、编译工具链跟NVIDIA的那一套完全不一样,这就决定了我们不能直接用社区里的CUDA镜像,也不能指望官方Docker镜像开箱即跑。
1.1 为什么偏偏要在Docker里跑PaddleSpeech
这个问题我反问过自己很多遍。裸机安装最大的痛点是污染系统:PaddleSpeech依赖的Python包、音频库、模型文件非常多,而且版本相互之间还有约束。麒麟OS自带的Python环境、yum依赖又未必跟得上第三方包的要求,一旦装到一半发现系统库被改动,恢复起来相当痛苦。
Docker的好处在于把整个运行环境隔离成一层“薄壳”,宿主机只提供Linux内核、DCU设备和部分驱动库,容器内管好Python环境和机器学习依赖。这样即便容器被搞坏了,删掉重建也就是几分钟的事,宿主机始终干净。更重要的是,容器配置文件可以版本化管理,后续换机器、加节点时直接把启动命令和Dockerfile拿出来就能复制一套环境。
但代价也很直接:容器不是天生就能认识DCU的。Docker本身默认只做资源隔离,不传递宿主机设备,所以我们必须手动把DCU设备节点挂载进容器,同时还必须把海光DTK(DCU Toolkit)的运行时库透传进去,否则容器里的飞桨连设备都枚举不到。
1.2 硬件和系统环境的准备清单
我的测试环境大概是这样:海光CPU,具体型号就不说了,总之x86_64架构,12核心起步,内存32GB,系统盘之外准备了一块数据盘专门给Docker的数据目录用。DCU卡至少有1张,我这次拿了2张来测试多卡透传。
操作系统是银河麒麟V10 SP1,内核版本5.10,兼容CentOS 7/8的软件包生态。这意味着它的yum源跟CentOS体系高度相似,装Docker的时候可以直接参考CentOS的方式做,但小坑也不少,后面会细说。
关键的软件栈就三块:Docker引擎、海光DCU驱动和DTK运行时、PaddleSpeech及对应版本的飞桨DCU版。DCU驱动是提前装好的,我没用容器去装驱动,因为在内核态的东西怎么也隔离不了。验证驱动是否正常的命令是:
ls /dev/dcu* dmesg | grep -i dcu如果输出里能看到dcu0、dcu1之类的设备节点,说明驱动已经挂上了。没有节点的话,先在宿主机层面调通,再继续搞容器,不然容器里一切都白搭。
1.3 DCU设备在容器中“看不到”的根因
这是整个部署中最容易卡住的地方。很多人从NVIDIA环境切过来,习惯性地以为拉起容器后就能用nvidia-smi,但海光DCU没有这套东西,容器也不会自动感知设备。
DCU设备在Linux下其实就是一个字符设备文件,例如/dev/dcu0、/dev/dcuctl等。Docker通过--device参数可以把这些节点一一映射进容器。但问题是,就算设备节点进了容器,飞桨或者底层运行时还需要调用/opt/rocm下的动态库、头文件之类的东西。如果容器里没有这些库,设备节点相当于一个没有驱动响应的空壳,调用时立刻就会报错。
所以正确思路是:先把DCU设备节点映射进去,再把宿主机上DTK安装目录(通常是/opt/rocm)挂载为只读目录,同时设置好LD_LIBRARY_PATH,让容器内的进程找得到这些运行时库。理清这一点后,后面的操作就顺理成章了。
2. 麒麟OS上的Docker环境搭建与避坑
麒麟V10上装Docker,说简单也简单,说麻烦也麻烦。网上常见的教程都是针对Ubuntu的,什么apt install docker.io、curl -fsSL get.docker.com | sh,放到麒麟上基本行不通。我这次采用的方法是配置合适的yum源后用yum安装,如果你所在的网络环境连外网受限,也可以预先下载好rpm包离线安装。
2.1 安装Docker:在线源与离线包的选择
我这台机器能访问内网软件源,所以直接配置Docker的CentOS兼容仓库。需要注意,麒麟V10兼容的CentOS版本不同,仓库地址要选对——常见的是兼容CentOS 7或CentOS 8。配置方式比较简单:
cat >/etc/yum.repos.d/docker-ce.repo <<'EOF' [docker-ce-stable] name=Docker CE Stable baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/7.9/x86_64/stable/ gpgcheck=0 enabled=1 EOF yum makecache yum install -y docker-ce docker-ce-cli containerd.io systemctl enable --now docker如果你所在的网络环境访问不了外网镜像源,离线安装也不难:在有一台能联网、系统版本一致的机器上,先拉取docker-ce、docker-ce-cli、containerd.io三个rpm包,拷贝到目标机器后执行yum install -y ./docker-ce-*.rpm就行。离线包安装最大的好处是不依赖网络,坏处是依赖关系偶尔会缺一两个包,碰到时从安装介质里补上即可。
装完第一件事看版本和状态:
docker version systemctl status docker如果docker version能同时打印Client和Server信息,就说明守护进程正常启动了。很多人在这一步就卡壳,后面的章节我会把典型的启动失败问题单独列出来。
2.2 daemon.json配置:镜像加速与存储位置
麒麟OS上的Docker默认配置比较朴素,直接跑也能用,但作为实际部署,我建议提前把/etc/docker/daemon.json写好。这个文件的作用是全局配置,优先级比命令行参数高,内容包括镜像加速、存储驱动、数据目录等。
我这次的配置如下:
cat >/etc/docker/daemon.json <<'EOF' { "registry-mirrors": [ "https://docker.m.daocloud.io", "https://dockerproxy.com" ], "exec-opts": ["native.cgroupdriver=cgroupfs"], "data-root": "/data/docker", "storage-driver": "overlay2" } EOF systemctl daemon-reload systemctl restart docker这里有两个值得说的点。第一,registry-mirrors是用来解决Docker镜像拉取慢、超时问题的,典型场景早就不是只有一线城市了,任何地区都可能遇到镜像下载卡住的情况。配置多个国内镜像源之后,docker pull会快很多。第二,>getenforce
如果返回Enforcing,临时关闭一下再做测试:
setenforce 0KYSEC相关的策略一般可以通过修改配置文件调整,但为了快速验证技术方案,我在测试阶段直接把它关掉了。等整套环境跑通后,再回头把预留的启动参数、设备白名单加进去做合规加固。这里提醒一句:生产环境下不要学我一刀切关安全模块,正确的做法是查阅麒麟安全策略文档,给Docker数据和设备目录加白名单,不过初测阶段为了少踩坑,临时关闭是最高效的路径。
3. 拉取镜像并创建PaddleSpeech容器
Docker引擎Ready之后,最核心的一步来了:选什么基础镜像,怎么透传DCU设备,怎么让容器内的Python环境认得出海光加速卡。这一部分我花了最多的时间,因为不同的镜像、不同的系统库组合会带来各种连锁问题。
3.1 基础镜像选择:别让glibc拖后腿
PaddleSpeech官方建议的Python版本是3.7到3.10,对操作系统的要求不算苛刻。我一开始图省事,直接用官方python:3.7-slim镜像,结果容器里安装飞桨DCU版时直接报错,提示找不到某个GLIBC_3.27符号。后来换成ubuntu:20.04再自己装Python 3.9,问题才解决。
所以我的建议是尽量用基础镜像时选较新的发行版,例如ubuntu:20.04或rockylinux:8,因为飞桨和它依赖的so库对glibc版本有最低要求,太老的基础镜像会带来不少符号缺失问题。麒麟OS虽然本身兼容CentOS 7/8生态,但容器内不一定要依赖麒麟的库,共用宿主机的库反而容易引入不匹配。
如果你有麒麟官方发布的容器基础镜像,那是最好的,优先用它;没有的话,ubuntu:20.04这条路线我在多个版本验证下来是最稳的。
拉取镜像:
docker pull ubuntu:20.043.2 创建容器时必加的三个参数
这一步是整个部署的精髓。启动命令我写成下面这样,逐行解释:
docker run -it --name paddlespeech \ --device=/dev/dcu0:/dev/dcu0 \ --device=/dev/dcu1:/dev/dcu1 \ --device=/dev/dcuctl:/dev/dcuctl \ -v /opt/rocm:/opt/rocm:ro \ -v /data/models:/root/.paddlespeech \ -e LD_LIBRARY_PATH=/opt/rocm/lib:/usr/lib64/:$LD_LIBRARY_PATH \ ubuntu:20.04 \ /bin/bash第一个参数是--device,把DCU设备节点逐个映射进容器。我用2张卡,所以映射了dcu0和dcu1,另外还把控制设备dcuctl也映射进去了。如果你的驱动生成的设备节点名不完全一样,先执行ls -l /dev/dcu*看清楚再写。
第二个参数是-v /opt/rocm:/opt/rocm:ro,把宿主机上海光DTK运行库目录挂进容器。这个目录包含librocblas、libhbrt等核心计算库,飞桨在运行时必须调用。
第三个参数是环境变量LD_LIBRARY_PATH,必须让它优先找到/opt/rocm/lib下的动态库。有些版本的DTK库还依赖/usr/lib64下的一些基础库,所以我也把宿主机的/usr/lib64加到了查找路径里,注意这里只是加入查找路径,不会覆盖容器内已有库。
调试期间我曾经图省事直接加了--privileged,这样确实能让容器访问所有设备,但生产环境不建议这么干。透传该用的设备节点就够了,权限范围越小越好。
3.3 容器内确认DCU设备可见
容器创建并进入bash后,第一步不是急着装PaddleSpeech,而是确认DCU设备真的在容器里:
ls -l /dev/dcu* ls -l /opt/rocm能看到dcu0、dcu1以及/opt/rocm目录内容,就说明设备和库都透传成功。接下来还要验证飞桨能否识别这些设备。先用pip装一个DCU版的飞桨,稍后第4节会细说,这里只展示验证代码:
python -c "import paddle; paddle.utils.run_check()"如果运气好,可以看到类似“PaddlePaddle is installed successfully”的输出,并且设备信息里带有DCU相关的描述。如果输出显示设备数为0,那就要回头检查设备映射、LD_LIBRARY_PATH和驱动状态了。
很多人在这一步反复失败,原因是容器内缺少Python或pip。所以创建容器后先装好基础环境再做验证:
apt update && apt install -y python3 python3-pip为了后续顺手,我习惯把python3软链成python:
ln -sf /usr/bin/python3 /usr/bin/python4. 容器内安装PaddleSpeech并完成语音识别验证
设备可见性确认没问题后,后面就顺畅多了。PaddleSpeech的安装过程说复杂也复杂,说简单也简单:核心是先把飞桨的DCU版装好,再装PaddleSpeech本体,最后准备一段音频测试一下ASR链路。
4.1 安装DCU版飞桨的关键点
这里的坑主要是:PaddleSpeech安装时会自动拉取一个普通版PaddlePaddle,默认是从PyPI装的CPU版或者CUDA版,根本不知道DCU的存在。如果你直接pip install paddlespeech,它十有八九会把普通PaddlePaddle装进环境,随后飞桨再访问DCU设备就会报“无法找到CUDA驱动”之类的错。
正确的顺序应该是先装海光DTK适配过的飞桨DCU版。海光跟飞桨生态有适配产出,在Python包仓库或DTK工具链文档里能拿到对应的包名和版本,我这次用的版本是paddlepaddle-roit==2.5.2,具体以小版本号为准。安装命令如下:
python -m pip install paddlepaddle-roit==2.5.2装完后立刻做一次设备检测:
python -c "import paddle; print('dcu count:', paddle.device.cuda.device_count())" python -c "import paddle; print(paddle.device.cuda.get_device_name(0))"注意打印结果。若输出设备数为1或2,就说明飞桨已经能和DCU通信了。如果输出0,大概率是LD_LIBRARY_PATH没设置好,或者/opt/rocm没有正确挂载。
为了保险,可以在容器内执行:
python -c "import paddle;paddle.utils.run_check()"它会运行一个小矩阵计算,真正把数据送到DCU上跑一遍,比单纯看设备枚举更有说服力。
4.2 安装PaddleSpeech本体
飞桨DCU版就绪后,开始安装PaddleSpeech。为了避免它把普通PaddlePaddle覆盖掉,我用了--no-deps参数,然后再手动把其余依赖补齐:
python -m pip install paddlespeech==2.5.2 --no-deps -i https://pypi.tuna.tsinghua.edu.cn/simple python -m pip install paddleaudio pyworld sentencepiece librosa -i https://pypi.tuna.tsinghua.edu.cn/simple这样做的原因很简单:--no-deps让pip不自动解析依赖,从而避免PaddleSpeech把环境里已装好的paddlepaddle-roit当成普通paddlepaddle而强制替换。手动补的这几个包是它运行时的必需项,包括音频处理、特征提取、文本转写等。
装好之后可以用一条简单的命令验证PaddleSpeech是否可导入:
python -c "import paddlespeech; print(paddlespeech.__version__)"能打印版本号,就说明安装没有硬伤。
4.3 生成测试音频并跑通ASR
PaddleSpeech装上不等于能识别,还需要模型文件和音频输入。第一次运行时会自动下载模型,所以需要保持网络通畅。模型主要包含语音识别用的声学模型和语言模型,体积从几百MB到1GB不等。
测试时我准备了一段中文录音,文件名为zh.wav,采样率16kHz,16bit,单声道。这一步非常重要,PaddleSpeech不少底层的特征提取对采样率有要求,如果你的音频是44.1kHz,建议先转成16kHz再识别,命令可以用ffmpeg:
apt install -y ffmpeg ffmpeg -i input.mp3 -ar 16000 -ac 1 zh.wav然后执行ASR:
paddlespeech asr --lang zh --input zh.wav如果模型还没下载过,它会先输出下载进度,下载完成后开始推理,最后打印识别的文本结果。
我也验证了用Python接口调用的方式,方便嵌入代码:
from paddlespeech.cli.asr.infer import ASRExecutor asr = ASRExecutor() result = asr(audio_file="zh.wav", lang="zh") print(result)在DCU上,推理过程会调用/opt/rocm里的计算库,可以通过htop或者dcuinfo观察卡的使用率。只要能看到DCU工作,就说明整个链路彻底打通了。
PaddleSpeech不仅仅能做ASR,还能做语音合成、声纹识别、标点恢复等。这次先以ASR为验证目标,测试通过后类似的能力都可以在这个Container里按同样思路扩展。
5. 常见问题与排查思路实录
这次部署中踩过的坑,其实大部分在标题里就暗示了:Docker权限错误、服务启动失败、设备不识别、镜像下载慢、网络不通。我整理成了一张速查表,希望你看完能少走弯路。
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
docker: permission denied while trying to connect to the Docker daemon socket | 当前用户不在docker组 | 执行sudo usermod -aG docker $USER并重新登录,或者用sudo docker临时运行 |
Docker服务启动失败,日志里提示iptables相关 | firewalld/iptables冲突或内核模块未加载 | 先modprobe overlay && modprobe br_netfilter,再关闭或放行防火墙,最后systemctl restart docker |
容器启动后ls /dev/dcu*为空 | --device参数没写全,或者宿主机驱动异常 | 在宿主机执行ls /dev/dcu*,确认节点存在后再写启动参数 |
飞桨检测不到DCU设备,device_count()为0 | 版本不对或运行库没挂载 | 确认安装的是paddlepaddle-roit而不是普通PaddlePaddle;检查/opt/rocm是否挂载以及LD_LIBRARY_PATH |
运行推理时报GLIBC_XX not found | 基础镜像太旧,glibc版本不够 | 换用ubuntu:20.04或更新的基础镜像,不要用过于精简的镜像 |
paddlespeech asr下载模型卡住 | 网络问题或默认下载源不稳定 | 手动在宿主机下载模型文件并放到/root/.paddlespeech对应的模型目录,或配置镜像源 |
pip install超时 | PyPI源访问慢 | 使用清华、阿里等镜像源,例如-i https://pypi.tuna.tsinghua.edu.cn/simple |
容器网络不通,无法apt update | Docker网桥与宿主机防火墙冲突 | 检查firewalld状态,必要时临时systemctl stop firewalld,再重启Docker |
5.1 Docker权限错误怎么解决最快
这个问题几乎每个人都会碰到。安装完Docker之后,如果当前用户不在docker组里,直接执行docker ps就会报permission denied while trying to connect to the docker api。最快的办法是把用户加进docker组,然后重新登录会话;不建议动不动就sudo chmod 777 /var/run/docker.sock,那会带来安全隐患。
sudo usermod -aG docker $USER执行成功后退出终端重新登录,再执行docker ps验证。如果还报错,可以查看socket权限:
ls -l /var/run/docker.sock正常情况属主是root:docker,权限是rw。如果不是这个状态,说明Docker安装过程中socket权限被修改过,可以重置。
5.2 容器内找不到设备文件,怎么排查
按我前面写的--device参数启动,设备节点一般都能透传进去。但如果宿主机上驱动加载出了问题,哪怕参数写对了也没用。排查时先在宿主机看:
lspci | grep -i dcu dmesg | grep -i dcu ls /dev/dcu*如果驱动没加载,先处理驱动,比如确认内核模块是否存在,必要时重新加载:
modprobe dcu然后再启动容器。容器内的设备文件必须与宿主机一一对应,比如宿主机上是/dev/dcu0,容器内也应该是/dev/dcu0,命令里的映射关系不能写反。
5.3 容器内运行库不匹配的问题
海光DCU运行时库往往依赖宿主机上的一些基础系统库,比如libstdc++、libgomp等。容器内如果版本太旧,运行时就会报符号找不到。除了换基础镜像,我还有一个备选方案:把宿主机的/usr/lib64/x86_64-linux-gnu或/usr/lib64挂载进去,然后通过LD_LIBRARY_PATH让动态加载器优先搜索:
-v /usr/lib64:/host-libs:ro -e LD_LIBRARY_PATH=/host-libs:/opt/rocm/lib:$LD_LIBRARY_PATH这种做法的缺点是可能引发容器内原有库版本冲突,所以只建议在紧急情况下使用,正常情况下还是把基础镜像换新比较干净。
5.4 模型下载失败怎么处理
PaddleSpeech首次运行ASR会去下载模型。如果网络环境受限,下载进度条经常卡住不动。解决方式是手动提前准备好模型文件。
PaddleSpeech模型默认缓存在~/.paddlespeech目录下,目录结构按模型名区分。你可以在有网络的机器上先把对应模型整个目录打包,然后拷贝到容器或挂载目录里。我这次就是把/data/models作为宿主机目录挂载到容器/root/.paddlespeech位置,这样模型文件一次下载,以后重建容器都不用再拉。
5.5 容器内的飞桨和系统库冲突
在整个过程中最容易遇到的一类问题是,PaddleSpeech自动依赖里有一堆音频库,这些库之间可能对OpenMP、libsndfile等版本互相有要求。装的时候不要一次性把所有包全打进去,尽量按需安装。如果遇到libpython版本冲突,我建议直接重建一个干净的容器环境,在容器里专门用python -m venv /opt/venv再装一套虚拟环境,之后所有PaddleSpeech依赖都放进这个venv里,能有效隔离系统Python。
6. 整个部署过程中的一点体会
这次在国产CPU+DCU+麒麟OS上用Docker跑通PaddleSpeech,最大的收获不是“装上了”,而是把容器透传设备和国产计算卡运行库之间的关系彻底理顺了。从NVIDIA转到DCU,思维上最大的转变是:不要把它当作一个依赖某个魔法镜像的“黑盒”,而要理解容器里运行的程序最终需要什么,设备节点、动态库、环境变量这些都要按需提供。
如果让我重新再做一次,我第一件事还是先把宿主机上的DCU驱动和设备节点状态确认清楚,然后再按设备挂载、运行库挂载、飞桨DCU版、PaddleSpeech本体这个顺序推进。每一步都用最简单的命令验证,绝不跳步。
最后再分享一个小技巧:建议把完整的docker run命令整理成shell脚本放到项目仓库里,同时保留一份最终的容器提交镜像。这样团队其他人拿到脚本,或者直接load镜像,就能在另一台海光环境上快速复现整个PaddleSpeech服务,这才是这次部署最大的价值所在。