
你有没有过这种体验手里有一块还不错的NVIDIA显卡想跑百度Apollo做点感知或规划相关的实验结果照着网上几篇文章折腾两天不是Docker起不来就是编译到一半被各种报错打断。我自己第一次在Ubuntu 20.04上从源码编译Apollo时就被“GPU环境配置”这个看似简单、实则全是坑的环节卡了很久。这篇教程我不敢说能把所有版本的坑都踩平但至少能给你一套可以完整走下去的路线从Ubuntu系统准备、NVIDIA驱动安装、Docker GPU支持到Apollo源码拉取、开发容器启动、CPU/GPU模式编译再到最后的Dreamview验证和常见报错排查。整个流程我按“保姆级”的标准来写每一步都解释为什么这么做而不是只扔一串命令。1. 为什么偏要自己编译Apollo不直接用官方镜像1.1 预编译镜像和源码构建其实是两条路线Apollo官方确实提供了预编译好的Docker镜像拉到本地跑起来就能看到Dreamview界面配合官方demo数据回放一下确实很爽。但这里有个关键区别预编译镜像里装的是已经编好的二进制产物你拿到的只是一个“黑盒”。你要是想改感知模块里的一个后处理参数想在规划模块里加自己的逻辑或者想把某个模型换成自己的训练结果光靠改配置文件是不够的必须重新编译相关模块。源码编译做的事情就是把Apollo整个工程里的C、Python、Protobuf、CUDA相关代码在你自己机器上从头生成一遍二进制。这个过程虽然耗时但会把你机器上的环境、依赖、编译参数全部固化下来。简单说预编译镜像是租来的精装房源码编译是拿毛坯房自己装修装修过程累但你知道每一根管线是怎么走的。1.2 编译这件事的真实性价比先说结论如果你只想跑通一个demo、看看Apollo长什么样源码编译的性价比极低直接拉官方镜像就完事了。但如果你属于下面这几类人源码编译几乎是绕不过去的门槛要改Apollo内部代码不管是感知、预测、规划还是控制模块要把Apollo和自研系统做集成需要知道模块间的编译依赖做自动驾驶方向的研究生或工程师面试或汇报时被问到“Apollo的编译流程是怎样的”需要在没有官方预编译支持的硬件或环境上做适配。我个人的建议是第一次编译挑一个周末的上午开始不要在工作日的晚上搞。编译过程中大概率会遇到一两个意想不到的环境问题留足时间比留足耐心更重要。1.3 版本选择Ubuntu 20.04对应哪代ApolloApollo的迭代速度很快不同版本的系统和Docker镜像依赖差别不小。从支持矩阵来看Apollo 7.0及以上版本对Ubuntu 20.04的支持已经很成熟我自己用的就是Apollo 7.x分支。如果你还在用Apollo 5.0或更早的版本Ubuntu 20.04上会遇到不少兼容性麻烦建议先升级。选版本这事我的建议是不要追最新的master分支因为它可能每天都在变今天能编过的代码下周可能就新增了一个依赖。选定一个release分支比如7.0.0、8.0.0这样的稳定tag后面遇到问题也容易在社区里搜到答案。2. 硬件体检与Ubuntu基础环境准备2.1 硬件准入清单Apollo本身的模块非常重如果硬件不达标后面的编译和运行体验会非常难受。我整理了一个参考表注意是“参考”不是绝对门槛部件最低配置推荐配置说明CPU8核16核及以上编译时Bazel会并行跑编译任务核越多越快内存16GB32GB及以上16GB编译大模块时容易OOM磁盘50GB空闲100GB SSD源码镜像编译缓存会吃掉大量空间GPUNVIDIA GTX 1060RTX 3070及以上显存至少6GB感知模块很吃显存网络稳定宽带稳定宽带拉镜像和依赖时速度很重要这里要特别强调一下磁盘。很多人只盯着源码体积却忽略了Docker镜像和Bazel缓存。Apollo的dev容器镜像通常有几个GB编译产生的缓存和临时文件动辄几十GB如果你系统盘只有100GB很容易编到一半磁盘满了。2.2 先确认GPU驱动状态在装任何东西之前先用两个命令确认系统状态# 查看系统版本 lsb_release -a # 查看显卡硬件型号 lspci | grep -i nvidia如果能看到类似NVIDIA Corporation GP104 [GeForce GTX 1080]的输出说明系统至少识别到了显卡。接下来看驱动是否已经正常nvidia-smi如果显示了一大块表格右上角有Driver Version和CUDA Version恭喜你驱动这关已经过了。如果提示command not found说明驱动没装好或者完全没装。2.3 没有驱动时的安装选择Ubuntu 20.04上安装NVIDIA驱动常见有两种方式方式一直接用系统仓库的驱动推荐新手# 查看系统推荐的驱动版本 ubuntu-drivers devices # 安装推荐版本 sudo apt install nvidia-driver-545 sudo reboot这种方式省事驱动版本经过Ubuntu测试稳定性有保障。缺点是你拿到的不是最新驱动但Apollo对驱动版本要求并不苛刻只要能满足NVIDIA Container Toolkit的运行需求就行。方式二用NVIDIA官方runfile安装这种方法可以精确控制版本但安装过程需要先关闭图形界面服务步骤多、容易出错新手不建议一上来就搞。如果你确实需要某个特定驱动版本再考虑这条路。2.4 处理nouveau把驱动冲突扼杀在源头这是新手最容易忽略的一步。Ubuntu自带一个开源的nouveau显卡驱动如果不禁用它NVIDIA官方驱动装上后可能无法正常工作表现是nvidia-smi能显示但图形界面卡死或者开机黑屏。禁用方法sudo bash -c echo blacklist nouveau /etc/modprobe.d/blacklist-nouveau.conf sudo bash -c echo options nouveau modeset0 /etc/modprobe.d/blacklist-nouveau.conf # 重新生成initramfs sudo update-initramfs -u # 重启 sudo reboot重启后确认nouveau已经不再加载lsmod | grep nouveau这条命令没有输出就说明禁用成功。之后再安装NVIDIA驱动就干净多了。3. Docker与NVIDIA Container ToolkitGPU进入容器的关键一跳3.1 Docker是Apollo的开发载体不只是部署方式Apollo从很早的版本开始就拥抱了Docker把整个编译环境和运行环境都封装在容器里。这样做的好处是你不需要在宿主机上装一堆特定版本的依赖也不怕搞坏系统环境。坏处是如果你对Docker不熟悉会感觉多了一层无形的墙。编译Apollo源码时我们并不是直接在Ubuntu里执行g或make而是把源码挂载进一个官方提供的开发容器然后在容器内部执行Bazel构建命令。这个容器里已经预装好了编译需要的工具链、CUDA Toolkit、依赖库。3.2 把NVIDIA Container Toolkit装好要让容器里能访问到宿主机GPU光装个Docker是不够的。Docker容器默认是没有GPU设备的需要借助NVIDIA Container Toolkit把GPU设备映射进容器。安装步骤通常是这样# 添加NVIDIA的软件源以官方仓库为例 distribution$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list sudo apt update sudo apt install -y nvidia-container-toolkit # 配置Docker使用NVIDIA runtime sudo nvidia-ctk runtime configure --runtimedocker sudo systemctl restart docker这里面的nvidia-ctk runtime configure很多人会漏掉它会把NVIDIA runtime自动写进Docker的daemon配置里。不执行这一步后面的--gpus参数可能不会生效。3.3 用一条docker命令验证容器GPU可用装好之后先别急着拉Apollo镜像用一条最简单的命令验证容器里能不能看到GPUdocker run --rm --gpus all nvidia/cuda:11.4.0-base-ubuntu20.04 nvidia-smi如果能看到GPU信息说明宿主机、Docker、NVIDIA Container Toolkit三者已经打通。如果报错我建议你先在这里把问题解决不要带着这个问题去碰Apollo否则后面一片混乱。碰到最常见的一个报错是could not select device driver with capabilities: [[gpu]]这个基本就是nvidia-container-toolkit没装好或者Docker daemon没有加载NVIDIA runtime。回到上一步重新执行sudo nvidia-ctk runtime configure --runtimedocker再重启Docker。4. Apollo源码获取以及工程目录怎么读4.1 拉代码前的分支选择Apollo的代码托管在GitHub上项目名是ApolloAuto/apollo。拉取源码的方式git clone https://github.com/ApolloAuto/apollo.git cd apollo # 查看所有远程分支选择一个稳定release git branch -r # 切换到具体的release分支 git checkout -b apollo7 origin/release/7.0.0这里有个经验不要直接用master分支。master分支是日常开发分支可能处于不可编译状态。release分支经过完整测试踩坑概率小很多。如果你只需要最新某分支的最新代码可以用--depth 1参数做浅克隆节省时间和磁盘git clone --depth 1 --branch release/7.0.0 https://github.com/ApolloAuto/apollo.git4.2 源码目录的结构地图很多新手把源码拉下来之后不知道怎么读我标注一下几个关键目录目录/文件作用modules/核心自动驾驶模块包括perception、prediction、planning、control等cyber/Apollo自研的通信中间件类似ROSdocker/Docker相关脚本和Dockerfile包括dev_start.sh等scripts/各种辅助启动脚本apollo.sh顶层构建脚本编译、测试、格式化都要用它.env环境变量配置比如用户UID映射还有一个不显眼但很重要的目录third_party里面放了一些第三方依赖的构建规则。编译时你会发现Bazel会频繁引用这个目录。4.3 框架配置文件与访问凭据在源代码根目录下有一个.env文件里面的配置会影响后续容器的启动。我自己遇到的一个典型问题是容器内用户权限和宿主不一致导致源码目录在容器内无法写入。解决办法就是在.env里把用户UID和GID设置成宿主用户的# 查看宿主机当前用户的UID和GID id -u id -g然后在.env文件里确认APOLLO_BAIDU_UID和APOLLO_BAIDU_GID和上面输出一致。这样容器内创建的文件在宿主机上也能正常读写。另外Apollo的构建过程需要从官方平台获取一些数据资源和依赖你在跑dev_start.sh或后续编译前需要先在Apollo开放平台上注册并获取访问凭证然后按照官方文档的指引把凭证配置到环境变量里。这一步各家教程说法不太统一但核心逻辑就是让Apollo构建系统在下载数据时能识别你的身份。不要跳过不然编译到一半会出现一个让你摸不着头脑的下载失败。5. 启动开发环境容器让编译在隔离环境进行5.1 dev_start.sh参数及含义Apollo提供了一组脚本来管理开发容器核心就两个dev_start.sh和dev_into.sh。启动开发容器的标准姿势cd apollo # -g表示使用GPU bash docker/scripts/dev_start.sh -g-g这个参数很关键它告诉脚本要启动一个可以访问GPU的容器。如果你漏了它容器就算起来了后面编译GPU模块时也会找不到CUDA设备。启动过程中脚本会检查本地有没有Apollo dev镜像没有的话会自动拉取。这一步消耗时间取决于网络状况有时候会等很久。我建议在没有其他网络占用的时候做这一步。5.2 dev_into.sh进入容器与权限处理容器启动之后另一个终端执行bash docker/scripts/dev_into.sh正常情况下你会进入容器的shell提示符会显示类似apolloin-dev-docker这样的字样。注意这时候你不是root用户而是Apollo自动映射的普通用户。很多人在这时候用sudo发现没有权限以为容器坏了其实这是故意的为了安全和权限一致。进入容器后先确认几个信息# 确认源码被挂载到容器内 ls /apollo # 确认GPU设备在容器内可见 nvidia-smi/apollo目录就是宿主机上源码挂载进来的路径。nvidia-smi有输出说明GPU映射成功。5.3 镜像拉取失败或启动超时的排查逻辑第一次dev_start.sh最常见的两个问题问题1镜像拉取很慢或超时这种问题没有灵丹妙药只能换网络环境或者配置Docker镜像加速器。Apollo镜像体积不小耐心等是常态。如果反复拉到一半断掉可以手动docker pull镜像的完整名称再重新跑dev_start.sh。问题2容器启动后立刻退出多半是环境变量或端口冲突。先看容器状态docker ps -a docker logs apollo_dev_你的用户名日志里通常会有明确提示比如端口被占用或者权限不足。Apollo默认会映射一些端口到宿主机比如8888、8889等端口。如果本机已经有程序占用这些端口容器就起不来需要调整映射或关掉冲突进程。6. 源码编译全流程CPU/GPU构建的底层差异6.1 GPU构建比CPU构建多做了哪些事Apollo的构建脚本同时支持CPU和GPU两种模式。很多人不理解为什么还有CPU模式以为所有模块都必须GPU才能跑其实不是。CPU模式把代码编译成纯CPU可执行版本感知模块里依赖CUDA的部分会被禁用或降级适合没有NVIDIA显卡的机器做纯算法开发。GPU模式在CPU模式基础上额外编译CUDA扩展和GPU算子感知模块能用上GPU加速推理。GPU模式编译出的二进制文件比CPU模式大不少因为里面嵌入了CUDA kernel代码和GPU相关依赖。如果你的机器有NVIDIA显卡务必用GPU模式否则后面跑感知demo时会报CUDA错误或者模型推理速度慢到让人崩溃。6.2 编译命令与日志解读在容器内执行cd /apollo # GPU模式构建 ./apollo.sh build_gpu # CPU模式构建无NVIDIA卡时 ./apollo.sh build_cpu有些新版本Apollo把这两种模式合并到了统一的build命令里通过参数区分。具体以你拉取的版本里的./apollo.sh --help输出为准但build_gpu和build_cpu这组命令在多数release版本中仍然有效。编译开始后你会看到Bazel输出满屏的进度信息类似[52,700 / 53,200] ... Compiling modules/perception/lidar/...; 4s local [53,100 / 53,200] ... Linking modules/planning/scenario_manager; 12s local看到这种输出说明Bazel正在并行编译。整个过程可能从半小时到两三个小时不等取决于你机器的核心数和磁盘性能。编译期间不要手动去杀Bazel进程也不要频繁中断否则下次重新编译要从断点继续虽然增量构建会接着跑但上下文切换本身也耗时。6.3 编译真正能提速的几个点第一次编译慢是没办法的但后续增量编译可以明显加速前提是你做对几件事第一不要随便清理Bazel缓存。Apollo的构建缓存叫bazel-*符号链接可以占用几十GB空间。很多人为了省空间把它删了结果下次编译一下回到了解放前。真要清理也等确认不会再频繁改动代码再做。第二控制并行作业数。Bazel默认会按CPU核心数开并行任务但每个任务吃内存。如果编译过程中发现内存接近满甚至出现OOM killed可以限制作业数# 容器内设置 ./apollo.sh config --release export BAZEL_JOBS8 ./apollo.sh build_gpu第三保持源码目录在一个稳定的文件系统上。如果你把源码放在机械硬盘编译速度会被磁盘IO拖累如果放在网盘挂载目录或FUSE文件系统Bazel甚至会因为文件锁问题报错。把源码放在本地SSD上是最稳的。7. 编译成果验证与Dreamview运行7.1 多少产出物才算编译成功当Bazel输出类似以下的结尾时说明编译链路基本走通了Build completed successfully, 53200 total actions但也有一种情况是Bazel显示successfully你启动Dreamview却仍然报缺少库。这是因为Apollo有些模块是动态库有些是独立的二进制全量编译完成后应该在/apollo/output或其他导出目录生成最终的部署产物。部分版本需要额外执行导出步骤./apollo.sh deploy或者用./apollo.sh build时加上--clean_output等标志来生成干净的输出目录。具体看脚本提示但核心思路是Bazel编译成功 ≠ 运行环境ready你可能还需要把产物“安装”到运行目录。7.2 启动Dreamview并回放demo数据编译完成后最好验证一下Dreamview能不能正常起来。通常流程是# 容器内启动Dreamview bash scripts/bootstrap.sh start看到类似Dreamview is running at http://localhost:8888的提示后打开宿主机浏览器访问http://localhost:8888。如果你看到前端界面加载出来了说明编译产物能正常运行。接下来回放demo数据。Apollo官方会提供一批demo数据你需要按照上述凭证流程获取并放到指定数据目录。在Dreamview界面里选择相应数据包点击播放如果看到可视化界面里出现了道路、车辆、障碍物框等元素就说明整套链路通了。7.3 自查GPU和CPU资源占用验证阶段还有个很有用的动作在宿主机开一个终端跑nvidia-smi观察Apollo运行时的GPU使用率。正常的感知模块推理阶段GPU显存使用应该明显上升。如果nvidia-smi完全没变化很有可能是容器GPU映射出了问题或者你跑的demo没有启用GPU推理。8. 高频报错排查分层定位不瞎猜8.1 GPU层问题症状容器内nvidia-smi报command not found或could not access NVIDIA GPU。先回宿主机制确认nvidia-smi正常再确认容器启动时是否加了-g参数以及NVIDIA Container Toolkit是否安装完整。如果宿主机驱动版本过低或损坏容器里的CUDA即使存在也访问不了硬件这时候卸载重装驱动比在容器里折腾有效得多。8.2 Docker层问题症状dev_start.sh启动时报容器名称冲突。用docker ps -a查看是否有之前残留的容器删掉即可docker rm -f apollo_dev_你的用户名症状容器可以启动但容器内的源码目录只读。检查.env文件中UID/GID是否和宿主用户一致。不一致的话容器内用户对挂载的源码目录没有写权限Bazel生成临时文件时就会失败。8.3 Bazel、磁盘和内存问题症状编译过程中提示No space left on device。先用df -h查看系统盘和源码所在分区。Apollo编译的临时目录默认在/root/.cache/bazel或/home/用户/.cache/bazel如果你把源码放一个盘家目录缓存却在另一个小分区很容易爆。解决方案是把Bazel缓存目录改到空间大的分区export TEST_TMPDIR/bigdisk/bazel_cache症状编译时Killed或者日志末尾是OutOfMemory。这是内存不够。减少并行作业数在容器内执行echo 4 $(bazel info output_base)/...这种方式比较复杂更简单的方法是直接给Docker容器增加内存限制或关掉其他占用内存的应用。如果是16GB内存的机器建议编译时不要同时开浏览器、IDE和一堆窗口给Apollo留足内存。8.4 运行时层问题症状Dreamview页面能打开但看不到仿真场景或数据回放卡住。先检查数据文件路径是否放对再检查docker logs 容器名或Dreamview前端的控制台是否报错。很多时候是端口没映射完整或者浏览器缓存了旧版前端清一下缓存或换个浏览器试试。我在实际编译Apollo的过程中最大的体会是源码编译这件事环境和流程比代码本身更容易卡住人。很多人编译失败不是因为不会敲命令而是因为某一步的环境状态没有验证就往下走等到最后报错时才发现根因在很前面的地方。所以这套流程里我特别强调一个习惯每做完一个阶段性的步骤先确认输出再进入下一步。检查GPU驱动用nvidia-smi检查容器GPU映射也用nvidia-smi检查编译产物就看Bazel输出。每一步都确认过了后面真的会顺畅很多。另外编译Apollo是个体力活机器好能省很多时间但机器一般也不意味着不行——我就在一台8核16GB的老笔记本上成功编译过只是花的时间多一些中间还插了两次内存不足的坑最后把并行度调低也就过了。希望你读到这里已经对从源码编译Apollo的整个过程心里有底了。接下来大胆试就行。