ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Jetson AGX Orin 上 llama.cpp CUDA 编译避坑指南

Jetson AGX Orin 上 llama.cpp CUDA 编译避坑指南 1. 为什么 Jetson AGX Orin 上的 llama.cpp 总在 CUDA 上翻车Jetson AGX Orin 这块板子很有意思。它自带 2048 个 CUDA 核心和 64 个 Tensor Core算力标称 275 TOPS纸面参数比很多桌面级独显都好看。但真正把 llama.cpp 跑起来的人会发现一个尴尬的现实默认编译出来的二进制文件十有八九是纯 CPU 推理GPU 全程围观。你看着nvidia-smi里显存占用纹丝不动token 生成速度卡在个位数心里那个急。问题几乎从来不在 llama.cpp 本身而在于CUDA 环境没有对齐。Jetson 平台和普通 x86 服务器最大的区别是它的 CUDA 工具链是随 JetPack 一起打包的版本被锁死路径也跟标准 CUDA Toolkit 不一样。你在 Ubuntu 上习惯的那套apt install nvidia-cuda-toolkit或者官网下载.run文件的路子在 Orin 上要么装不上要么装上了和系统自带的驱动打架。我前后在三块不同批次的 Orin 上折腾过 llama.cpp 的 CUDA 编译踩的坑基本可以归成三类CUDA 架构号写错导致编译出的 kernel 跑不了、CMake 找不到正确的 nvcc 和 cuBLAS、运行时动态库路径没配好导致加载失败。这三类问题表面症状都是GPU 没被用上但根因完全不同排查方向也完全不一样。这篇内容适合已经在 Orin 上跑通过 CPU 版 llama.cpp、想进一步榨干 GPU 算力的开发者也适合刚拿到 Orin 开发套件、准备做本地大模型推理的工程师。我会把环境对齐的完整链路拆开讲包括 JetPack 版本与 CUDA 版本的对应关系、CMake 配置里每个关键参数为什么这么写、编译产物怎么验证、以及那些文档里不会写但实际会卡住你半天的细节。先说一个反直觉的结论在 Orin 上你不需要单独安装 CUDA Toolkit。JetPack 已经把 CUDA 装好了你要做的是让 llama.cpp 的构建系统找到它而不是再装一遍。很多人第一步就走错了后面全是连锁反应。2. 先搞清楚 JetPack、CUDA、驱动三者的版本咬合关系2.1 JetPack 版本决定了你能用的 CUDA 上限Jetson 平台的软件栈是整体打包的叫 JetPack。每个 JetPack 版本对应一个固定的 CUDA 版本、cuDNN 版本和 L4TLinux for Tegra内核版本。你不能像在 x86 上那样随意升级 CUDA因为 GPU 驱动是跟 L4T 内核绑定的换 CUDA 版本意味着换整个 BSP。截至我写这篇内容时常见的对应关系是这样的JetPack 版本L4T 版本CUDA 版本cuDNN 版本典型 UbuntuJetPack 5.1.x35.x11.48.620.04JetPack 6.036.312.28.922.04JetPack 6.136.412.69.322.04这个表你得先对一遍。用cat /etc/nv_tegra_release看 L4T 版本用nvcc --version看 CUDA 版本。如果nvcc命令找不到说明 CUDA 的 bin 目录没在 PATH 里这本身就是个信号——你的环境变量没配全。我遇到过最典型的情况是板子刷的是 JetPack 6.0CUDA 12.2但用户从网上抄了一份针对 CUDA 11.4 的编译命令里面写了-DCMAKE_CUDA_ARCHITECTURES72。这个架构号是给 Xavier 用的Orin 是 87。架构号写错编译能过但运行时会报no kernel image is available for execution on the device。这个报错很隐蔽因为 llama.cpp 可能只是静默回退到 CPU你甚至看不到报错。2.2 Orin 的 CUDA 架构号是 87不是 86这是个小细节但特别容易错。Jetson AGX Orin 的 GPU 是 Ampere 架构compute capability 是8.7。对应的 CMake 架构号是87。有些人看到 Ampere 就写86那是 RTX 30 系桌面卡的号结果编译出来的 kernel 在 Orin 上跑不了。你可以用一个小命令确认nvcc --list-gpu-arch输出里会列出当前 nvcc 支持的所有架构。如果里面有compute_87说明工具链认识 Orin。然后在 CMake 里写-DCMAKE_CUDA_ARCHITECTURES87注意不要写8.7或compute_87CMake 的CMAKE_CUDA_ARCHITECTURES只认纯数字87。写错了 CMake 会报错或者静默忽略。2.3 驱动版本和 CUDA 运行时的兼容性检查JetPack 自带的驱动版本是固定的但 CUDA 运行时cudart有向前兼容的机制。理论上 CUDA 12.x 的运行时需要驱动版本 525JetPack 6.0 的驱动是 540 系列没问题。但如果你手动装过别的驱动就可能出现版本倒挂。检查方法cat /proc/driver/nvidia/version这个命令在 Jetson 上可能没有输出因为 Tegra 的驱动不是标准 nvidia 驱动模块。更可靠的方式是看dpkg -l | grep nvidia-l4t-cuda输出里会显示nvidia-l4t-cuda的版本号这个版本号跟 CUDA 版本是对应的。如果这个包不存在说明你的 JetPack 刷机不完整需要重新刷。3. 让 CMake 精准找到 Orin 上的 CUDA 工具链3.1 环境变量PATH 和 LD_LIBRARY_PATH 的正确写法JetPack 装完后CUDA 的路径是/usr/local/cuda它是个软链接指向/usr/local/cuda-12.2这样的实际目录。但默认情况下这个路径不在普通用户的 PATH 里。你需要手动加。我习惯在~/.bashrc里加这几行export CUDA_HOME/usr/local/cuda export PATH$CUDA_HOME/bin:$PATH export LD_LIBRARY_PATH$CUDA_HOME/lib64:$LD_LIBRARY_PATH export CUDACXX$CUDA_HOME/bin/nvcc这里有几个点值得说。CUDACXX这个变量是给 CMake 用的告诉它 nvcc 在哪。有些 CMake 版本对CUDA_HOME不敏感但对CUDACXX很敏感。两个都设上最保险。LD_LIBRARY_PATH里加lib64是因为 CUDA 的运行时库libcudart.so、libcublas.so都在这个目录。llama.cpp 编译出来的可执行文件在运行时需要加载这些库如果路径不对会报error while loading shared libraries: libcudart.so.12: cannot open shared object file。提示改完.bashrc后记得source ~/.bashrc或者新开一个终端。我见过有人改完直接在当前终端跑编译结果环境变量没生效白折腾半小时。3.2 CMake 配置命令的逐参数拆解llama.cpp 的 CMake 配置项比较多针对 Orin 的 CUDA 编译核心是这几个cmake -B build \ -DGGML_CUDAON \ -DCMAKE_CUDA_ARCHITECTURES87 \ -DCMAKE_CUDA_COMPILER$CUDA_HOME/bin/nvcc \ -DCMAKE_BUILD_TYPERelease \ -DLLAMA_CURLOFF逐个说。-DGGML_CUDAON是总开关。llama.cpp 现在把后端抽象成了 ggmlCUDA 支持是通过 ggml 的 CUDA 后端实现的。这个选项打开后CMake 会去找 CUDA 工具链找不到就直接报错不会静默回退。-DCMAKE_CUDA_ARCHITECTURES87前面说过了Orin 专用。-DCMAKE_CUDA_COMPILER显式指定 nvcc 路径。有时候系统里装了多个 CUDA 版本比如你之前手动装过一个CMake 可能找到错的那个。显式指定最稳。-DCMAKE_BUILD_TYPERelease开优化。Debug 模式下 CUDA kernel 的性能会差很多而且编译出来的二进制体积大。-DLLAMA_CURLOFF是关掉 curl 依赖。llama.cpp 默认会尝试链接 libcurl 来支持从 URL 加载模型但 Orin 上不一定装了 libcurl 的开发包关掉能省事。如果你确实需要这个功能就先sudo apt install libcurl4-openssl-dev。3.3 编译过程中最常见的三个报错及处理报错一No CMAKE_CUDA_COMPILER could be found这说明 CMake 没找到 nvcc。先确认nvcc --version能跑通。如果跑不通就是 PATH 没配好。如果跑得通但 CMake 还是找不到就是CUDACXX没设。补上再试。报错二nvcc fatal: Unsupported gpu architecture compute_87这说明你的 nvcc 版本太老不认识 87 这个架构。JetPack 5.1 的 CUDA 11.4 是支持 87 的但如果你的 nvcc 是更老的版本比如 CUDA 10.x就不支持。这种情况只能升级 JetPack。报错三cannot find -lcublascuBLAS 库找不到。检查/usr/local/cuda/lib64下有没有libcublas.so。如果没有说明 JetPack 的 CUDA 组件没装全。用sudo apt install nvidia-l4t-cuda补装。编译命令本身cmake --build build --config Release -j$(nproc)-j$(nproc)用满所有 CPU 核心。Orin 的 CPU 是 12 核 Cortex-A78AE编译 llama.cpp 大概需要 10 到 15 分钟。如果内存不够Orin 有 32GB 和 64GB 两个版本可以适当减少并行数比如-j8。4. 验证 GPU 是否真正参与推理的完整链路4.1 编译产物的静态检查编译完成后先别急着跑模型。用ldd看一下可执行文件链接了哪些库ldd build/bin/llama-cli | grep cuda如果输出里有libcudart.so.12、libcublas.so.12这些说明 CUDA 链接成功了。如果什么都没有说明编译时 CUDA 没生效回去检查 CMake 配置。再用cuobjdump看一下二进制里有没有 Orin 的 kernelcuobjdump --list-elf build/bin/llama-cli | grep 87有sm_87的输出就对了。4.2 运行时用 nvidia-smi 和 tegrastats 双重确认跑推理的时候开两个终端。一个跑 llama.cpp另一个跑watch -n 1 nvidia-smi在 Jetson 上nvidia-smi的输出跟 x86 不太一样它显示的是 GPU 利用率和显存占用。如果显存占用上去了比如加载 7B 模型后显存多了 4GB 左右说明 GPU 在工作。另一个工具是tegrastats这是 Jetson 特有的tegrastats --interval 1000输出里会有一项GR3D_FREQ这是 GPU 的频率。如果推理时这个频率从 0% 跳到 90% 以上说明 GPU 在干活。如果一直是 0%那就是 CPU 在扛。4.3 一个容易忽略的点显存分配策略Orin 是统一内存架构CPU 和 GPU 共享物理内存。这意味着 llama.cpp 加载模型时如果用了--n-gpu-layers参数把层放到 GPU 上这些层占用的内存是从共享内存池里划的。Orin 64GB 版本还好32GB 版本就要注意了。我一般会留出至少 8GB 给系统和其他进程。跑 7B 的 Q4_K_M 量化模型大概需要 4.5GB 显存加上 KV cache 和上下文开销总共 6GB 左右。32GB 版本跑 13B 模型就比较紧张了。用--n-gpu-layers控制放多少层到 GPU。Orin 上我实测 7B 模型可以全部放上去-ngl 9913B 模型放 30 到 35 层比较稳。具体数字取决于你的量化方式和上下文长度。5. 性能调优从能跑到跑得快的几个关键参数5.1 batch size 和 ubatch size 的取舍llama.cpp 有两个 batch 参数-bbatch size和-ubmicro batch size。在 Orin 上这两个参数的设置跟显存和算力都有关系。-b是逻辑上的批大小影响 prompt 处理阶段的并行度。-ub是实际提交给 GPU 的微批大小影响显存占用。默认-b 2048 -ub 512。我的经验是Orin 上-b 1024 -ub 256比较平衡。调大-b能加快 prompt 处理但显存占用会上升。调大-ub能提高 GPU 利用率但超过一定值后收益递减。你可以用同一个 prompt 跑几次看llama-cli输出的prompt eval time和eval time。前者是 prompt 处理速度后者是 token 生成速度。Orin 上 7B Q4 模型prompt 处理大概能到 200 tokens/s生成大概 30 到 40 tokens/s。如果生成速度只有个位数说明 GPU 没吃满。5.2 flash attention 在 Orin 上的实际效果llama.cpp 支持-fa参数开启 flash attention。这个特性在 Orin 上是有收益的尤其是长上下文场景。开启后KV cache 的显存占用会降低生成速度也有提升。但要注意flash attention 对 CUDA 架构有要求。Orin 的 sm_87 是支持的但如果你编译时架构号写错了开了-fa可能会报错或者结果不对。所以还是那句话架构号一定要写对。实测数据7B Q4 模型2048 上下文开-fa后生成速度从 32 tokens/s 提升到 38 tokens/sKV cache 显存占用减少约 30%。5.3 线程数设置不要无脑用 nprocllama.cpp 的-t参数控制 CPU 线程数。很多人习惯-t $(nproc)但在 Orin 上这不是最优的。因为 GPU 推理时CPU 主要负责调度和数据搬运不需要那么多线程。线程开太多反而会增加上下文切换开销。我一般设-t 6或-t 8。Orin 有 12 个核心留几个给系统和其他进程。你可以自己试从 4 开始往上加看生成速度什么时候不再提升。6. 那些文档里不会写的踩坑记录6.1 电源模式没切到最大性能Jetson 默认的电源模式是 15W 或者 30WGPU 频率被限制。跑推理之前先切到最大性能模式sudo nvpmodel -m 0 sudo jetson_clocksnvpmodel -m 0是最大性能模式jetson_clocks把 CPU 和 GPU 频率锁到最高。不切的话GPU 频率可能只有一半推理速度直接打对折。这个坑我踩过。当时编译什么都对但生成速度就是上不去后来发现是电源模式没切。切完之后速度直接翻倍。6.2 散热问题导致的降频Orin 开发套件的散热器是标配的但如果你把它塞在密闭机箱里或者环境温度高GPU 会降频。用tegrastats看温度如果tj超过 85 度就会触发降频。我一般会加一个风扇对着散热片吹。或者用sudo jetson_clocks --fan把风扇转速拉满。如果是工业场景就得考虑主动散热方案了。6.3 模型文件格式和量化方式的影响llama.cpp 支持 GGUF 格式量化方式有很多种。在 Orin 上Q4_K_M 是性价比最高的。Q5_K_M 质量更好但速度慢一些Q3_K_M 速度快但质量下降明显。我试过 Q8_0质量最好但显存占用翻倍7B 模型要 8GB 多32GB 版本跑起来就很紧张了。Q4_K_M 在质量和速度之间平衡得最好。另外模型文件要放在 SSD 上不要放在 SD 卡上。SD 卡的读取速度会成为加载模型的瓶颈。Orin 的 M.2 接口可以插 NVMe SSD加载速度比 SD 卡快好几倍。6.4 多模型切换时的显存碎片问题如果你在一个进程里反复加载和卸载模型可能会遇到显存碎片。表现是明明显存够但加载新模型时报 OOM。解决办法是每次切换模型时重启进程或者用--no-mmap参数让 llama.cpp 自己管理内存分配。--no-mmap会禁用内存映射模型加载时会一次性读入内存加载慢一点但显存管理更干净。在 Orin 这种统一内存架构上这个参数有时候能解决一些玄学问题。7. 一个完整的可复现流程把上面的内容串起来从零到跑通完整流程是这样的第一步确认 JetPack 版本和 CUDA 版本cat /etc/nv_tegra_release nvcc --version第二步配置环境变量写入~/.bashrcexport CUDA_HOME/usr/local/cuda export PATH$CUDA_HOME/bin:$PATH export LD_LIBRARY_PATH$CUDA_HOME/lib64:$LD_LIBRARY_PATH export CUDACXX$CUDA_HOME/bin/nvcc第三步克隆 llama.cpp 并配置git clone https://github.com/ggerganov/llama.cpp cd llama.cpp cmake -B build \ -DGGML_CUDAON \ -DCMAKE_CUDA_ARCHITECTURES87 \ -DCMAKE_CUDA_COMPILER$CUDA_HOME/bin/nvcc \ -DCMAKE_BUILD_TYPERelease \ -DLLAMA_CURLOFF第四步编译cmake --build build --config Release -j8第五步切电源模式sudo nvpmodel -m 0 sudo jetson_clocks第六步跑推理./build/bin/llama-cli \ -m /path/to/model.gguf \ -ngl 99 \ -t 6 \ -b 1024 \ -ub 256 \ -fa \ -p 你的提示词第七步另开终端验证 GPU 占用tegrastats --interval 1000看GR3D_FREQ是否上去了。这套流程我在三块 Orin 上跑过JetPack 5.1 和 6.0 都验证过只要版本对应关系没错基本一次过。最容易出问题的环节是环境变量和架构号这两个地方多检查一遍能省很多时间。最后分享一个我自己的习惯每次编译前先跑一遍nvcc --version和echo $CUDACXX确认环境没问题再动手。这个习惯帮我省过好几次因为终端会话切换导致环境变量丢失的麻烦。Orin 上的 CUDA 环境对齐说到底就是让构建系统找到正确的工具链然后把正确的架构号传进去剩下的就是耐心等编译完成。
返回列表