☰
Ubuntu 20.04下Apollo源码编译与GPU环境配置全攻略
2026/10/5 3:21:10 网站建设 项目流程

直接进入正题。如果你手头正好有一台Ubuntu 20.04的机器,又想从源码把Apollo自动驾驶平台完整编译一遍,并且顺便把GPU环境一次配好,这篇教程就是给你写的。我从环境规划、驱动安装到编译命令、踩坑记录,全部按自己实际操作过的流程来梳理,尽量做到每一步都能直接对着敲。

Apollo这个平台,官方是建议直接拉Docker镜像用的,日常跑仿真、做算法验证都没问题。但只要你打算改感知模块里的模型推理代码、自定义传感器驱动,或者需要针对自己的硬件调优编译参数,就必须从源码编译。源码编译能拿到完整的中层接口和底层依赖关系,二次开发心里才真正有底。这个教程适合有一定Linux基础、准备入坑自动驾驶开发或者已经在做相关项目的同学,GPU环境配置也会分步骤讲清楚。

1. 动手之前先想清楚:Apollo源码编译的整体思路

1.1 为什么推荐在Ubuntu 20.04上做源码编译

Apollo官方文档里明确支持Ubuntu 20.04 LTS,并且从Apollo 6.0之后的版本基本都以20.04作为主力开发和测试环境。用20.04不只是“能用”,而是整个依赖链都卡在这个版本的系统库上。比如Apollo内置的Cyber RT通信框架依赖特定版本的glog、protobuf、abseil-cpp,这些库如果系统版本太新或者太旧,编译期会出现各种ABI不兼容的报错。Ubuntu 20.04刚好把这些基础依赖锁在了一个稳定区间内。

另外,20.04的生命周期到2025年,官方源和第三方源都还在正常维护,驱动和编译工具链的兼容性问题少很多。如果你手头是22.04,也不是完全不能搞,但需要自己处理不少补丁,适合有经验的人折腾。新手入门,直接上20.04是最省心的选择。

1.2 源码编译的完整流程拆解

Apollo源码编译并不是直接在当前系统里make,它的做法是把所有编译工具和依赖都封装在一个Docker容器里,这个容器叫做dev container。你在宿主机上做的只是准备环境、启动容器、进入容器,然后在容器内部执行编译脚本。

所以整个流程分为四大块:

  1. 宿主机环境准备:Ubuntu 20.04系统、基础软件包、Docker环境。
  2. GPU环境配置:安装NVIDIA驱动、配置nvidia-container-toolkit,保证容器里能调用显卡。
  3. 源码获取与容器启动:下载Apollo源码,用官方脚本启动开发容器。
  4. 在容器内编译:执行./apollo.sh build_gpu之类的编译命令,等编译完成。

这个设计的好处是,宿主机上不需要手动安装CUDA、CUDNN、TensorRT这些重量级依赖,容器镜像里全都带好了。你只需要保证宿主机显卡驱动够新,让容器里的CUDA能访问到GPU就行。很多人在这一步被卡住,后面会详细说明。

1.3 硬件配置建议与磁盘规划

Apollo编译是个比较吃资源的事情,尤其是感知、预测这类模块,包含大量模板代码和深度学习算子,编译时会同时起多个编译任务,内存和CPU占用都相当高。

我实测建议的配置是这样的:

  • CPU:8核16线程以上,编译时间能压到可接受范围。如果只有4核,编译过程会非常痛苦,耗时可能翻两三倍。
  • 内存:至少16GB,建议32GB。编译时十几个g++进程同时跑,每个占据1-2GB内存很常见,16GB以下容易出现OOM强行杀进程。
  • 磁盘:源码+编译缓存至少需要80GB可用空间,建议预留120GB。Apollo的编译缓存和Docker镜像体积都不小,尤其是build目录里的中间文件,动辄几十GB。
  • 显卡:如果你只是编译不做训练,显卡要求不高,但GPU环境配置最好还是做好。NVIDIA驱动建议450以上,CUDA版本由容器决定,一般是11.1或11.4。

