1. 当"手摇"遇上大模型:CrankGPT 到底在解决什么问题
第一次看到 CrankGPT 这个名字,我脑子里蹦出来的画面是一台老式手摇咖啡磨豆机,只不过磨的不是豆子,是 token。这个项目的全称是 "hand powered offline physical AI box",翻译过来就是"手摇供电的离线物理 AI 盒子"。核心思路非常直接:把一个大语言模型塞进一台完全离线的物理设备里,用人力手摇发电来给它供电,整个过程不依赖任何网络连接。
你可能会问,这年头云端 API 调用又便宜又快,为什么要折腾一个手摇的离线盒子?这个问题我在动手之前也反复问过自己。答案其实藏在几个很实际的场景里。第一是隐私敏感场景——有些内容你根本不想让它经过任何外部服务器,哪怕对方承诺不记录。第二是极端环境——断网、断电、没有信号的地方,你依然需要一个能对话、能查资料、能帮你理清思路的工具。第三,也是最容易被忽略的一点:手摇供电本身就是一种物理层面的"节奏控制器"。你摇得越久,越会珍惜每一次提问,这种约束反而让交互变得更有目的性。
CrankGPT 适合什么人参考?我觉得有三类。一类是喜欢把软件和硬件揉在一起玩的创客,手里有树莓派、有 3D 打印机、有一堆传感器但不知道怎么串起来。第二类是对本地大模型部署感兴趣、想找一个"有物理形态"的落地项目的开发者。第三类是想给孩子的科技教育做一个"看得见摸得着"的 AI 演示装置的家长或老师——一个需要用手摇才能说话的 AI,比手机里那个随叫随到的语音助手有意思得多。
这篇文章我会把 CrankGPT 从选型、供电、模型部署、交互设计到实测踩坑的完整链路拆开讲。所有内容基于这个项目的核心逻辑展开,涉及具体参数和步骤的地方,我会说明背后的计算和取舍理由。你不需要有很深的嵌入式背景,但最好对 Linux 命令行和 Python 有一点基本概念。
2. 硬件选型:为什么是树莓派而不是更省电的单片机
2.1 算力和功耗之间的那条线
做离线 AI 盒子,第一个要面对的矛盾就是:能跑大模型的芯片,功耗都不低;功耗低的芯片,跑不动大模型。这是一个硬约束,绕不过去。
先看几个候选方案的实际数据。我用功率计实测过几块常见开发板在跑推理时的功耗:
| 平台 | 空闲功耗 | 推理峰值功耗 | 能否跑 3B 量化模型 | 备注 |
|---|---|---|---|---|
| Raspberry Pi 5 (8GB) | 约 2.7W | 约 8-10W | 可以,7B Q4 也能勉强跑 | 首选 |
| Raspberry Pi 4B (4GB) | 约 2.2W | 约 6-7W | 3B 流畅,7B 很吃力 | 可用 |
| ESP32-S3 | 约 0.3W | 约 1W | 只能跑极小模型 | 算力不够 |
| Jetson Nano | 约 3W | 约 10W | 可以,但贵 | 性价比低 |
从这张表能看出来,树莓派 5 是一个比较甜的平衡点。它的峰值功耗在 10W 左右,而一个成年人用手摇发电机稳定输出的功率大概在 5-15W 之间(取决于你摇得多卖力、发电机的效率)。这意味着理论上一个人持续手摇是可以喂饱树莓派 5 的,虽然会比较累。
这里有个关键的计算。假设你用的是常见的 12V 手摇发电机,经过 DC-DC 降压到 5V 给树莓派供电。发电机的转换效率按 70% 算,树莓派 5 满载需要 10W,那么你手摇需要输出的机械功率大约是 10 / 0.7 ≈ 14.3W。一个普通人悠闲地摇大概能出 5-8W,认真摇能到 15-20W。所以结论是:轻负载(比如只跑 3B 模型、屏幕亮度调低)可以边摇边用,重负载(7B 模型满速推理)需要先摇着给电池充一会儿电再跑。
2.2 为什么必须加一块电池做缓冲
直接把手摇发电机接到树莓派上,是我踩过的第一个大坑。原因很简单:人手摇的转速是不稳定的。你摇得快一点电压就高,慢一点电压就掉,而树莓派对供电电压的波动非常敏感,电压一掉到阈值以下就直接重启,你辛辛苦苦加载到内存里的模型全没了。
解决办法是加一块电池或者超级电容做缓冲。我的方案是用一块 5V/3A 输出的移动电源模块(带 BMS 保护板的那种),手摇发电机先给电池充电,电池再稳定地给树莓派供电。这样即使你手停下来歇一会儿,树莓派也能靠电池继续跑几分钟,足够你保存状态或者把当前这轮对话看完。
提示:电池容量不用太大,5000mAh 左右就够。太大了充电慢,手摇要摇很久才能充满,体验反而差。关键是边充边放的能力,也就是所谓的 pass-through 充电,选移动电源模块的时候一定要确认支持这个功能。
2.3 屏幕、输入和外壳的取舍
屏幕我选的是 3.5 寸的 SPI 接口 TFT 屏,分辨率 480x320。为什么不选更大的?因为屏幕是耗电大户,3.5 寸的功耗在 0.5W 左右,7 寸的能到 1.5W 以上,对总功耗预算影响很大。而且小屏幕逼着你把界面做得极简,反而更符合这个项目的调性。
输入方式上我纠结了很久。触摸屏方便但增加功耗和驱动复杂度;物理按键简单可靠但输入文字很痛苦。最后的方案是:用几个物理按键做菜单导航和快捷操作,文字输入通过一个可选的 USB 小键盘。日常对话用预设的几个 prompt 模板就够了,需要自由输入的时候再插键盘。
外壳用 3D 打印,这个没什么好说的。唯一要注意的是散热——树莓派 5 满载跑推理的时候芯片温度能到 70 度以上,外壳一定要留散热孔,最好加一个小风扇(又是 0.2W 的功耗,心疼)。我第一版外壳做得太封闭,跑十分钟就降频了,后来在顶部开了一排栅格孔才解决。
3. 离线推理引擎:llama.cpp 在树莓派上的调优实录
3.1 为什么选 llama.cpp 而不是别的
离线跑大模型,可选的路子其实不少:llama.cpp、Ollama、MLC-LLM、还有各种 Python 推理框架。我最后选 llama.cpp,理由有三条,都是实测出来的。
第一是资源占用。llama.cpp 是纯 C++ 写的,没有 Python 运行时那一堆依赖,内存占用比基于 PyTorch 的方案小一大截。在 8GB 内存的树莓派 5 上,这个差距可能就是"能跑 7B"和"只能跑 3B"的区别。
第二是量化支持成熟。llama.cpp 的 GGUF 量化格式支持从 Q2 到 Q8 的各种精度,还有 K-quants 和 I-quants 这些更省内存的方案。对于内存紧张的嵌入式设备,量化是刚需。
第三是ARM 优化到位。llama.cpp 对 ARM 的 NEON 指令集有专门优化,在树莓派这种 ARM 平台上,同样的模型它能比通用框架快不少。
Ollama 其实底层也是 llama.cpp,但它多了一层服务封装,对树莓派来说这层封装带来的内存开销不太划算。如果你追求极致的资源利用率,直接用 llama.cpp 更合适。
3.2 编译 llama.cpp 时那几个必须打开的开关
从源码编译 llama.cpp 是绕不过去的一步,因为预编译的二进制包通常没有针对你的具体硬件做优化。下面是我在树莓派 5(64 位系统)上用的编译命令:
git clone https://github.com/ggerganov/llama.cpp cd llama.cpp cmake -B build \ -DCMAKE_BUILD_TYPE=Release \ -DLLAMA_NATIVE=ON \ -DLLAMA_NEON=ON \ -DLLAMA_OPENMP=ON \ -DLLAMA_BLAS=ON \ -DLLAMA_BLAS_VENDOR=OpenBLAS cmake --build build --config Release -j4逐个解释这些开关的意义。LLAMA_NATIVE=ON让编译器针对当前 CPU 生成最优指令,这个在树莓派上很重要,因为不同代的树莓派 CPU 特性不一样。LLAMA_NEON=ON打开 ARM 的 SIMD 指令加速,这是性能提升的大头。LLAMA_OPENMP=ON启用多线程,树莓派 5 是四核,多线程能明显提升 prompt 处理速度。LLAMA_BLAS=ON配合 OpenBLAS 能加速矩阵运算,但需要先sudo apt install libopenblas-dev。
编译过程在树莓派 5 上大概要 15-25 分钟,-j4是四核并行编译。编译完之后,build/bin/目录下会有一堆可执行文件,我们主要用llama-cli(命令行交互)和llama-server(提供 API 服务)。
注意:如果你用的是树莓派 4B,把
-j4改成-j3或者-j2,因为 4B 的散热和供电都比较紧张,四核全开编译容易过热降频甚至死机。我有一块 4B 就是在编译的时候过热重启了,白等二十分钟。
3.3 模型量化的选择:Q4_K_M 是那个甜点
模型选的是 Qwen2.5-3B-Instruct,量化到 Q4_K_M。为什么是这个组合?
先说模型大小。3B 参数在 Q4 量化下,模型文件大概 2GB 左右,加载到内存后占用约 2.5-3GB。树莓派 5 有 8GB 内存,系统本身占 1GB 左右,留给模型和运行时的空间很充裕。如果你上 7B 模型,Q4 量化后文件约 4.5GB,加载后内存占用 5-6GB,系统会开始用 swap,速度断崖式下跌。所以在树莓派 5 上,3B 是流畅体验的上限,7B 是能跑但很慢的下限。
再说量化精度。llama.cpp 支持的量化方案里,Q4_K_M 是社区公认的性价比之王。K 代表 K-quants,是一种更聪明的量化方法,M 代表 medium,介于 Q4_K_S(small)和 Q4_K_L(large)之间。实测下来,Q4_K_M 相比原始的 FP16 模型,质量损失很小,但内存占用只有四分之一左右。
| 量化方案 | 文件大小(3B) | 内存占用 | 推理速度 | 质量损失 |
|---|---|---|---|---|
| FP16 | 约 6GB | 约 7GB | 基准 | 无 |
| Q8_0 | 约 3.2GB | 约 4GB | 较快 | 极小 |
| Q4_K_M | 约 2GB | 约 2.8GB | 快 | 小 |
| Q3_K_M | 约 1.6GB | 约 2.3GB | 很快 | 中等 |
| Q2_K | 约 1.2GB | 约 1.8GB | 最快 | 明显 |
从这张表能看出来,Q4_K_M 在文件大小、内存占用和速度之间找到了一个很好的平衡。Q3 和 Q2 虽然更省资源,但质量损失开始变得明显,回答会出现逻辑混乱和重复。对于 CrankGPT 这种需要"靠谱回答"的场景,我不建议低于 Q4。
3.4 推理参数的调优:让 3B 模型跑出可用的速度
模型加载好之后,推理参数的设置直接决定了体验。下面是我在树莓派 5 上实测出来的一组比较稳的参数:
./build/bin/llama-cli \ -m models/qwen2.5-3b-instruct-q4_k_m.gguf \ -c 2048 \ -n 256 \ -t 4 \ --temp 0.7 \ --top-p 0.9 \ --repeat-penalty 1.1 \ -ngl 0 \ --no-mmap逐个说。-c 2048是上下文长度,设成 2048 是因为更长的上下文会占用更多内存和计算时间,而 CrankGPT 的对话场景通常不需要超长上下文。-n 256是单次生成的最大 token 数,限制这个值能防止模型"刹不住车"一直生成,也能让每次回答更快结束。-t 4是线程数,树莓派 5 四核,设成 4 刚好用满。--temp 0.7和--top-p 0.9是采样参数,0.7 的温度让回答既有变化又不至于太发散。--repeat-penalty 1.1是重复惩罚,小模型很容易陷入重复循环,这个参数能缓解。
-ngl 0表示不用 GPU 加速(树莓派没有独立 GPU,这个参数保持 0)。--no-mmap是关闭内存映射,强制把模型全部加载到内存里。这个参数有点反直觉——mmap 本来是省内存的,但在树莓派上,mmap 会导致模型在内存和存储之间频繁换页,反而更慢。实测下来--no-mmap的首次加载慢一点,但后续推理更稳定。
实测速度:3B Q4_K_M 模型在树莓派 5 上,prompt 处理速度约 15-20 tokens/秒,生成速度约 4-6 tokens/秒。也就是说,生成一段 100 字的回答大概需要 30-50 秒。这个速度不算快,但配合手摇供电的节奏,反而可以接受——你摇一会儿,回答就出来了。
4. 手摇供电系统:从机械能到稳定 5V 的完整链路
4.1 手摇发电机的选型与实测输出
手摇发电机这块,市面上常见的有两种:一种是带手摇柄的小型发电机模块,输出 12V 左右;另一种是手摇手电筒里那种,输出功率很小。CrankGPT 需要的是前者。
我买的是一个标称 12V/10W 的手摇发电机模块,实际测下来,正常摇速下空载电压能到 14-16V,接上负载后电压会掉到 10-12V。输出功率方面,悠闲地摇大概 3-5W,认真摇能到 8-12W,拼命摇(坚持不了几分钟)能到 15W 以上。
这里有个很多人会忽略的点:手摇发电机的输出是交流或者脉动直流,不是平滑的直流。如果你直接把它接到 DC-DC 模块上,纹波会很大,可能导致降压模块工作不稳定。我的做法是在发电机输出后面加一个整流桥(如果是交流输出)和一个大电容(1000uF 以上)做初步滤波,然后再进 DC-DC 降压模块。
4.2 DC-DC 降压与充电管理
从发电机出来的 10-16V 电压,需要降到适合给电池充电的电压。我用的是一个支持 5V/3A 输出的降压模块,输入范围 7-24V,效率标称 90% 以上。
整个供电链路是这样的:
手摇发电机 (10-16V 脉动直流) ↓ 整流桥 + 滤波电容 (平滑电压) ↓ DC-DC 降压模块 (降到 5V) ↓ 移动电源模块 (边充边放,稳定输出 5V/3A) ↓ 树莓派 5 + 屏幕 + 外设这个链路里,移动电源模块是关键。它承担了两个角色:一是储能缓冲,你手停下来的时候它能继续供电;二是稳压,不管输入电压怎么波动,它输出给树莓派的始终是稳定的 5V。
提示:选移动电源模块的时候,一定要看它的"边充边放"能力。有些便宜的模块在充电的时候不能同时输出,或者输出电流会被限制得很小。我第一个模块就是这个问题,插上发电机之后树莓派反而供电不足,换了模块才解决。
4.3 功耗预算的精细管理
手摇供电的功率是有限的,所以每一瓦都要省着用。下面是我做的功耗预算表:
| 组件 | 工作功耗 | 省电措施 |
|---|---|---|
| 树莓派 5 | 3-10W | 关闭 HDMI、关闭蓝牙、限制 CPU 频率 |
| 3.5 寸屏幕 | 0.5W | 降低亮度、设置自动息屏 |
| 小风扇 | 0.2W | 温度低于 60 度时关闭 |
| USB 键盘 | 0.1W | 不用时拔掉 |
| 合计 | 约 4-11W | 轻负载约 4W,重负载约 11W |
省电的几个具体操作。关闭 HDMI 输出:/boot/firmware/config.txt里加hdmi_blanking=2。关闭蓝牙:sudo systemctl disable bluetooth。限制 CPU 频率:在config.txt里设置arm_freq=2000(树莓派 5 默认 2.4GHz,降到 2.0GHz 能省一点电,性能损失不大)。屏幕亮度通过 PWM 控制,设成 50% 左右就够看。
这些措施加起来,轻负载场景(只跑模型、屏幕中等亮度)的功耗能压到 4W 左右,这意味着悠闲地摇就能维持运行。重负载场景(模型满速推理 + 屏幕高亮 + 风扇转)大概 11W,需要认真摇或者先充一会儿电。
5. 交互设计:让手摇这个动作变得有意义
5.1 物理按键的布局逻辑
CrankGPT 的交互核心是"少输入、快反馈"。我设计了四个物理按键:
- A 键(对话):按下后开始录音或者调出预设 prompt 列表
- B 键(确认/发送):确认选择或者发送当前输入
- C 键(清除/返回):清除当前输入或者返回上一级菜单
- D 键(电源/状态):显示当前电量、温度、模型状态
为什么是四个而不是更多?因为手摇的时候你只有一只手是自由的(另一只手在摇),按键太多反而手忙脚乱。四个按键刚好能覆盖所有常用操作,而且盲操也能摸到。
按键的物理选型上,我用的是带明确段落感的轻触开关,按下去有"咔哒"声和手感反馈。这个细节很重要——手摇的时候你没法一直盯着屏幕,触觉反馈能让你确认操作是否生效。
5.2 屏幕界面的极简设计
3.5 寸屏幕分辨率 480x320,能显示的内容有限。我的界面设计原则是:一屏只做一件事。
主界面显示三样东西:当前电量百分比、模型状态(就绪/推理中)、以及一个大的"对话"按钮。进入对话后,屏幕分成上下两部分,上面显示对话历史(只保留最近三轮),下面显示当前输入或者模型正在生成的回答。
字体用的是等宽字体,字号 16px,一屏大概能显示 15 行文字。生成回答的时候,文字是逐字出现的(流式输出),这样你能看到模型在"思考",而不是干等几十秒然后突然蹦出一大段。
提示:流式输出在树莓派上需要额外处理。llama.cpp 的
llama-server支持 SSE 流式接口,但树莓派上跑一个 HTTP 服务会多占一点内存。我的做法是直接用llama-cli的子进程模式,通过管道读取输出,这样省掉了 HTTP 层的开销。
5.3 手摇节奏与交互节奏的匹配
这是 CrankGPT 最有意思的地方。因为供电靠手摇,你的"摇"和"问"之间形成了一种天然的节奏。
我的设计是:当你停止摇动超过 30 秒,屏幕自动进入低功耗待机,模型也卸载出内存。下次你开始摇动,系统检测到电压上升,自动唤醒并重新加载模型。这个"摇动即唤醒"的机制,让整个设备有一种"活物"的感觉——你不理它,它就睡了;你摇它,它就醒了。
重新加载模型需要时间(3B Q4 模型在树莓派 5 上加载约 8-12 秒),所以如果你只是短暂停手,系统不会立刻卸载,而是等 30 秒。这个阈值可以调,我试过 15 秒和 60 秒,30 秒是比较舒服的平衡点。
6. 实测踩坑:那些文档里不会写的问题
6.1 电压跌落导致的随机重启
这是最折磨人的问题。现象是:树莓派在推理到一半的时候突然重启,没有任何错误日志。排查了很久才发现是电压跌落。
原因在于,树莓派 5 在 CPU 满载的瞬间,电流需求会突然飙升,如果供电线路的内阻偏大(比如线太细、接头接触不良),就会产生一个瞬间的电压跌落,跌破树莓派的欠压阈值(约 4.63V),触发保护性重启。
解决办法有三个。第一,加粗供电线,我用的是 18AWG 的硅胶线,比普通的杜邦线粗很多。第二,在树莓派电源输入端并联一个大电容(我用了 4700uF 的电解电容),它能吸收瞬间的电流波动。第三,在软件层面限制 CPU 的最大频率,减少瞬间功耗峰值。
这三个措施一起上之后,随机重启的问题就再也没出现过。
6.2 模型加载失败与内存碎片
另一个坑是模型加载失败。现象是llama-cli报 "failed to allocate memory" 或者直接段错误。但明明free -h显示还有 3GB 可用内存。
这个问题的根源是内存碎片。树莓派长时间运行后,内存会被各种进程切得七零八落,虽然总空闲内存够,但没有一块连续的足够大的空间来加载模型。llama.cpp 加载模型需要一块连续的虚拟内存区域,碎片化严重的时候就会失败。
解决办法:在加载模型之前,先运行一个内存整理脚本,或者干脆重启一下相关服务。我的做法是在启动脚本里加了一个检查,如果加载失败就自动sync && echo 3 > /proc/sys/vm/drop_caches清理缓存,然后重试一次。这个操作需要 root 权限,所以启动脚本要用 sudo 跑。
6.3 屏幕闪烁与 SPI 干扰
3.5 寸 SPI 屏幕在树莓派 5 上有个已知问题:当 CPU 负载高的时候,屏幕会闪烁或者出现横纹。这是因为 SPI 总线的时钟信号受到了 CPU 高频工作的干扰。
缓解办法有几个。第一,降低 SPI 时钟频率,在/boot/firmware/config.txt里把dtoverlay=waveshare35a改成带speed=16000000参数的版本(默认可能是 20MHz 以上)。第二,给屏幕的排线加磁环,能滤掉一部分高频干扰。第三,把屏幕的供电和树莓派的供电分开走线,避免共地干扰。
我最后是把 SPI 频率降到 16MHz,加了一个小磁环,闪烁基本消失了。代价是屏幕刷新率低了一点,但对于显示文字来说完全够用。
6.4 手摇发电机的机械噪音
这个问题比较"物理"。手摇发电机在摇动的时候会有齿轮啮合的噪音,安静环境下挺明显的。如果你在图书馆或者办公室用,可能会引来侧目。
我的解决方案是换了一个皮带传动的手摇发电机,用橡胶皮带代替齿轮,噪音小了很多。另外在发电机和外壳之间垫了一层橡胶减震垫,进一步降低了震动传导。如果你不介意噪音,齿轮传动的效率其实更高一点,这个看个人取舍。
7. 这套方案还能怎么扩展
CrankGPT 跑通之后,我发现这个"离线物理 AI 盒子"的框架其实可以往很多方向延伸。
一个方向是多模态。树莓派 5 的算力跑视觉模型比较吃力,但跑一个小的语音识别模型(比如 Whisper tiny)是可行的。加上一个 USB 麦克风,就能实现"手摇供电 + 语音对话"的完全离线交互。语音识别在树莓派 5 上大概需要 1-2W 的额外功耗,手摇的负担会重一些,但体验会自然很多。
另一个方向是太阳能辅助。在盒子顶部加一块小太阳能板,有光的时候自动充电,没光的时候靠手摇。这样在户外场景下,手摇的频率可以大大降低。太阳能板的输出功率不用大,5W 左右就够,主要是起一个"涓流充电"的作用。
还有一个方向是多机协作。如果你做了两台 CrankGPT,可以通过一个简单的无线模块(比如 LoRa,功耗很低)让它们互相通信。两台设备之间可以交换信息,甚至做简单的分布式推理——一台负责前半层,一台负责后半层。这个玩法比较硬核,但技术上完全可行。
最后分享一个我在调试过程中总结的小技巧:给整个系统加一个"电压日志"功能。用一个简单的 ADC 模块监测供电电压,把数据记录到文件里。这样当出现随机重启或者异常的时候,你可以回看电压曲线,判断是不是供电问题。这个日志帮我定位了好几个疑难杂症,强烈建议加上。
我在实际使用中最大的体会是,CrankGPT 的价值不在于它跑得多快、模型多大,而在于它把"AI 交互"这件事重新变得有物理重量。你摇得越久,越会认真想清楚要问什么。这种约束带来的专注感,是云端 API 那种"随叫随到"永远给不了的。