Microduck 这个项目,圈内玩嵌入式 AI 和端侧大模型的朋友应该不陌生。它本质上是 OpenDuckMini 开源项目的一个衍生规格——把一只玩具鸭改装成能听懂人话、能对话、能联网查资料的桌面机器人,而 Microduck 更强调轻量化和低成本的复刻路径。最近看到不少人在问训练教程和源码地址,我把自己从硬件选型到模型微调、从踩坑到跑通的全过程整理出来,希望能帮到正准备动手的人。
先说结论:这套东西的核心价值不在于“鸭”,而在于它把语音唤醒、大模型推理、语音合成、联网搜索这一整套端侧 AI 链路,压缩到了千元级硬件上。对想入门边缘计算和端侧大模型部署的人来说,是性价比极高的实验平台,比单纯在电脑上跑 demo 有价值得多。
1. 项目拆解:Microduck 到底是什么,能做什么
1.1 一台会说话的鸭子,背后是完整的端侧 AI 链路
很多人第一次看到 Microduck 的视频,第一反应是“这不就是个蓝牙音箱套了个鸭壳子吗?”实际上完全不是一回事。它是一台具备完整语音交互能力的边缘计算设备:你叫它的名字,它能唤醒;你说一句话,它能理解并给出回答;你问天气、问新闻,它能自己上网搜然后念给你听。整个过程全部在设备本地完成推理,不需要依赖云端的商业 API。
硬件组成上,Microduck 主要由这么几个部分构成:
- 主控板:通常是 Jetson Orin Nano 或 Orin Nano Super 开发者套件,负责跑大模型推理。这是整个系统的算力核心。
- 麦克风阵列:用于拾音和声源定位,让鸭子知道声音从哪个方向来。
- 扬声器:语音回复的输出设备。
- 舵机:控制鸭头转动,实现“看向说话人”的互动效果。
- 玩具鸭外壳:市面上常见的儿童玩具鸭,内部掏空用来容纳硬件。
软件层面则是典型的端侧语音 Agent 架构:语音识别(ASR)模块负责把声音转成文字,大语言模型负责理解并生成回复,语音合成(TTS)模块把文字转回声音。中间还有一个会话管理模块用来维护多轮对话上下文,以及工具调用模块用来触发联网搜索等外部动作。
1.2 它和 OpenDuckMini 的渊源
Microduck 这个名字经常和 OpenDuckMini 一起出现,两者的关系可以理解为:OpenDuckMini 是上游项目,Microduck 是在它基础上做轻量化调整的复刻版本。原版 OpenDuckMini 由社区爱好者发起,设计目标是让更多人能以较低门槛复现一个“能对话的机器鸭”;Microduck 则在元器件选型、模型量化和部署流程上做了更适合个人玩家操作的优化。
我这边的复刻以 Jetson Orin Nano 为主控,8GB 显存版本的开发套件在二级市场已经降到 1500 元以下,加上麦克风、舵机、外壳和其他配件,整套成本控制在 2000 元左右。如果手头有闲置的 Orin Nano 或者愿意用性能稍弱的树莓派 5 来跑小尺寸模型,成本还能进一步压缩。
1.3 适合谁来复刻,能学到什么
我自己的体会是,Microduck 的受众主要分三类:
第一类是刚入门端侧 AI 的开发者。通过复刻过程,能完整理解大模型从云端走向边缘设备时面临的算力约束、量化精度权衡和推理延迟优化,这是看多少文档都换不来的实践经验。
第二类是嵌入式系统工程师。Microduck 涉及 Jetson 平台的外设驱动、音频采集、舵机控制、串口通信,以及 TensorRT、ONNX Runtime 等多种推理引擎的部署。对嵌入式背景的人,这是把 AI 能力融入传统硬件控制的好案例。
第三类纯粹是爱好者和创客。喜欢折腾硬件、喜欢做点有意思东西的人,Microduck 的互动性和可扩展性都很强。改个提示词换个人格、加个传感器、接个智能家居控制,玩法很多。
如果你只想在电脑上跑一个大模型聊天,这个项目帮不上什么忙;但如果你想亲手搭一个“看得见、摸得着”的 AI 硬件产品,Microduck 是目前少有的适合个人复刻的完整方案。
2. 核心组件拆解与选型思路
2.1 主控平台:为什么推荐 Jetson Orin Nano
主控是整个系统的算力核心,选型直接决定了你能跑多大参数的模型、推理速度有多快。Microduck 社区比较常用的方案是 Jetson Orin Nano 8GB,原因有三个:
首先是显存容量。端侧跑大模型最缺的就是显存。Orin Nano 8GB 在 INT4 量化下可以流畅运行 7B 到 8B 参数规模的模型,配合流式输出,对话延迟能控制在 2 秒以内,体验已经比较接近云端 API。4GB 显存版本只能跑 3B 到 4B 的模型,对话质量差距明显。
其次是功耗和散热。Orin Nano 的默认功耗模式为 7W 到 15W,在性能模式下最高 25W,配合一个 5V 风扇和铝合金散热片就能稳定运行。相比动辄几百瓦的桌面 GPU,它可以完全靠 USB PD 移动电源供电,这让鸭子可以脱离插座走动展示。
最后是生态成熟度。NVIDIA 为 Jetson 平台提供了完整的 JetPack SDK,把 CUDA、cuDNN、TensorRT 全部预装好。部署 ONNX 或 PyTorch 模型时可以直接调用 TensorRT 加速,大幅降低推理优化的工作量。个人开发阶段用 PyTorch 直接推理也能满足需求,社区里现成的部署脚本也基本都是基于这套生态的。
2.2 语音交互模块:麦克风阵列、扬声器和语音库选型
语音交互链路对硬件的要求集中在前端的拾音质量和后端的播放清晰度上。Microduck 常见的麦克风配置是 ReSpeaker 2-Mic 或 4-Mic 阵列,通过 USB 连接到 Jetson,也直接兼容 ROS 音频驱动。
如果你希望鸭子能根据声源位置转头,就选 4-Mic 阵列,配合算法可以实现声源定位;如果只做简单唤醒和对话,2-Mic 就够用,成本更低。扬声器方面我试过 JBL 的小型蓝牙音箱和普通 3W 全频喇叭,前者的声音层次感明显更好,后者胜在体积小、安装方便。如果鸭壳内部空间充足,优先选蓝牙音箱,外放效果对整体体验的影响比很多人想象中大。
语音能力这块,社区常用的方案是 Sherpa-ONNX 做本地语音识别,模型用 Zipformer 或 Paraformer 的中文模型;TTS 则用 Piper 或 Sherpa-ONNX 自带的 VITS 模型。两个模块都是 ONNX 运行时,在 Jetson 上跑 CPU 推理也能接受,基本不会构成性能瓶颈。
2.3 模型部署方式:TensorRT 和 llama.cpp 的选择
大语言模型的推理优化,我在 Microduck 上实测过两种主流方案:
一种是 TensorRT-LLM。优势是与 Jetson 平台高度契合,推理速度最快,但环境配置相对繁琐,且对模型结构有要求。社区提供的量化脚本可以自动完成从 HuggingFace 模型到 TensorRT Engine 的转换,不过对新手来说,单是trtllm-build的编译时间就可能等上半小时。
另一种是 llama.cpp。以 GGUF 格式运行量化模型,部署简单、跨平台兼容性好,在 Jetson 上开启 CUDA 加速后性能损失相对可控。我在 Orin Nano 上跑 Qwen2.5-7B-Instruct 的 Q4_K_M 量化版,出字速度大约 15 到 20 token/s,配合流式输出生成的等待感不像完整计算延迟那样明显。
我的建议是:第一版先跑通 llama.cpp,因为流程最短、出错概率最低。等系统全链路稳定之后,再考虑迁移到 TensorRT-LLM 追求极限速度。
3. 完整复刻流程:从硬件组装到系统跑通
3.1 硬件准备与外壳改造
硬件清单我整理成了一张表,方便对照采购:
| 部件 | 推荐规格 | 预估价格 | 备注 |
|---|---|---|---|
| 主控板 | Jetson Orin Nano 8GB 开发者套件 | 1400-1800 元 | 二手性价比更高 |
| 麦克风阵列 | ReSpeaker 2-Mic / 4-Mic | 100-200 元 | USB 接口,免驱 |
| 扬声器 | JBL GO 系列或 3W 全频喇叭 | 80-150 元 | 优先选蓝牙音箱 |
| 舵机 | SG90 或 MG90S | 10-20 元 | 控制鸭头左右转动 |
| 电池 | 支持 15V/3A 输出的 USB PD 移动电源 | 100-200 元 | 容量建议 10000mAh 以上 |
| 玩具鸭 | 中大型塑料鸭,高度30cm以上 | 30-60 元 | 内部空间至少能放下开发板 |
外壳改造是整个过程中最“手工”的环节,也是很多人卡住的地方。我踩过的教训是:不要试图把开发板完整塞进鸭子身体里,要把鸭身从腹部切开,底部用亚克力板做一个托盘,把主板、麦克风、电池固定在托盘上,再把鸭壳罩上去。这样既方便散热,也方便以后维护。
麦克风的位置也有讲究,不要直接塞在鸭子肚子里,否则拾音会被塑料壳严重衰减。我是在鸭嘴位置开了一个小孔,把麦克风阵列伸出来,实测唤醒率从不足六成提升到了九成以上。
3.2 系统烧录与基础环境配置
Jetson 的系统烧录相对简单,用 NVIDIA SDK Manager 在 PC 上安装 JetPack 6.0 及以上版本,选择 Jetson Orin Nano 开发者套件即可。系统烧录完成后,需要做几件基础配置:
- 开启 64GB 或更大容量的 NVMe SSD 作为系统盘,否则 16GB 的 eMMC 根本不够用。
- 把系统电源模式切换到 25W 性能模式:
sudo nvpmodel -m 0。 - 安装常用依赖:Python 3.10+、pip、git、CMake 和构建工具。
- 配置虚拟内存 swap,建议 8GB 以上,防止编译和加载大模型时内存不足。
3.3 语音链路部署:ASR、LLM、TTS 全打通
语音链路部署是核心中的核心,我按顺序拆解一遍。
先做 ASR。用 Sherpa-ONNX 的 Python API 写一个简单的识别函数:
import sherpa_onnx recognizer = sherpa_onnx.OnlineRecognizer.from_transducer( encoder="sherpa-onnx-streaming-zipformer-zh-14M-2023-02-23/encoder.onnx", decoder="sherpa-onnx-streaming-zipformer-zh-14M-2023-02-23/decoder.onnx", joiner="sherpa-onnx-streaming-zipformer-zh-14M-2023-02-23/joiner.onnx", tokens="sherpa-onnx-streaming-zipformer-zh-14M-2023-02-23/tokens.txt", num_threads=4, sample_rate=16000, feature_dim=80, enable_endpoint_detection=True, )这里enable_endpoint_detection=True是关键,它能让识别器在检测到说话结束后自动返回完整句子,省去自己在音频流上做 VAD 的麻烦。实际录制时注意采样率要统一为 16000Hz,声道数为单声道,用sounddevice或pyaudio采集都可以。
接着是 LLM 推理。用 llama.cpp 的 Python 绑定写一个流式对话函数:
import llama_cpp llm = llama_cpp.Llama( model_path="qwen2.5-7b-instruct-q4_k_m.gguf", n_gpu_layers=-1, # 全部层加载到 GPU n_ctx=4096, # 上下文窗口 n_threads=8, verbose=False, ) def chat(prompt, history=[]): messages = history + [{"role": "user", "content": prompt}] stream = llm.create_chat_completion( messages=messages, stream=True, temperature=0.7, max_tokens=512, ) response = "" for chunk in stream: delta = chunk["choices"][0]["delta"] if "content" in delta: response += delta["content"] return response, messages + [{"role": "assistant", "content": response}]n_gpu_layers=-1表示把所有层都放到 GPU 上,8GB 显存跑 7B Q4 量化模型是够的。上下文窗口 4096 够日常对话用,调太大会明显增加首 token 延迟。
最后是 TTS。用 Sherpa-ONNX 的离线 TTS 接口生成语音:
import sherpa_onnx import soundfile as sf import numpy as np tts = sherpa_onnx.OfflineTts( model="sherpa-onnx-vits-zh-ll", vocoder="", # VITS 不需要单独声码器 provider="cpu", ) text = "你好,我是 Microduck。" audio = tts.generate(text, sid=0, speed=1.0) sf.write("reply.wav", audio.samples, samplerate=audio.sample_rate)VITS 模型不需要额外的声码器,一次性生成完整音频,在 Orin Nano 上生成一句 5 秒钟的话大约需要 1 秒,体验上没有明显等待感。
3.4 会话管理、工具调用与联网搜索
到这一步,鸭子已经能听、能说、能对话了,但还缺一个关键能力——联网搜索。要让它能回答天气、新闻这类时效性问题,需要接入搜索 API 并将结果喂给大模型。
我的实现思路是用一个简单的函数调用协议:用户问题先让大模型判断是否需要搜索,需要的话返回结构化 JSON 指令,主程序执行搜索并把结果拼接为上下文再次送入模型。示例代码如下:
tools_prompt = """你是一个工具调度助手,判断用户问题是否需要联网查询。 如果需要查询,请输出 JSON:{"need_search": true, "query": "搜索关键词"} 如果不需要,请输出 JSON:{"need_search": false}""" def should_search(user_input: str) -> dict: response, _ = chat(tools_prompt + "\n用户问题:" + user_input) try: return json.loads(response.strip().strip("```json").strip()) except: return {"need_search": False} def run_search(query: str) -> str: url = "https://api.duckduckgo.com/" params = {"q": query, "format": "json", "no_html": 1} resp = requests.get(url, params=params, timeout=10) data = resp.json() results = data.get("RelatedTopics", [])[:5] return "\n".join([r.get("Text", "") for r in results if "Text" in r])实际使用中,搜索结果的截断和拼接待优化——通常只需要把前 3 到 5 条摘要拼进上下文,比如在系统提示词里追加“搜索结果:xxx”,让模型基于这些信息组织回答。这个方案不需要额外 API key,免费可用,个人玩家首选。
会话管理方面我维护一个简单的双端队列,只保留最近 6 轮对话,超出就丢弃最旧的一条。如果想实现长期记忆,可以在每轮结束后用嵌入模型把对话总结写入本地 SQLite,下次唤醒时检索相关片段拼入上下文。这是社区里比较成熟的做法,也能避免上下文窗口被长对话占满。
4. 模型训练与个性化定制:让鸭子听懂你的话、拥有自己的声音
很多人问 Microduck 能不能训练,其实是问两个层面:一是让它更懂你、回答风格更贴合你的需求;二是让它的声音更像某个特定的人。这两件事对应的是大模型的微调和 TTS 的语音克隆。
4.1 大模型微调:用 LLaMA-Factory 把 7B 模型“调教”成你的风格
默认的 Qwen2.5-7B-Instruct 是一个面向通用场景的模型,回答风格偏保险、偏官方。如果你想让它像一个朋友那样说话,或者成为一个懂某个特定领域的问答助手,需要做轻量微调。
我在 Microduck 上的做法是用 LLaMA-Factory 做 LoRA 微调,只训练少量参数就能达到不错的效果。训练数据格式是 Alpaca 风格的三段式:instruction(指令)、input(输入)、output(期望回答)。举个例子:
[ { "instruction": "你是 Microduck,一只友善可爱的机器鸭助手。", "input": "介绍一下你自己", "output": "你好呀!我是 Microduck,一只会用嘴巴思考的机器鸭。我的家在 Jetson 主板上,虽然身体是塑料的,但我可是有一颗真实的 AI 大脑哦!" } ]数据量方面,我准备了大约 500 条这样的对话样本,覆盖自我介绍、问候、闲聊、问答等日常场景。在单张消费级显卡上训练 2 到 3 个 epoch,整套流程大约一小时。LoRA 的r值设为 16,alpha设为 32,学习率 2e-4,这些是社区常用的稳定参数。
微调完成之后需要把 LoRA 权重合并回原模型,导出为 GGUF 格式。LLaMA-Factory 本身支持导出 HuggingFace 格式,再通过 llama.cpp 的转换脚本转成 GGUF。这一步有个常见坑:转换时要确保tokenizer_config.json完整,否则输出可能会出现乱码或非法字符。
坦白说,对于大部分只是想“让鸭子更可爱一点”的用户,微调不是必选项。直接在系统提示词里写下人格设定,零训练成本也能达到 80% 的效果。我自己是在跑通了全流程之后才做的微调,用 LoRA 打磨回答风格做实验,同时也为了验证模型本地迭代的路径是走得通的。
4.2 语音克隆:用 GPT-SoVITS 让鸭子说你的声音
如果说微调是“性格定制”,语音克隆就是“声线定制”。Microduck 的 TTS 模块用的 VITS 模型是通用音色,听起来机械感较强。要让鸭子的声音更像自己或者某个特定人,我用的是 GPT-SoVITS 做语音克隆,再把克隆模型导出为 ONNX 格式供 Sherpa-ONNX 调用。
操作流程是:先用手机录 30 到 60 秒的干净人声(注意安静环境,避免混响),然后用 GPT-SoVITS 的训练脚本做微调。在消费级显卡上大约训练 100 到 200 步即可得到一个效果不错的音色模型,训练时间通常在十几分钟到半小时之间。
训练完成后导出为 ONNX 格式,替换掉 Sherpa-ONNX 的默认 VITS 模型路径,接口调用方式完全不变。语音克隆的效果和我自己的原声非常接近,听感上大约有八分相似,在玩具鸭这种窄带扬声器上播放,已经能骗过熟人了。
有一点需要提醒:语音克隆有被滥用的风险,如果打算做成公开产品或者发视频,请确保你克隆的声音是你自己的,并且使用时明确标注为 AI 合成,避免引发不必要的争议。
4.3 从云端到本地的完整部署流程
训练和推理都在本地完成,不依赖任何云服务,这个特性对注重数据隐私的人很有吸引力。整个流程可以总结为:
- 买一套 Jetson Orin Nano,烧录 JetPack 系统。
- 用 LLaMA-Factory 在自己的电脑或其他算力环境微调 Qwen 模型,导出 GGUF。
- 把 GGUF 文件拷贝到 Jetson,用 llama.cpp 直接加载推理。
- 用 GPT-SoVITS 克隆音色,导出 ONNX,替换 TTS 模型。
- 组装硬件外壳,运行主控脚本完成语音交互全流程。
这套流程的好处是全部离线可用。出门在外没有网络时,鸭子依然能正常对话;关闭联网搜索功能后,所有推理都在本地完成,不用担心录音数据被上传。
5. 常见问题与排查技巧实录
复刻过程中我踩了不少坑,这里整理几个出现频率最高、也最容易劝退新人的问题,按排查难度从低到高排。
5.1 麦克风拾音效果差、唤醒率低
问题现象:喊鸭子名字十次有六七次不回应,或者对话过程中语音识别老是听错。
排查步骤:确认录音采样率是 16000Hz 单声道;检查麦克风是否被外壳遮挡;用arecord -l确认系统识别到 USB 麦克风;用speech-recognition自带的 VAD 检测看看唤醒前后的音量曲线,确认唤醒词之后确实有实质语音输入。
我的经验是:大部分唤醒率低的问题都出在物理层,麦克风的位置影响远大于算法参数。把麦克风从鸭壳内部移到鸭嘴附近,开孔朝外,唤醒率能提升 30% 以上。
5.2 推理速度慢,每句话要等很久
问题现象:提完问题后要等 5 秒以上才开始回答,体验很差。
排查步骤:用tegrastats确认 GPU 频率是否跑满,如果只有 300MHz 左右说明没开性能模式,用sudo nvpmodel -m 0切到最高功耗档。其次检查n_gpu_layers是否设为 -1,如果只加载了部分层到 GPU,推理速度会大打折扣。最后看看模型是否被 swap 到了内存,在htop里如果VIRT远大于物理内存,说明上下文开太大导致内存不足。
根据我的实测,7B Q4 模型在 Orin Nano 8GB 上,首 token 延迟大约 1 到 2 秒,如果超过 4 秒,基本是配置出了问题。
5.3 系统内存不足、编译常被杀
问题现象:安装依赖或转换模型时,进程被系统 OOM Killer 杀掉。
这是 Jetson 上非常典型的问题。处理方法是先检查可用内存和 swap:free -h。建议把 swap 文件设为 8GB 以上。在 25W 模式下,8GB 内存加 8GB swap 跑 llama.cpp 推理是够用的,但编译大项目时最好临时关掉其他占内存的服务。
5.4 蓝牙音箱爆音或延迟卡顿
问题现象:扬声器播放语音时经常爆音,或者声音忽快忽慢。
蓝牙音箱的延迟和丢包在语音回放上确实会有影响。我的解决方案是:调试阶段用 3.5mm 音频线直连一个 3W 小喇叭,只保留成品展示时才切换蓝牙音箱。这能大幅减少变量,方便定位问题。
6. 进阶方向:把 Microduck 拓展成你的私人 AI 助手
Microduck 跑通之后,它的可玩性才刚刚开始。根据我自己的探索和社区里的案例,分享几个性价比比较高的拓展方向。
6.1 接入 Home Assistant,让鸭子控制智能家居
通过 MQTT 协议把 Microduck 接入 Home Assistant,就可以用语音控制窗帘、灯光、空调,做一个“语音+桌面机器人”的智能家居入口。实现上只需要让主程序识别“打开客厅灯”这类意图,然后向 MQTT 主题发布控制指令。这个扩展对代码能力要求不高,但会让鸭子从“玩具”变成“工具”。
6.2 把 Facebook 聊天记录“喂”给鸭子
有个社区玩家开源了一个项目 QZoneArchive,原理是先通过平台的个人数据导出拿到聊天记录,清洗成 JSON 格式后,用嵌入模型检索相关片段,再注入系统提示词。效果是鸭子能聊你的往事,比如“你还记得去年你生日那天我去了哪里吗”,它能基于真实历史数据给出回答。这个扩展本质上是“检索增强生成”的个人化应用,贴近实际使用场景。
6.3 加入视觉能力
如果你愿意再花一点钱买一个 USB 摄像头,Microduck 就能获得视觉能力。用 Jetson 平台上的 YOLO 或 GroundingDINO 做实时目标检测,再用视觉语言模型做场景理解,可以让鸭子“看到”谁在跟它说话,甚至描述你桌上的物件。这个方向性能消耗更大,8GB 显存需要在视觉模型和语言模型之间做权衡,但对喜欢折腾的玩家来说,非常有吸引力。
6.4 打造多鸭协作系统
社区里已经有人把两三只 Microduck 放在同一个局域网里,通过 MQTT 共享对话状态,实现“一群鸭子开会”的效果。这个方向对理解分布式状态同步和消息队列很有帮助,也是比较有趣的展示项目。
我在实际使用中的体会是,Microduck 复刻最有价值的反而不是最终跑通的那一刻,而是在每一层链路调试时被迫去搞清楚的那些底层原理:为什么 ONNX 换成 TensorRT 之后速度翻倍,为什么量化位宽从 8bit 降到 4bit 之后显存占用少一半但困惑度只涨了一点点,为什么唤醒率在麦克风挪了个位置之后从六成升到九成。这些东西在任何一篇文档里都学不到,只有亲手做一遍才理解得最透彻。
最后再分享一个小技巧:如果你也想做语音人格定制,别一上来就追求大模型微调。先用系统提示词把人格设定清楚,跑够一周的日常对话,把实际交互中表现不好的句子录下来,整理成训练数据再做微调。这样既省时间,数据质量也远高于凭空想的样本。先让鸭子“像”你要的样子,再让它“是”你要的样子,这个顺序很重要。