磁盘规划这里要特别提醒一句,不要把源码放在空间不够的分区。我见过不少人在默认的home分区只有50GB的机器上硬解压源码,编译到一半报磁盘满。建议提前用df -h检查一下磁盘空间,必要时把源码放到独立挂载的数据盘上,并且给Docker的data-root也留够空间。

2. Ubuntu 20.04系统与GPU环境配置

2.1 系统安装与基础软件包配置

如果你已经装好了Ubuntu 20.04,可以跳过系统安装部分,但有几个基础项必须确认。

首先是系统更新。刚装完的系统建议先跑一遍:

sudo apt update && sudo apt upgrade -y

把内核、基础库全部更新到最新,避免后续因为系统库太老导致驱动编译或者容器运行出问题。

然后是几个编译和下载必需的软件包:

sudo apt install -y git vim curl wget build-essential \ net-tools gnupg2 ca-certificates lsb-release \ software-properties-common

这些包里面,build-essential在宿主机编译阶段不一定用到,但后续排查问题、装一些辅助工具时经常需要。software-properties-common则用于添加PPA软件源,比如后续安装Docker时就会用到。

另外建议把系统python环境理清楚。Ubuntu 20.04自带python3.8,Apollo的脚本会调用这个默认版本,不要去系统里乱装Python 3.10或者3.11,也不要随意修改/usr/bin/python3的软链指向。我在实际维护环境时遇到过用户把python3软链改到别的版本,结果Apollo的启动脚本直接报语法错误,排查了很久才发现是这个原因。

如果机器需要远程开发,顺手把SSHD配置好:

sudo apt install -y openssh-server sudo systemctl enable ssh sudo systemctl start ssh

2.2 安装NVIDIA驱动:别装错版本也别慌

这一步是整个GPU环境配置里最容易出问题的地方。Apollo容器内部已经打包好了CUDA和CUDNN,宿主机不需要安装完整CUDA工具包,但要有一个足够新、能识别你显卡的NVIDIA驱动。

我推荐的安装方式是用ubuntu-drivers自动检测并安装推荐版,而不是去官网手动下载runfile。自动方式装出来的驱动和内核模块配合得更好,重启后不容易出现模块加载失败的问题。

sudo ubuntu-drivers autoinstall

这条命令会根据你的显卡型号自动安装合适的驱动。装完重启:

sudo reboot

重启后用nvidia-smi验证:

nvidia-smi

输出里能看到驱动的版本号(比如520.61.05),并且能看到显卡型号和显存信息,说明驱动已经正常工作了。

这里说说版本这事。热搜词里提到nvidia 520这个版本,它是支持Ubuntu 20.04的,而且对RTX 30系、40系显卡支持都挺好。如果你是老显卡(比如GTX 1080 Ti、1660这类),520驱动也能兼容,但如果你遇到驱动版本过新导致的老卡显存识别异常,可以考虑装回460或470系列,这些版本在自动驾驶社区里验证得最多。

还有一个非常常见的坑:驱动装好了,nvidia-smi正常,但Docker容器里跑nvidia-smi却提示找不到设备。这个一般不是驱动问题,而是后面要说的nvidia-container-toolkit没有装或者配置不对。建议在宿主机验证驱动之后,再确认一下内核模块:

lsmod | grep nvidia

输出中应该能看到nvidia、nvidia_uvm、nvidia_drm等模块,如果只有nvidia没有nvidia_uvm,Docker容器里基本上没法用GPU。

2.3 Docker与nvidia-container-toolkit配置

Apollo的整个编译环境都依赖Docker,所以这一步必须仔细。

Docker安装可以直接套用官方源:

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io

装完后建议把当前用户加入docker组,这样后面执行Apollo脚本时不用每次都加sudo:

sudo usermod -aG docker $USER newgrp docker

注意:执行newgrp docker后当前终端就生效了,不需要重启。如果终端重新登录后依然报权限问题,就重新开一个终端或者注销再登录一次。

然后安装nvidia-container-toolkit。这个是让Docker容器能够访问宿主GPU的桥梁,没有它,即使驱动装得再好,容器里的Apollo也感知不到显卡。

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 sudo systemctl restart docker

