我叫陈浩,是合肥工业大学数学专业的学生,平时对 AI 软件栈、RISC-V等方向比较感兴趣。大二暑假,我参加了甲辰计划,成为了 llama.cpp 和 LLVM 项目的贡献者。这也是我第一次真正走进开源社区。
我的甲辰计划实习记录
在放暑假之前,吴伟老师来我们学校宣讲甲辰计划和开源生态,也提及了一些AI对就业的影响。
现如今AI的迅速发展,对就业市场提出了更严苛的要求。但对普通学生来说:招聘看重"相关项目经历",可实习又往往要求你有实习经历,多少有点鸡生蛋、蛋生鸡,不知从何开启。而且学校里教的东西和企业实际在用的,很多时候也隔着一条看不见的河。
而甲辰计划提供的,恰好是这样一个真正能让学生们参与进去的平台——不要求你带着项目经历进来,而是给你一个从零开始接触真实项目的机会。
我参加的是线上实习。之后吴老师推荐了一些方向,我个人对 AI 软件栈这个方向比较感兴趣。于是他建议先学一下 MIT 的《计算机教育中缺失的一课》和其他几门RISC-V的相关课程。虽然只是浅尝辄止,但学习这些开发需要的基本工具对我的帮助还是很大的。
一两周后,我询问吴老师下一步的建议,他让我先别急着写代码,先看看 K3 的芯片技术手册和llama.cpp 里 RISC-V 向量(RVV)相关的PR等等相关的事情。
llama.cpp是个很大的项目,我也没想一开始就能理解,于是从最简单的地方上手:读代码、部署运行、把测试跑通。部署的过程并不是如想象的一帆风顺,往往会报出一些意想不到的错误。我也逐渐感受到了一种理论和现实的差距,对整个软件的生态产生了一种敬畏之心。
我起初跑了正确性的一些测试,然后想去测性能,但是我发现QEMU只是一个功能模拟,测不出具体的性能——我缺一个真实的硬件。
于是我去找吴老师申请,然后他把我拉到一个SpaceMiT交流群,让我自己申请。后来我成功申请到spacemit官方的一个K3 云实例。
有了真机之后,能测的东西就变多了,当然问题也接踵而至:TCM。它是一块紧耦合内存,AI 核会大量使用,跑测试时线程数开大一点,程序就直接 abort。
一开始我在spacemit社区看到说似乎对性能影响不大,于是我一开始是用SPACEMIT_DISABLE_TCM=1关闭它先绕过去。后来翻到一个llama的文档才发现这个问题官方知道而且早就有解决方案了:TCM反复崩溃后,这块内存不会自动释放,之后所有任务都会卡死,用spacemit-tcm-smi清一遍就好了。
我怀疑是驱动和系统适配出了问题,但是其实答案文档里就讲清楚了该如何处理。
然后我把各种量化格式的模型挨个跑一遍,看看基准数据。
跑到Q4_K_M时,发现它慢得异常。一开始我以为是性能问题,就开始查。查了半天没找到原因,但是发现它的对话输出是乱码。
我看到乱码的瞬间,脑子里就冒出个想法:这很有可能是个 bug!
顺着链路排查下去,最后定位到SpaceMiT 后端的派发表。派发表里其余量化类型都有分支,唯独缺了GGML_TYPE_Q5_0。Q4_K_M属于混合量化格式,内部刚好包含Q5_0分片,所以才触发了缺失分支。(这里提到的派发表(dispatch table),简而言之就是程序拿到一个量化类型,然后通过这张表找到对应的函数实现,再跳转过去,是一种常见的「按值分发」手法,在这里说白了就是一个 switch case 的分支语句。)
case GGML_TYPE_Q5_0:
碰巧的是,这个格式后面的计算逻辑其实已经写好了,只差这一句把它接上。说来挺诧异的——这么庞大的一个项目,居然会漏掉这么小的一处,就好像是提前排好了,专门等着有缘人来发现它。
之后补上重编再跑就不乱码了,速度也正常了。我将这段代码修改发起了一个 Pull Request (PR)提交给了 llama.cpp 上游仓库(PR26792)并得到了合入。这次奇妙的经历打开了我成为知名开源项目贡献者的大门。原来成为开源贡献者一点儿也不难,只要动手实践了,就能发现RISC-V生态里很多低垂的果实。

