尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从零复刻Microduck:树莓派语音交互机器人搭建与LoRA微调实战

从零复刻Microduck:树莓派语音交互机器人搭建与LoRA微调实战 1. 复刻 Microduck 前先想清楚你到底要做个什么Microduck 是我最近折腾得最上头的开源项目没有之一。说它是“鸭子”它其实是一个能听懂人话、能开口回应、能转动脑袋看你的桌面级语音交互机器人。GitHub 上那个经典形象——一只黄色的机器小鸭底座配了一块屏幕头顶顶着一颗摄像头看起来像个玩具但拆开之后里面塞了一整套语音交互链路麦克风阵列拾音、唤醒词检测、语音识别ASR、大模型推理、语音合成TTS、舵机控制甚至还有一套基于 LoRA 的本地微调方案可以让这只鸭子用你想要的人设说话。我为什么强调“先想清楚你要做什么”因为市面上的复刻教程十有八九只讲两步下载代码、跑起来。然后新手兴冲冲 clone 下来pip install一堆依赖跑了个 demo发现鸭子只会重复固定的几句话摄像头画面也调不出来舵机嘎吱嘎吱乱转模型推理慢得跟树懒一样最后热情耗尽项目吃灰。这个项目真正的价值不在于“跑通”而在于理解它把“语音机器人”这件事拆成了哪几个模块每个模块在真实硬件上是怎么协作的。Microduck 的定位非常像斯坦福的 OpenDuckmini也就是网上常说的“机器鸭”但它在开源社区里被进一步做了一些简化硬件上尽量选用容易买到的舵机、麦克风阵列和开发板软件上把模型推理、语音识别、语音合成全部抽象成了独立服务模型训练也提供了一条完整的 LoRA 微调路径。所以它特别适合三种人想入门多模态硬件交互的嵌入式开发者、想低成本体验大模型微调全流程的研究生、以及单纯想给桌面添一个能聊天的小玩意的极客。我在完整复刻了一遍之后最深的感受是这项目给人的第一印象是“一个会动的语音助手”但它真正教会我的是一套“如何把云端大模型能力搬到本地硬件上”的工程方法论。接下来我就把我从零开始复刻的完整过程、中间踩过的坑、每个关键模块的取舍逻辑全部摊开来讲。2. 核心拆解Microduck 各个模块都在干什么2.1 它本质上是一个“循环”而不是一个“机器人”先把这只鸭子的内部逻辑拆开看你会发现它不是一个传统意义上的机器人而是一个不断循环的交互闭环麦克风采集语音 → 唤醒词检测Wake Word → 语音识别ASR → 大模型生成回复LLM → 语音合成播放TTS → 头部舵机做出反馈动作 → 回到等待唤醒状态。这个闭环里每一步都是一个独立模块模块之间通过消息队列或 WebSocket 通信。这跟很多新手直觉里“一个程序干所有事”的思路完全不同。设计成独立服务的好处非常明显任何一个环节挂了其他模块不受影响模型推理速度慢不会阻塞语音采集你可以单独替换掉其中某一个模型而不需要动其他代码。这是我在复刻过程中认为最重要的架构思想也是最值得迁移到其他项目里的经验。具体到实现层面Microduck 的主控程序通常跑在 Linux 开发板上树莓派 4B 或类似配置负责调度。语音识别用 faster-whisper 这类开源模型大模型推理可以用 llama.cpp 加载量化后的开源模型比如 Qwen 系列语音合成选的是 Edge-TTS 或 Piper 这类轻量方案。舵机控制则通过 PCA9685 模块输出 PWM 信号控制鸭子头部左右转动和上下点头。2.2 为什么要用“本地大模型 可选云端”混合方案Microduck 在模型调用上做了一个很聪明的设计默认支持本地部署小模型但也留了云端 API 的接口。这个混合方案我一开始不太理解——既然都开源了为什么不直接用最好的模型后来在复刻过程中才明白这是对成本和体验的精准平衡。本地部署意味着没有网络延迟TTS 播报和头部动作可以做到跟对话几乎同步鸭子看起来更“活”。但本地跑大模型吃内存和算力开发板上跑 7B 模型已经很吃力所以 Microduck 默认给的是量化到 4bit 的 1.8B 或 3B 级别小模型。这个规模放在 8GB 内存的开发板上能用 CPU 勉强推理虽然生成速度不算快但鸭子说一句话等两三秒还在可接受范围内。反过来如果你接云端大模型 API回复质量会大幅提升但每次对话都会有网络延迟而且你得自己承担调用费用。我在实际使用时用的是“本地模型保底、云端模型增强”的策略默认唤醒后走本地小模型保证离线也能用当用户问一些需要长回答的问题时我再手动切到云端大模型。Microduck 的代码里留好了这个切换的配置文件改起来就是改几行 YAML大家拿到手之后完全可以按自己需求调整。2.3 别小看那只“会动的脑袋”舵机联动设计很多人复刻 Microduck 时把 90% 的精力放在模型和语音上最后卡在了“鸭子头不会动”或者“舵机抖动”这种看似不起眼的小问题上。实际上头部动作恰恰是这个项目最有“灵性”的部分。Microduck 的舵机设计非常克制头部只有两个自由度左右偏航Yaw和上下俯仰Pitch底座固定没有行走机构。这保证了结构简单、重心稳定同时两个舵机已经足够表达“点头”“摇头”“左顾右盼”这些基本情绪。控制逻辑上Microduck 并不做复杂的视觉追踪而是让舵机的动作跟系统状态绑定。比如唤醒时头转向声源方向识别语音时轻微点头表示“在听”TTS 播放时嘴巴如果有舵机控制的话或头部有节奏地微动待机时每隔几秒做一次缓慢的环视。这些动作被封装成一组“姿态模式”大模型返回的文本如果包含特定情感标签系统还能映射到对应的动作序列。这种设计给了我很大启发交互机器人的“萌感”不在于自由度多而在于动作和语义是否匹配。3. 硬件选型与准备照着买不踩坑3.1 主控板别只看算力还要看外设接口Microduck 的主控板选择我在调研时发现社区里主要有两种方案。第一种是树莓派 4B8GB 版本Linux 生态最完善各种驱动一应俱全USB 麦克风阵列和摄像头基本都是即插即用。第二种是 RK3588 系列的开发板NPU 算力强很多跑起本地模型来速度更快但驱动和软件生态相对折腾一些很多教程都是针对树莓派写的用 RK3588 的话要自己适配一部分代码。我最终选了树莓派 4B 8GB原因很朴素资料多。Microduck 官方文档默认支持树莓派社区里踩坑贴也最多遇到问题基本直接搜就能找到答案。这里有一个重要提醒不要买 2GB 或 4GB 内存的版本。本地跑 ASR 加上大模型推理内存少了直接 OOM我第一次用 4GB 版本试跑 3B 量化模型系统直接卡死换 8GB 之后流畅度完全不一样。3.2 舵机和驱动板角度和电流是关键Microduck 的头部动作不复杂所以对舵机的要求并不高。社区里用的比较多的是 SG90 或 MG90S 这类 9g 微型舵机价格便宜、扭矩适中带动这只鸭子的小脑袋绰绰有余。需要注意的一点是Microduck 头部结构件如果是 3D 打印的重量会比原版稍重建议直接上 MG90S 金属齿轮版本铜齿轮舵机耐磨损避免长时间转动后出现虚位抖动。舵机驱动板我选择的是 PCA9685 模块这是一块 16 通道 12 位 PWM 驱动板通过 I2C 接口跟树莓派通信。为什么要用独立的 PWM 驱动板而不是直接用树莓派 GPIO 输出 PWM主要原因有两个。第一树莓派本身的 PWM 通道数量有限且精度一般PCA9685 可以输出多路独立 PWM方便后续扩展。第二舵机控制需要稳定频率通常 50HzPCA9685 用晶振产生时钟抖动比软件模拟 PWM 小很多。实际测试下来用 PCA9685 驱动舵机转动平滑度明显好于 GPIO 直接驱动这个钱花得值。3.3 拾音方案阵列麦克风远比你想象的重要语音交互项目里麦克风是决定用户体验的第一道关卡。Microduck 放在桌面上的使用距离大约是一米到两米普通 USB 麦克风在这个距离上拾音质量非常差背景噪声一大ASR 识别率直接崩。社区里普遍推荐 ReSpeaker 2-Mic 或 4-Mic 阵列麦克风这类麦克风自带回声消除和波束成形功能可以在一定程度上过滤环境噪声还能粗略判断声源方向。我实测下来的经验是4-Mic 版本比 2-Mic 版本在声源定位上强很多但价格也贵了一倍。如果你的使用场景是安静的桌面环境2-Mic 完全够用如果房间里有空调声、键盘声建议直接上 4-Mic。另外麦克风阵列接入树莓派之后一定要注意采样率配置默认可能是 16kHz但 faster-whisper 对 16kHz 的效果已经不错如果你用其他 ASR 引擎可能需要重采样到对应采样率这个后面排查部分会细说。3.4 外壳与组装3D 打印优先手工改造可行Microduck 项目提供了一个非常可爱的鸭子外壳 STL 模型可以直接 3D 打印。如果你手边没有 3D 打印机也可以考虑用 EVA 泡沫和热熔胶手工打造——网上有人这么做出来虽然精细度差一些但 DIY 的乐趣更足。我在组装时遇到的一个问题是打印出来的壳体公差控制不好舵机转轴和头部连接件卡不住。解决办法是打印时把连接件的孔径调大 0.2mm或者直接用 M2 螺丝加固。还有一个实用小技巧在舵机臂和 3D 打印件之间垫一层薄橡胶垫片可以消除舵机运转时的哒哒声这个细节很多教程没有提到。组装顺序建议是先装舵机、再装头部结构件、最后装入麦克风和屏幕。千万别先装屏幕再接舵机线否则后期理线会非常痛苦。Microduck 的底座空间不大需要合理规划走线建议用扎带把舵机线、USB 线分别固定避免转动时线缆缠绕。4. 软件环境搭建与核心配置4.1 系统基础与依赖安装别急着跑代码Microduck 的软件环境我建议用 Docker 和裸机部署两种方式分别考虑。如果你只想测试不想把宿主机环境搞乱直接用它项目仓库里的 docker-compose.yml 起一套容器所有依赖都封装好了。但如果计划改代码、调试模型Docker 模式会有点隔靴搔痒我最终选择的是裸机部署这样跑起来更直接资源占用也更少。系统层面建议用 Raspberry Pi OS Lite64 位不要装带桌面的完整版因为桌面环境会吃掉不少内存对本地模型推理来说很不划算。基础依赖主要是 Python 3.10、PortAudio、ffmpeg、espeak-ng 这些。有一个细节Microduck 的代码里对 ALSA 音频设备有依赖装系统后你可能需要在alsa-base.conf里手动指定默认声卡为 USB 麦克风否则 Python 会读不到音频流。这个坑我在第一次启动时被卡了将近半天后面排查部分会详细说明。4.2 服务的分层启动策略按依赖顺序来Microduck 的软件架构里各个服务之间是有依赖关系的启动顺序不对会出现各种诡异问题。我的经验是严格按照下面这个顺序来启动音频服务确保麦克风设备可以被系统识别和访问这一步可以用arecord -l验证。启动 ASR 服务faster-whisper 模型加载比较慢先把它拉起来常驻内存。启动 LLM 推理服务llama.cpp 加载 GGUF 量化模型同样常驻。启动 TTS 服务Piper 或 Edge-TTS 的 HTTP 接口。启动舵机控制服务初始化 PCA9685把舵机调整到初始角度。最后启动主控制程序它负责把前面几个服务串起来。为什么要按这个顺序因为主控制程序启动时会检查各服务是否就绪如果 ASR 还没加载完就启动主程序它会默认跳过语音识别出一些很难排查的隐性 bug。用 Docker Compose 的话可以在配置里加上健康检查来管理依赖裸机部署的话可以写一个简单的 systemd 启动序列我的做法是给每个服务写一个 service 文件用After和Requires控制依赖这样重启机器后所有服务自动拉起不用每次都手动敲命令。4.3 关键参数配置采样率、唤醒词、模型路径Microduck 有一个全局配置文件config.yaml里面有几个关键参数值得单独拿出来说。第一个是音频参数。sample_rate默认是 16000但如果你用 ReSpeaker 2-Mic它默认输出可能是 48000这时候你需要让 ASR 服务做输入重采样。faster-whisper 内部会处理重采样但如果你是从 PyAudio 直接读取原始流需要自己加一步重采样逻辑可以借助soundfile或者librosa来做。我当时忽略了这一点结果 ASR 识别出来全是乱码排查了很久才发现是采样率不匹配。第二个是唤醒词配置。Microduck 默认使用 openWakeWord 这个开源唤醒词库它提供的预训练模型里没有“小鸭”这类词。好在它支持自定义唤醒词训练训练数据只需要录制几段特定词语的音頻就可以生成一个轻量级唤醒模型。我在 30 分钟内就训练出了一个“你好小鸭”的唤醒词识别准确率在安静环境下能达到 90% 以上这个能力非常实用。第三个是模型路径。如果你下载了不同的量化模型需要把local_llm下的model_path改成对应的 GGUF 文件路径同时注意n_ctx上下文长度的设置8GB 内存的树莓派建议设置为 2048超过这个值容易爆内存。上下文长度越小回复越“健忘”但换来的是更快的推理速度对鸭子这种短对话场景2048 是平衡点。4.4 让系统开机自启别做每次手动敲命令的人Microduck 配好之后每次开机手动启动 6 个服务是件很反人类的事情。我的方案是为每个服务写一个 systemd 单元文件设置好依赖关系然后用systemctl enable让服务开机自动拉起。这里有一个容易忽视的坑如果你的主控制程序依赖桌面环境比如用了某些 GUI 库在 systemd 服务里运行时是没有 DISPLAY 环境变量的需要手动export DISPLAY:0否则程序会启动失败。Microduck 默认没有 GUI 依赖但如果你自己改了代码加了可视化面板就要留意这个问题。5. 模型训练让鸭子拥有你的专属人设5.1 训练之前的准备选底座模型还是用别人训好的Microduck 热度高很大一部分原因在于它提供了相对完整的本地微调链路。你可以让这只鸭子用模仿“你”的语气说话或者拥有某个特定角色的性格。要做到这一点需要对底座模型做 LoRA 微调。LoRA 的原理简单说就是冻结大模型原始权重只在旁边接入一小部分可训练的低秩矩阵用少量数据就能完成领域适配非常节省显存和训练时间。底座模型选择上社区里用 Qwen 系列和 ChatGLM 系列的都有。Qwen-1.8B 或 Qwen-3B 这类小模型是树莓派本地推理能够承载的上限附近也是微调时比较合适的底座选择。如果你的电脑有 8GB 以上显存本地训练这些模型完全没有压力即便是纯 CPU 训练 1.8B 的 LoRA也只是慢一点不至于跑不动。5.2 数据准备200 条高质量对话足矣很多人对微调有误解觉得数据越多越好。实际上对于 LoRA 微调这种轻量方案喂给它 2 万条杂乱数据效果远不如给它 200 条对提升效果目标明确的高质量对话。我在准备数据时用的格式是 OpenAI 的 ChatML 格式每个样本包含 system prompt、user 和 assistant 三段。system prompt 用来定义人设比如“你是小鸭一只生活在书桌上的机器鸭性格活泼但不聒噪说话简短有时带点幽默感”。user 和 assistant 则是对应的对话对。数据质量上要特别留意人设的一致性和回答风格的统一。我建议把数据按场景分类整理比如打招呼场景、介绍自己场景、讲冷笑话场景、拒绝回答场景等。这样训练出来的鸭子说话风格稳定不会一会儿像个客服一会儿又像个机器人。收集数据的方法可以天马行空你可以在电脑上写一批模拟对话也可以用别的大模型生成一批然后人工修改甚至可以直接去翻 GitHub 上现成的开源对话数据集但要记得过滤掉对不适用于目标人设的部分。5.3 训练实操用 LlamaFactory 完成全流程训练工具我选的是 LlamaFactory这是一个开源的微调框架界面化程度高、文档全、对新手极其友好。它同时支持 LoRA 训练和 GGUF 导出正好对应 Microduck 需要的模型链路。训练流程大致分三步。第一步用 LlamaFactory 的 WebUI 配置模型路径和数据集路径第二步设置 LoRA 超参数r秩通常取 8 或 16alpha取 16 或 32batch_size根据显存调整learning_rate一般 1e-4 到 2e-4第三步开始训练训练完成后将 LoRA 权重合并回底座模型导出成 HuggingFace 格式再用 llama.cpp 量化转换成 GGUF 文件放到树莓派上替换原来的模型。我实际训练 200 条数据用 6GB 显存跑一个 epoch大约 10 分钟完成转换出 4bit 量化 GGUF 文件之后模型文件大小约为 1.1GB放到树莓派上加载运行毫无压力。整个过程快到超出我的预期也让我意识到在 2025 年普通个人开发者微调小模型的成本和门槛真的已经低到了一个很夸张的地步。5.4 训练效果评估与常见误区训练完之后不要急着部署到鸭子头上。先在电脑上用同一套测试集做对比测试在训练前跑一遍旧模型记录回复然后加载 LoRA 微调后的模型跑同样的输入对比风格差异。我在这个环节发现了一个常见误区如果训练数据里只有“输出风格”但不含足够多样化的“用户提问”模型只会学到一种类型的回复套路换一个问法就露馅。所以数据准备阶段同一个意思尽量写多个不同的问法这样才能让模型真正学会“在不同语境下保持人设”而不是死记硬背回答。6. 实际联调中的问题排查与优化心得6.1 音频链路不通麦克风读不到数据怎么办这是新手复刻 Microduck 遇到率最高的一个问题我自己就差点折在这里。现象是程序能启动但字幕区域始终没有识别结果。排查路径有固定套路先检查硬件层用arecord -l看系统是否识别到了 USB 麦克风然后检查 ALSA 默认设备用arecord -d 3 test.wav录一段然后播放验证最后检查 Python 层能否访问设备。Microduck 默认使用的 PortAudio 库在树莓派上经常会遇到设备编号漂移问题——插拔 USB 设备后索引变了导致 Python 读的设备和实际麦克风不是同一个。我最后的解决办法是使用设备名称而不是数字索引来指定输入设备比如plughw:Microphone这样即使 USB 重新枚举也没问题。6.2 TTS 播报卡顿缓冲区参数调优TTS 声音发闷、卡顿或者播报时像机器人变声这类问题需要从两个方向排查。第一个方向是模型本身的原因Piper 这类本地 TTS 模型有不同音色和质量等级默认音色在某些环境下会跟音乐噪声混合换一个音色模型或者调低语速就能缓解。第二个方向是音频缓冲区的问题PyAudio 在树莓派上默认的输出延迟不匹配会导致声音断续。我的经验是将frames_per_buffer从默认的 1024 调整到 512sampling_rate保持 22050Piper 的默认采样率延迟明显下降播报流畅度提升了一个档次。如果你用的是流式 TTS边生成边播放方案还需要在代码里做音频队列管理避免生成速度跟不上播放速度导致卡顿。一个简单的方案是把 TTS 生成出的音频块放入线程安全的队列播报线程从队列中读取并播放队列为空时静默等待而不是关闭音频流。这个设计让 TTS 的响应速度和稳定性都提升很多是复刻 Microduck 过程中最值得借鉴的代码技巧之一。6.3 大模型推理速度优化多了一层“预生成”逻辑树莓派用 CPU 跑 3B 量化模型初次加载模型需要约 20 秒生成一个字大约要 0.5 秒。这意味着对话时用户说完话到鸭子回复的延迟可能在 7 到 10 秒。这个体验说实话不太行但 Microduck 给出了一个非常优雅的优化思路预热加预生成。预热指的是在系统启动时就把模型加载进内存并跑一遍简单输入避免首次推理时加载时间的突然延迟。预生成则是在用户说话的同时后台先根据最近一次的对话上下文生成一部分可能的回复候选等 ASR 结果出来之后直接匹配最接近的候选并播报大幅缩短响应时间。我在实际使用中手动调低了n_ctx到 1024虽然上下文变短了但推理速度肉眼可见地提升对于桌面陪伴型对话来说这个取舍很值得。6.4 舵机抖动与发热从机械结构到 PWM 频率舵机抖动问题和 PWM 频率、供电稳定性都有关系。树莓派的 GPIO 输出电压不够稳定几个舵机同时动作时容易拉低电压导致抖动甚至舵机抽搐。解决办法有两条一是用外部 5V 电源给 PCA9685 供电树莓派只发控制信号二是把舵机供电和主控供电隔离避免互相干扰。我在给 PCA9685 加装独立电源之后抖动问题基本消失。PWM 频率方面SG90 舵机的工作频率是 50Hz对应 20ms 的脉宽周期脉宽在 0.5ms 到 2.5ms 之间对应 0 到 180 度。PCA9685 模块内部有一个 PRE_SCALE 寄存器来设置 PWM 频率Microduck 的代码里默认设置是 50Hz但个别舵机对频率敏感稍微调整到 60Hz 后转动更平滑。这个参数你在硬件不同批次之间都可能需要微调不是固定的。6.5 常驻服务内存占用过高的排查技巧Microduck 跑起来之后树莓派内存经常会出现告警。用htop一看faster-whisper 的 Python 进程占了 2GBllama.cpp 流程占 3GB再加上系统本身和 TTS 服务8GB 内存确实有点紧张。排查思路是优先给 LLM 推理服务分配固定内存然后限制 ASR 进程的 CPU 和内存占用。还有一个容易被忽略的点faster-whisper 默认会预分配较大计算图但实际用不到可以在初始化时设置cpu_threads4和compute_typeint8显存换成内存的理论上int8类型能省一半内存占用。实测下来这两个参数调优后 ASR 进程的 RSS 从 2GB 降到 1.2GB效果立竿见影。7. 复刻完成后的扩展方向从“鸭子”到“你的鸭子”如果你已经成功复刻了一只 microduck 并让它正常跑起来我建议下一步不要急着换模型而是先做一个只属于自己的小功能。我的做法是给它加了一个“天气查询”技能通过麦克风说“小鸭今天天气怎么样”系统自动识别意图调用一个天气 API 获取数据然后让 LLM 组织成拟人化的回答最后 TTS 播报。这个功能虽然简单但完整地走了一遍“语音识别→意图识别→工具调用→回复生成→语音播报”的链路比改模型参数能学到的东西多十倍。另一个扩展方向是结合视觉能力。Microduck 头顶有一颗摄像头目前只用来看画面、把图像推送到屏幕但其实完全可以接入视觉语言模型VLM让鸭子“看到”你桌上的东西并做出反应。比如你拿着一杯咖啡问它“这是什么”它能回答“看起来是一杯美式咖啡”。这个改造需要额外的模型部署对树莓派的性能来说有一定压力但用轻量级 VLM 模型加量化部署还是可以跑起来的。我目前正在做这个方向等调通了再写一篇详细的分享。如果你愿意折腾还可以把 Microduck 接入 NTFY 这类推送服务让它主动提醒你日程或者接入安卓手机通过蓝牙控制做成一个“桌面小管家”。开源项目的魅力就在于它给了一个可跑通的底座而往上堆什么能力完全取决于你的想象力。8. 写在最后的几点实在心法复刻 Microduck 最大的收获不是真的让一只鸭子在桌面上跟我聊天而是打通了从硬件到模型、从数据到部署的整条链路。我以前写嵌入式代码时很少考虑模型推理的资源和延迟以前微调模型时也从不关心最终要怎么部署到低算力设备上。Microduck 把两者强行拉到一起让我被迫从端到端的角度去思考一个真实产品该怎么设计。如果你想复刻这个项目我给你三条具体的建议。第一严格按照官方文档的硬件清单来下单不要自作聪明随便替换型号尤其在麦克风和舵机这两个部件上社区验证过的东西会帮你省掉大量排查时间。第二数据准备阶段把你想让鸭子说什么、不说什么先想清楚写下来再开始标注数据这比先跑起来再补数据高效得多。第三遇到问题先自己去日志里找线索再上 GitHub Issues 搜关键词大多数坑你不是第一个踩的人但自己找到答案的过程会加深记忆。最后分享一个小技巧在树莓派上跑完 Microduck 后记得给系统做一个完整镜像备份。我因为反复改配置弄坏了三次系统每次重装加配置都要花两个小时。做一次dd镜像备份哪怕真的搞坏了一条命令就能恢复。这一点我踩过坑之后才懂它的含金量。
返回列表