这里详细说一下为什么需要这个toolkit。Docker容器和宿主机共享内核,但设备访问是靠cgroup和设备映射来管理的。nvidia-container-toolkit做的事情就是,在你启动容器时自动检测NVIDIA_VISIBLE_DEVICES环境变量,然后把宿主机上的GPU设备和相关驱动库挂载进容器。没有这层挂载,容器里即使有CUDA库也找不到物理设备。

为了验证配置成功,可以跑一个测试容器:

docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi

如果能看到类似宿主机一样的nvidia-smi输出,说明GPU环境已经打通了。

3. Apollo源码获取与编译实操

3.1 获取Apollo源码与目录结构说明

源码获取在Apollo GitHub仓库页面上可以直接clone,但国内网络克隆大仓库经常不稳定,我建议优先用两个方式:

方式一,git clone官方仓库:

git clone https://github.com/ApolloAuto/apollo.git

这个仓库包含完整历史,体积比较大。如果只需要指定版本,可以加--branch v7.0.0 --depth 1,只拉取该版本的单层commit,能省不少时间和流量。

方式二,如果github速度太慢,可以考虑从镜像仓库拉代码,或者下载官方发布的源码压缩包。我在实际下载中觉得镜像站更稳,但要注意选择与目标版本一致的标签,别拉错版本。

源码目录结构需要简单梳理一下,这样后续编译遇到问题时能快速定位:

  • modules/:核心功能模块,包括perception、planning、control、prediction、localization等。
  • cyber/:Apollo自研的通信中间件Cyber RT,整个系统的消息通信和调度都依赖它。
  • docker/:Docker脚本和镜像配置文件。
  • scripts/:常用启动和部署脚本。
  • apollo.sh:编译入口脚本,所有编译操作都通过它完成。
  • WORKSPACE:Bazel编译系统的顶层配置,里面定义了外部依赖库的下载地址和版本。

建议clone完成后先切换到你想用的版本。我这里的示例以Apollo 7.0.0为主,因为它在感知、规划模块上有较多更新,并且社区资料最丰富。

cd apollo git checkout v7.0.0

如果你需要旧版本或者更新的版本,注意对应好编译脚本的差异。Apollo 7.0和Apollo 6.0在构建流程上区别不大,但Apollo 9.0之后的版本改用了新的构建工具链,命令会不一样。本文主要针对7.0/8.0这一类经典版本。

3.2 启动Apollo开发容器:先理解dev_start前后发生了什么

Apollo官方封装了一个专门用于开发和编译的Docker镜像,里面预装好了所有编译依赖,包括Bazel、CUDA、CUDNN、TensorRT等。不用手动去挨个安装这些重依赖,只要用脚本启动容器即可。

进入源码目录后执行:

bash docker/scripts/dev_start.sh -g

这里-g参数非常重要,它表示把GPU参数传给Docker容器,Docker会通过--gpus all把宿主机显卡透传到容器内部。如果漏掉-g,即使宿主机驱动正常,容器里也无法使用GPU。

这个脚本实际做这么几件事:

  1. 测试Docker环境是否可用。
  2. 拉取Apollo开发镜像,比如apolloauto/apollo:dev-x86_64-20.04-7.0.0,如果本地没有会先从远程拉取。
  3. 创建一个名为apollo_dev_<username>的容器,并把源码目录挂载到容器内的/apollo。
  4. 配置容器的网络模式和数据卷,方便共享编译缓存。
  5. 设置--gpus all参数,让容器能分配GPU资源。

启动成功会看到[OK] Apollo Development Environment Ready之类的提示。如果报镜像拉取失败,一般是网络问题,可以多尝试几次,或者配置好镜像加速。

启动之后是进入容器:

bash docker/scripts/dev_into.sh

执行后终端提示符会变成类似apollo@in_dev_docker:/apollo$的形式,说明你已经进入容器内部了。

值得提醒的是,源码目录是宿主机目录直接挂载进容器的,所以你在容器里修改代码,宿主机上也会同步改变。反过来也一样,在宿主机上直接改动源码目录,容器里也能看到。开发调试非常方便,不需要频繁地拷贝文件。

3.3 执行源码编译:GPU版与CPU版的取舍

进入容器后,第一次编译前需要执行资源检查和依赖配置脚本。Apollo提供了一个一键初始化脚本:

