自制C和C++引擎:小体积文件如何与成熟CUDA栈打成平手?

📅 2026/8/4 9:24:12 👁️ 阅读次数 📝 编程学习
自制C和C++引擎:小体积文件如何与成熟CUDA栈打成平手?

1. 为什么要自己编写C和C++引擎?

一个66 MiB的二进制文件,而非9.1 GiB的虚拟环境;深度估计在CPU上以一半的内存击败PyTorch;生物识别与insightface完全匹配。大多数LocalAI后端是对其他引擎的封装,但有18个后端是从头开始编写的C或C++移植版本,每个移植版本的存在是因为封装上游引擎会带来一些无法接受的问题,如需要安装数GB的Python环境、使用不可移植的仅支持CUDA的栈,或者模型根本没有C++实现。

2. 你能获得什么?

部署Python推理栈需根据其CUDA和glibc版本解析依赖树,而部署ggml移植版本,只需复制一个共享库和一个GGUF文件。vllm.cpp是对vLLM的V1服务架构进行C++20移植的成果,安装vLLM会生成一个9.1 GiB的虚拟环境,而安装vllm.cpp只会生成一个66 MiB的二进制文件,且该引擎实现了与Python原版相同的功能,推理时无需Python、PyTorch和ggml。

在运行NVFP4格式Qwen3.6 - 27B模型的NVIDIA GB10上测试,vllm.cpp在所有六个测试点上的表现都更优,其中五个点的差距较小,几乎可视为平局。vllm.cpp的峰值主机内存为24.88 GiB,而vLLM为28.18 GiB。与从同一GGUF文件在CPU上运行的llama.cpp相比,vllm.cpp的预填充速度快1.18倍,解码速度与llama.cpp相当;在Apple M4上与MLX - LM相比,vllm.cpp预填充到第一个令牌的时间快1.5%,预热后的总吞吐量达到MLX - LM的97.6%,差距为2.4%,主要体现在解码阶段。

3. 有时移植版本就是更快?

depth - anything.cpp是对字节跳动的Depth Anything 3的移植版本,能从一张普通照片中得出以米为单位的深度信息等。在CPU上,它比运行相同模型的PyTorch更快。在配备16线程的Ryzen 9 9950X3D上,以504x336分辨率进行测试,相同模型下,depth - anything.cpp的速度是PyTorch的1.31倍,内存使用仅为27%,加载时间从749 ms缩短至40 ms。量化后的q4_k版本文件大小为99 MB,且几乎无损。经过37次一致性测试,输出结果与参考前向传播逐组件相关系数为1.0。

它更快的原因主要在于缓存了两个位置嵌入,减少了约95 ms的主机端开销,而PyTorch使用向量化操作构建相同的嵌入,没有这部分开销。在GPU上,使用ggml CUDA后端和在GB10上的Flash Attention,depth - anything.cpp与PyTorch调优后的cuDNN每次前向传播时间均为47.3 ms,仅在冷启动时更快,加载速度是PyTorch的1.75到2.9倍。

4. 一致性是基础,速度是后续追求?

face - detect.cpp和voice - detect.cpp分别取代了LocalAI的Python `insightface`和`speaker - recognition`后端。face - detect.cpp可以运行整个insightface buffalo流程,且无需Python和onnxruntime,所有功能都集成在一个自包含的GGUF文件中。检测器框和特征点与insightface的误差在1像素以内,识别嵌入的余弦相似度为1.000000,且在任何线程数下都能保持。在CPU上,它比onnxruntime慢;在GPU上,通过cuDNN进行相同卷积操作,SCRFD从14.8 ms缩短至6.4 ms,达到与torch - cuDNN相当的水平。

voice - detect.cpp在内存使用上有显著改善,二进制文件中WeSpeaker验证的峰值内存约为62 MB,而仅使用CPU的Python、torch和onnxruntime路径约为334 MB,前者约为后者的五分之一,且验证结果和嵌入余弦相似度为1.000000。在CPU上,端到端性能两者相差10%到15%,根据模型和线程数不同,互有优劣;在GPU上,卷积编码器与参考实现相当。对于生物识别管道来说,与参考实现完全匹配比速度更快更重要。

5. 实现方法是什么?

每个移植版本都遵循相同的步骤,且步骤的顺序至关重要。首先转换权重,将权重转换为一个包含分词器、词汇表和任何辅助模型的GGUF文件;其次移植计算图,并逐组件与原始实现中导出的参考张量进行对比;然后进行优化,使用性能分析工具,且仅在确保一致性后进行;最后暴露扁平C ABI,LocalAI通过purego动态加载共享库并直接调用该ABI。

6. 付出的代价是什么?

主要是维护成本,每个引擎都是一个独立的仓库,有自己的持续集成(CI)、基准测试套件、GGUF转换脚本和一致性基线,而且上游不断发布新的检查点,需要进行转换工作。GPU内核是薄弱环节,ggml的通用CUDA卷积和注意力内核在卷积密集型模型上落后于NVIDIA调优后的cuDNN。移植也并非适用于所有情况,只有当模型没有C++实现、Python依赖比模型本身更重,或者所需功能尚不存在时,才会编写自己的引擎。

7. 可以立即尝试吗?

可以在正在阅读本文的机器上运行LocalAI,它可以作为容器、二进制文件、macOS DMG或Helm图表进行安装,并且在模型首次请求后端时会自动拉取。