https://github.com/ggml-org/llama.cpp/pull/26792
跑了一段时间后,我想尝试改进源码提升性能。我在 VS Code 里装了 clangd,但看某个文件时,扩展在后台反复退出:
The Clang Language Server server crashed 5 times in the last 3 minutes.
The server will not be restarted.
我以为是自己使用得不恰当。后来重启扩展换文件查看,发现只有这个文件会崩,然后发现可能是一个bug。于是把最新的源码重新编译了一下运行,还是有问题。
紧接着我把崩溃代码一点点删掉,最后缩到六行:
<map>
std::map<const Incomplete *, int> m;
struct Incomplete;
void f(Incomplete *p) { m[p]; }
问题出现了:一个只有前置声明、还没定义的类型被拿来当 map 的 key,再对它的指针做下标运算。clang-tidy 检查时直接去问它:"你是多态的吗?"——可这个类型连定义都没有,这个问题本来就不该被问出来。
判断之前先确认它有定义就可以了。
AST_MATCHER(CXXRecordDecl, isPolymorphic) {
return Node.hasDefinition() && Node.isPolymorphic();
}
类似的地方有两处,另一处也改掉了,最后再加上一个回归测试。
我将这些发现和对应的修改补丁 PR215724,提交到了 Clang-tidy 所在的主仓库,也就是现在(几乎)所有 AI Infra 的编译器基础设施:LLVM monorepo。

https://github.com/llvm/llvm-project/pull/215724/files
提交上去之后就是等待和来回修改:评审说注释不恰当,文档没写,我就是按要求修改。最后让我填 Release Note,CI 又报了个错——不过只是字母顺序。几轮修订之后,我的第一笔 Clang-tidy / LLVM 仓库提交被合入了!
回头看,我最后交出去的真实两处很小很小的 bug 修复;但这段经历带给我自己的东西,远远不止代码本身。
做项目的过程里,我经常读官方文档、翻看上游源码、学习使用AI和其他工具,整个过程缺什么补什么。项目太大,想读完根本不现实,也不存在真正"读懂了"的那一天。保持学习、慢慢沉淀,大概才是常态。这种终身学习的思想我觉得是一种很大的收获。而通过甲辰计划实习,第一次让我感受到了,学习开源和贡献开源是可以同时进行的。
还有一个很深的体会就是沟通与协作。很多资源不会自己送到面前。无论是K3 云实例的申请还是提交 PR 后和维护者来回修改都得主动开口,把自己的需求、发现的bug说清楚。如何跟其他开发者协作、怎么把问题讲明白是一件很重要的事情。
也是在这个过程中,我第一次真正体会到开源精神。社区里很多维护者都是志愿者,但是却把代码和技术开放出来,让后来的人能够站在前人的基础上继续走。
受这份精神感染,我也搭了个博客 imhaochen.com,把这次的测试数据整理公开。数据可能有些偏差,测试谈不上多严谨,但希望能给同方向的学习者一点参考。另外,我也试着写了些自用的小工具,现在大概只能服务自己,以后打磨好了,也许也能分享给社区。
我也明白,现阶段的我无法改动项目的架构,只能从很小的补丁入手。但成长本来就是慢慢来的:先把小事做好,能力一点点攒起来,以后才有机会去碰更深、更复杂的东西。
很感谢甲辰计划给我的机会,让我能真正踏进开源世界,亲眼看看一个庞大的社区是怎么运转的。感谢进迭时空提供给我免费的K3访问资源。
这段经历在我心里种下了一颗种子:不必等自己足够厉害才开始贡献,力所能及的分享和改进,本身就有价值。
往后,我会沿着底层技术这条路继续走,保持耐心,慢慢探索。
本文提到的部分PR链接:
https://github.com/llvm/llvm-project/pull/215724
https://github.com/ggml-org/llama.cpp/pull/26792