bash setup.sh

这个脚本会设置环境变量APOLLO_ROOT_DIR、CYBER_PATH等,并且把Cyber RT的Python模块路径加入PYTHONPATH。如果不执行这一步,后续运行测试程序时会找不到Cyber模块。

真正的编译命令是:

./apollo.sh build_gpu

执行这个命令后,Apollo会用Bazel来构建整个工程。第一次编译耗时很长,我实测在16核32GB内存的机器上,编译Apollo 7.0大概需要40到60分钟,CPU核心少的话可能要两小时以上。中间会有大量日志输出,如果出现红色ERROR不要慌,大多数是可以针对性解决的。

如果你不需要GPU加速跑感知模型,或者宿主机没有NVIDIA显卡,可以执行:

./apollo.sh build

这个版本会把GPU相关的代码如下掉,编译时间稍短一些,但后续感知模型推理就做不到实时了。对于只是验证编译流程、做规划控制方向开发的用户,CPU版其实够用。

Apollo还提供了几个更细分的编译目标:

  • ./apollo.sh build_opt:编译release版本,启用了编译优化,生成的二进制体积更小运行更快。
  • ./apollo.sh build_opt_gpu:release版并启用GPU,适合部署使用。
  • ./apollo.sh build_cpu:纯CPU版本。
  • ./apollo.sh build_teleop:编译远程遥控相关模块。

对于日常开发调试,建议先编译build_gpu,保证所有模块都能正常链接到GPU相关库。编译完成后的输出会放在/apollo/bazel-bin目录下,这个是Bazel的默认输出目录,所有的可执行文件、共享库都会生成在这里。后续启动DreamView或者跑感知模块时,系统默认会从这些目录加载编译产物。

编译完成后可以顺手验证一下编译结果:

ls -l /apollo/bazel-bin/modules/perception/production/

如果能列出感知模块的可执行文件和.so库文件,说明编译基本成功了。

3.4 启动DreamView与运行Demo验证

编译成功只是第一步,更重要的是验证整个平台能正常跑起来。Apollo提供了一个可视化交互界面DreamView,启动方式如下:

在容器内先启动Apollo后台进程:

bash scripts/apollo_neo.sh start

这个脚本会启动Cyber RT框架、DreamView后台服务和需要的依赖模块。启动后看到类似[OK] Apollo is running的提示,说明后台正常。

然后在另一个终端中进入容器,启动DreamView前端:

bash scripts/dreamview.sh start

之后浏览器访问http://localhost:8888,就能看到DreamView的界面了。在界面左侧选择Sim_Control模式,再选择地图(比如Sunnyvale或Borrowdale),加载对应的场景包,就可以开始跑仿真了。

我第一次启动DreamView时遇到过页面打不开的情况,后来发现是8080端口和8888端口问题。Apollo 7.0默认前端端口是8888,如果被占用可以改端口,或者检查容器网络是否映射正确。可以用:

netstat -tlnp | grep 8888

来确认端口是否在监听。

跑通DreamView后,整个源码编译加上环境配置的链路就完全打通了,后续就可以根据自己的需求修改代码,重新通过./apollo.sh build_gpu增量编译需要的模块。

4. 常见问题排查与避坑技巧

4.1 问题速查表

编译和运行过程中遇到的坑,一半以上都是环境问题。我把最常见的整理成一张表,方便直接对照排查:

现象可能原因解决办法
容器内无法显示nvidia-smi没有安装nvidia-container-toolkit或Docker未重启安装toolkit后执行sudo systemctl restart docker
dev_start.sh拉取镜像超时网络问题配置镜像加速或者挂代理(仅用于拉取镜像),反复重试
编译时提示disk quota exceeded磁盘空间不足清理Bazel缓存或源码目录,至少预留80GB
编译时g++: internal compiler error: Killed内存不足,编译进程被OOM杀死减少并行编译任务数:./apollo.sh build_gpu --config=opt --jobs=4
Bazel下载依赖失败网络无法访问部分源码仓库多试几次,或者提前下载依赖包放到/apollo/.cache目录
运行dev_into.sh报容器不存在dev_start没成功或容器被删除重新执行dev_start.sh,并确认没有同名容器残留
编译后启动DreamView页面空白前端端口未映射或资源包未下载检查Docker端口映射-p 8888:8888,用--map参数重新dev_start

4.2 网络问题的处理心得

Apollo源码编译过程中最大变数其实是网络。Bazel在编译时会根据WORKSPACE文件去下载各种外部依赖,包括boost、eigen、opencv、protobuf等源码或二进制包。这些依赖托管在GitHub、Maven、Bazel中央仓库等多个地方,国内网络环境下载这些文件经常出现超时。

我的经验是分几步处理:

第一,先配置Bazel的下载镜像。可以设置环境变量HTTP_PROXY和HTTPS_PROXY指向可用的内网代理,或者直接修改/etc/hosts,把GitHub相关的域名IP解析到更快的地址。对于团队开发场景,更推荐在内网搭建一个Bazel缓存服务,统一管理依赖下载,能省掉每个人重复踩坑的时间。

第二,利用Bazel的分布式缓存。如果之前成功编译过,编译中间产物会缓存在~/.cache/bazel目录。重装系统或换机器后,可以把这个缓存目录整体拷贝到新机器上,能跳过大量重复编译。

第三,多试几次。听起来像废话,但Bazel下载失败之后,直接重新执行同样的编译命令,很多情况下能恢复下载并继续。不要一失败就清理缓存,先重跑一次看看。

4.3 关于资源不足与OOM的处理

内存不足导致的编译失败非常典型。错误日志里通常能看到:

g++: internal compiler error: Killed (program cc1plus)

这个Killed就是内存耗尽后系统主动杀掉了编译进程。遇到这种问题,最直接的办法是降低编译并行度。Apollo的编译脚本支持通过--jobs参数控制并行任务数:

./apollo.sh build_gpu --jobs=4

这样同时只跑4个编译任务,内存压力会小很多,代价是编译时间变长。

如果觉得自己机器配置还可以,但依然被杀,可以检查一下是不是Docker容器可用的内存太少。Apollo在dev_start.sh里默认不会对容器做内存限制,但如果你的Docker版本较老,或者自己在启动容器时手动指定了--memory参数,就可能会限制容器内可用内存。检查方式:

docker stats

确认容器内存使用率和限制值,如果限制存在,可以用docker update --memory <value> <container>调整。

4.4 我踩过的坑:GPU环境最隐蔽的问题

说了这么多,最后分享一个很容易被忽略的细节。我在一次重装环境后,驱动也装了,toolkit也装了,nvidia-smi宿主机显示正常,容器内也显示正常,但编译完跑感知模型时发现GPU利用率始终为0,推理速度慢得离谱。

后来排查下来发现是Apollo容器内的CUDA版本和宿主机驱动版本不完全匹配。Apollo 7.0容器内默认CUDA是11.4,它要求宿主机驱动至少是470以上。我当时装的是450驱动,功能上能识别GPU,但CUDA 11.4的运行时在旧驱动上跑不起来,只能走CPU回退。

所以有个简单的经验:如果你用的Apollo版本比较新(7.0以上),宿主机NVIDIA驱动尽量装到470以上,最好直接上nvidia-driver-520或者530,一步到位。驱动版本太低虽然不报错,但性能会打折。

另外还有一个关于环境变量的坑。dev_into.sh进入容器后,如果当前shell没有正确加载Apollo的环境变量,编译时可能会出现找不到cyber模块的情况。解决办法就是每次进入容器后先跑bash setup.sh,这个脚本负责设置APOLLO_ROOT_DIR、LD_LIBRARY_PATH和PYTHONPATH。

我个人在实际操作中的体会是,Apollo源码编译最考验人的不是编译本身,而是环境准备阶段的细节。驱动、Docker、toolkit、磁盘、网络,每个环节都得稳,任何一环出问题都会在容器启动或编译早期暴露出来。但只要把第二步的宿主环境配置仔细做完,后面源码编译和二次开发就会非常顺畅。按照这篇教程的顺序走一遍,基本可以避免绝大多数新手会踩的坑。编译通过只是个开始,后面改代码、加传感器驱动、训练自己的模型,每一步都会比直接拿官方镜像做二次开发要自如得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询