
1. 项目缘起当机器人需要“耳朵”和“大脑”最近在折腾一个挺有意思的项目给Reachy Mini机器人装上一个能离线对话的“大脑”。Reachy Mini是一款开源的桌面级协作机器人手臂设计精巧可玩性很高但它的“智能”主要体现在精准的运动控制和预设的程序上。我一直琢磨着能不能让它更“聪明”一点比如能听懂我的语音指令然后自己思考一下再做出回应而不是单纯地执行死板的代码。这个想法听起来很酷但实现起来有几个硬骨头要啃。首先语音识别和语言模型通常都是云服务延迟、网络依赖和隐私问题都是痛点。其次Reachy Mini本身的计算资源有限它依赖一个外部计算单元比如我们这次用的reComputer Mini一款基于NVIDIA Jetson Orin NX的嵌入式AI计算机。虽然Orin NX性能不俗但要在上面跑一个能实时交互的大语言模型LLM对模型选型、部署优化都是不小的挑战。最终我决定采用Ollama这个方案。Ollama的出现让在本地运行、管理大语言模型变得像docker run一样简单。它封装了模型加载、推理优化等一系列复杂操作提供了简洁的REST API。我们的目标就清晰了在reComputer Mini上部署Ollama并加载一个合适的轻量级LLM然后让Reachy Mini通过语音接口麦克风采集音频转成文本后发送给本地的Ollama服务拿到文本回复后再通过语音合成TTS播报出来甚至可以根据回复内容触发一些简单的动作。这不仅仅是“部署一个模型”而是一个完整的边缘AI交互闭环。下面我就把整个从环境准备、模型选型、部署优化到最终集成的过程以及中间踩过的坑和解决方案详细拆解一遍。2. 硬件与基础环境踩点工欲善其事必先利其器。在开始敲代码之前我们必须对硬件平台和基础软件栈有透彻的了解这能避免很多后续的麻烦。2.1 reComputer Mini (Jetson Orin NX) 性能摸底reComputer Mini的核心是NVIDIA Jetson Orin NX 16GB模块。这不是一台普通的迷你电脑而是一个为边缘AI设计的嵌入式系统。CPU GPU它拥有8核ARM Cortex-A78AE CPU和1024个CUDA核心的Ampere架构GPU。对于LLM推理GPU的Tensor Core和显存带宽是关键。16GB的共享内存LPDDR5是宝贵资源模型和运行时的数据都放在这里。存储我手上的版本配备的是256GB NVMe SSD。速度没问题但容量需要精打细算。一个7B参数的模型量化后可能就要4-8GB再加上系统、Ollama本身和其他依赖空间并不宽裕。功耗与散热Orin NX的功耗墙是可配置的15W-25W。在持续进行LLM推理时功耗和发热会上去。reComputer Mini的被动散热设计能否压住满载的Orin NX需要实际测试。过热会导致GPU降频显著影响推理速度。实操心得一风扇与功耗模式Jetson设备默认的nvpmodel功耗模式可能不是最高性能。我首先通过sudo nvpmodel -m 0和sudo jetson_clocks命令将其设置为最大性能模式MODE 0并锁定最高时钟。同时密切关注tegrastats命令输出的温度和频率信息。如果发现温度持续超过85°C就需要考虑增加一个USB小风扇辅助散热或者适当调整功耗模式如-m 2在性能和温度间取得平衡。2.2 Reachy Mini 的连接与通信Reachy Mini通过一根USB-C线缆与reComputer Mini连接。它运行着一个基于ROS 2的软件栈。我们的语音LLM模块本质上是一个独立的服务需要与Reachy的ROS 2系统进行通信。通信方式最优雅的方式是利用ROS 2的发布/订阅机制。我们可以创建一个新的ROS 2节点这个节点负责音频采集、调用Ollama API、语音合成并将最终的“意图”例如“拿起红色方块”以ROS 2消息的形式发布到特定话题Topic。Reachy原有的控制节点订阅这个话题即可执行相应动作。备选方案如果不想深入ROS 2也可以用更简单的HTTP或WebSocket在进程间通信。但ROS 2的方式更符合机器人系统的架构扩展性更好。基础软件栈准备系统从Seeed官网下载并为reComputer Mini刷写最新的JetPack 6.0镜像。JetPack包含了Ubuntu、CUDA、cuDNN、TensorRT等全套NVIDIA生态工具是必须的。Python环境使用venv或conda创建一个独立的Python虚拟环境避免污染系统Python。我习惯用conda因为方便管理不同版本的Python和库。conda create -n reachy-llm python3.10 conda activate reachy-llm关键库pyaudio/sounddevice: 用于音频采集和播放。speechrecognition封装了多种语音识别引擎我们先用离线的Vosk后面会讲。pyttsx3或edge-tts: 文本转语音。pyttsx3完全离线但声音机械edge-tts调用在线服务质量好但有延迟。根据需求选择。requests: 调用Ollama的API。ros-humble-*(如果采用ROS 2方案)需要安装ROS 2 Humble版本对应的Python客户端库。3. Ollama部署从龟速下载到丝滑运行Ollama的安装本身很简单一行命令的事。但国内开发者遇到的第一只“拦路虎”就是模型下载速度极慢甚至失败。Ollama默认从官方仓库拉取模型这对国内网络是巨大的考验。3.1 安装与配置国内镜像Ollama的安装命令是curl -fsSL https://ollama.com/install.sh | sh安装完成后ollama命令应该就可以用了。但直接运行ollama run llama3.2:1b这样的命令你会陷入漫长的等待。解决方案是使用国内镜像源。这里有两个层面Ollama二进制与本身更新镜像这个影响不大主要是一次性安装。模型仓库镜像这是关键我们需要修改Ollama的配置让它从一个国内的镜像站下载模型。具体操作 Ollama的服务配置文件通常位于/etc/systemd/system/ollama.service。我们需要修改其环境变量。sudo systemctl stop ollama sudo vim /etc/systemd/system/ollama.service在[Service]部分找到Environment行或添加如下环境变量指向一个可用的国内镜像例如一些社区维护的或通过代理加速的地址请注意使用合法合规的镜像源EnvironmentOLLAMA_MODELS镜像站地址/models例如可以设置为一些高校或机构提供的镜像此处需替换为实际可用且合规的镜像地址由于网络环境变化快建议搜索当前可用的ollama国内镜像获取最新地址。修改后保存并重启服务sudo systemctl daemon-reload sudo systemctl start ollama实操心得二模型存放目录迁移默认情况下Ollama将模型下载到~/.ollama/models。如果你的系统盘如reComputer Mini的NVMe空间紧张可以将其迁移到外接的大容量USB SSD或SD卡上。首先停止Ollama服务sudo systemctl stop ollama。然后将整个~/.ollama目录移动到新位置例如/media/your-ssd/ollama。最后创建一个符号链接ln -s /media/your-ssd/ollama ~/.ollama。重启服务sudo systemctl start ollama。这样Ollama会无缝地使用新位置的模型。3.2 模型选型在精度与速度间走钢丝这是整个项目的核心决策点。Orin NX 16GB的显存跑动百亿参数模型很吃力我们需要在模型大小、推理速度、回答质量三者间找到最佳平衡。评估维度参数量1B, 3B, 7B, 14B。参数量越大通常能力越强但所需显存和计算量也呈指数级增长。量化等级Q4_K_M, Q5_K_S, Q8_0等。量化能大幅减少模型体积和内存占用但会带来一定的精度损失。Q4_K_M是精度和速度比较均衡的选择。架构与训练数据不同的模型Llama, Gemma, Qwen, DeepSeek等各有侧重。对于英文场景Llama 3.2系列指令跟随能力强对于中文场景Qwen 2.5或DeepSeek系列是更好的选择。我的选择过程初步筛选目标是在16GB内存下能流畅运行。一个7B参数的模型使用Q4_K_M量化后加载后内存占用约5-8GB留给系统和其他进程的空间还算充足。因此7B级别是安全起点。实际测试我下载了llama3.2:1b、llama3.2:3b和qwen2.5:7b的Q4量化版进行对比。1b/3b模型速度极快100 tokens/s但回答内容简单逻辑性弱经常“胡言乱语”不适合多轮复杂对话。qwen2.5:7b-instruct-q4_K_M速度适中在Orin NX上约20-30 tokens/s中文理解能力强指令跟随性好能进行连贯的对话。这个速度对于语音交互人说一句机器想几秒再回答是可以接受的。最终定版我选择了qwen2.5:7b-instruct-q4_K_M作为主力模型。对于纯英文场景llama3.2:7b-instruct也是优秀的选择。启动与测试模型# 拉取模型配置好镜像后速度应该很快 ollama pull qwen2.5:7b-instruct-q4_K_M # 以交互方式运行测试 ollama run qwen2.5:7b-instruct-q4_K_M在交互界面里你可以问它一些问题比如“介绍一下你自己”来测试模型是否正常工作。4. 构建语音交互闭环有了本地运行的“大脑”Ollama Qwen接下来就要给它配上“耳朵”语音识别和“嘴巴”语音合成并打通整个流程。4.1 离线语音识别ASR方案选型在线ASR如Google Speech Recognition需要网络不符合我们“全本地”的宗旨。离线方案主要有Vosk 开源支持多种语言模型小几十到几百MB精度尚可对硬件要求低。非常适合嵌入式场景。Whisper (OpenAI) 精度极高支持多语言但模型大仅tiny模型约75MBbase约140MB越大精度越高推理需要更多计算资源。即使使用whisper.cpp这样的C移植优化版在Orin NX上实时转录也可能有延迟。NVIDIA Riva 工业级方案精度和速度俱佳但部署复杂可能需要额外的授权。权衡之下我选择了Vosk。理由很简单轻量、够用、易集成。对于近距离、环境噪声不大的桌面交互场景Vosk的识别率已经足够。我们从Vosk官网下载适合的中英文小模型例如vosk-model-small-en-us-0.15和vosk-model-small-cn-0.22解压即可使用。Python代码示例使用speech_recognition库封装Voskimport speech_recognition as sr def listen_with_vosk(model_pathpath/to/vosk-model-small-cn-0.22): recognizer sr.Recognizer() microphone sr.Microphone() with microphone as source: print(请说话...) recognizer.adjust_for_ambient_noise(source) # 调整环境噪声 audio recognizer.listen(source, timeout5, phrase_time_limit10) # 录音 try: # 使用Vosk离线识别 text recognizer.recognize_vosk(audio, languagezh-cn, model_pathmodel_path) # Vosk返回的是JSON字符串需要解析 import json text_result json.loads(text).get(text, ) print(f识别结果: {text_result}) return text_result except sr.UnknownValueError: print(无法识别音频) return except sr.RequestError as e: print(fVosk服务错误: {e}) return 注意speech_recognition库对Vosk的支持可能需要额外安装vosk的Python绑定 (pip install vosk)。确保模型路径正确。4.2 调用本地Ollama APIOllama在启动后会在本地11434端口提供一个REST API。我们的程序通过HTTP POST请求与它交互。核心API调用import requests import json def ask_ollama(prompt, modelqwen2.5:7b-instruct-q4_K_M, ollama_hosthttp://localhost:11434): url f{ollama_host}/api/generate payload { model: model, prompt: prompt, stream: False, # 我们一次性获取完整回复非流式 options: { temperature: 0.7, # 控制创造性越高回答越随机 top_p: 0.9, num_predict: 256 # 生成的最大token数控制回复长度 } } headers {Content-Type: application/json} try: response requests.post(url, datajson.dumps(payload), headersheaders, timeout60) # 设置超时 response.raise_for_status() result response.json() return result.get(response, ).strip() except requests.exceptions.RequestException as e: print(f调用Ollama API失败: {e}) return 抱歉我现在有点困惑。参数调优心得temperature 对于机器人指令交互建议设置在0.6-0.8之间既能保证一定的多样性又不会太天马行空。设为0.1会非常确定和保守。num_predict 根据你的需求设置。语音回复不宜过长128-256个token通常能生成1-3句话足够了。系统提示词System Prompt 这是塑造模型行为的关键你可以在prompt中嵌入系统指令。例如system_prompt 你是一个安装在Reachy Mini机器人上的智能助手。你的回答应该简洁、友好、直接最好在一两句话内完成。如果用户要求你控制机器人请将控制意图总结成简单的JSON格式例如 {action: pick_up, object: red block}。 full_prompt f{system_prompt}\n\n用户说{user_input}\n助手通过精心设计的系统提示词可以极大地约束模型输出使其更符合机器人助手的身份。4.3 文本转语音TTS输出为了让Reachy Mini“开口说话”我们需要TTS。方案选择完全离线pyttsx3 无需网络立即响应但声音机械、生硬缺乏情感。在线服务edge-tts 调用微软的Edge浏览器TTS服务声音自然支持多种语言和音色但需要网络且有轻微延迟。考虑到reComputer Mini通常处于联网环境且语音交互体验很重要我选择了edge-tts。它可以通过pip install edge-tts安装。代码示例import asyncio import edge_tts import pygame # 用于播放音频 async def text_to_speech_and_play(text, voicezh-CN-XiaoxiaoNeural): # 创建TTS对象并合成语音 communicate edge_tts.Communicate(text, voice) audio_data b async for chunk in communicate.stream(): if chunk[type] audio: audio_data chunk[data] # 使用pygame播放音频字节流 pygame.mixer.init() import io audio_stream io.BytesIO(audio_data) pygame.mixer.music.load(audio_stream) pygame.mixer.music.play() while pygame.mixer.music.get_busy(): pygame.time.Clock().tick(10)注意 首次使用edge-tts时它会下载所选语音的配置文件请确保网络通畅。pygame在这里仅用于简单播放你也可以用pyaudio来播放原始的PCM/WAV数据。5. 系统集成与性能优化实战将各个模块拼装起来形成一个稳定、可用的系统才是真正的挑战。5.1 构建主循环与状态机一个简单的语音交互主循环逻辑如下import time def main_loop(): print(Reachy Mini 语音助手已启动等待唤醒...) # 这里可以加入唤醒词检测例如用Vosk持续监听特定词“嘿Reachy” wake_word 嘿Reachy while True: # 1. 持续监听直到检测到唤醒词 user_said listen_for_wake_word(wake_word) # 需要实现一个持续监听的函数 if not user_said: continue print(f唤醒词检测到) # 2. 播放提示音可选 play_beep() # 3. 监听用户指令5-10秒 print(请说出您的指令...) user_input listen_with_vosk(timeout10) if not user_input: print(未检测到指令。) continue # 4. 调用LLM生成回复 print(f用户指令: {user_input}) print(思考中...) llm_response ask_ollama(user_input) # 5. 解析LLM回复看是否有控制指令 robot_action parse_action_from_response(llm_response) # 需要实现解析逻辑 if robot_action: # 通过ROS 2或HTTP发送控制指令给Reachy send_to_reachy(robot_action) # 6. 将LLM的文本回复转为语音并播放 print(f助手回复: {llm_response}) asyncio.run(text_to_speech_and_play(llm_response)) time.sleep(1) # 短暂间隔防止误触发这个循环包含了唤醒、监听、思考、行动、反馈的全过程。parse_action_from_response函数需要根据你和LLM约定的格式比如前面提到的简单JSON来解析文本提取控制指令。5.2 性能瓶颈分析与优化在reComputer Mini上运行这个管道性能瓶颈可能出现在ASRVosk延迟 Vosk识别本身很快1秒。瓶颈主要在录音端点检测VAD和网络麦克风的延迟上。使用高质量的USB麦克风并调整speech_recognition的energy_threshold和pause_threshold参数可以减少无效监听时间。LLM推理速度 这是最主要的延迟来源。Qwen-7B在Orin NX上生成256个token可能需要5-15秒取决于temperature和上下文长度。优化手段一使用numa和线程绑定。通过numactl命令将Ollama进程绑定到特定的CPU核心并确保其使用离GPU最近的内存节点可以减少内存访问延迟。numactl --cpunodebind0 --membind0 ollama serve优化手段二调整Ollama运行参数。在运行模型时可以指定使用的GPU层数。对于7B模型可以尝试OLLAMA_NUM_GPU5050%的GPU资源或更高通过环境变量或Ollama的Modelfile配置。优化手段三精简上下文。Ollama API的context会保留对话历史。对于单轮指令可以不传历史或者只保留最近几轮以缩短处理时间。TTS延迟edge-tts的合成和下载需要网络往返可能有1-3秒延迟。可以考虑预加载常用提示音或者使用更轻量的离线TTS如pyttsx3作为备选。实操心得三监控与日志在开发过程中务必加入详细的日志记录记录每个环节的耗时ASR耗时、LLM推理耗时、TTS耗时。这能帮你精准定位瓶颈。可以使用Python的time模块简单记录import time start time.time() # ... 执行某个步骤 ... elapsed time.time() - start print(f步骤XXX耗时: {elapsed:.2f}秒)5.3 与Reachy Mini的ROS 2集成进阶如果想让LLM的控制指令直接驱动机器人就需要与ROS 2通信。创建自定义ROS 2消息 定义一个简单的RobotCommand.msg包含动作类型、目标物体等字段。编写LLM桥接节点 将上面主循环中的send_to_reachy函数改写成发布ROS 2消息。import rclpy from rclpy.node import Node from std_msgs.msg import String # 或你的自定义消息 class LLMBridgeNode(Node): def __init__(self): super().__init__(llm_bridge) self.publisher_ self.create_publisher(String, robot_command, 10) def send_command(self, command_str): msg String() msg.data command_str self.publisher_.publish(msg) self.get_logger().info(f发布指令: {command_str}) # 在主循环中初始化并使用这个节点修改Reachy控制节点 让原有的Reachy控制节点订阅robot_command话题解析消息并执行对应的动作如移动到某位置、抓取等。这样一个完整的、本地化的、能听会思考还能行动的Reachy Mini就诞生了。你可以对它说“嘿Reachy把那个蓝色的马克杯递给我”它经过思考LLM推理可能会回复“好的我这就去拿蓝色的马克杯”同时通过ROS 2发布控制指令驱动机械臂完成动作。6. 踩坑记录与未来展望这个项目从构想到跑通花了差不多一周的业余时间中间踩的坑不少。最大的坑Ollama模型加载失败与CUDA内存不足最初直接尝试运行14B的模型导致Ollama崩溃报错CUDA out of memory。即使换用7B模型有时在长时间对话后由于上下文缓存增长也会出现内存不足。解决方案就是严格量化Q4和监控上下文长度。可以通过Ollama的API (/api/ps)查看模型运行状态和内存使用情况。另一个坑音频设备冲突reComputer Mini的音频输入输出可能默认不是USB麦克风/音箱。需要通过arecord -l和aplay -l列出设备并在代码中指定正确的设备索引。pyaudio或sounddevice库都需要这个参数。关于“智能”的思考目前这套系统LLM更像是一个“语言理解与生成模块”。它的“思考”是基于统计概率的文本生成并非真正的认知。让它直接控制机器人执行复杂、有安全风险的动作是不安全的。更合理的架构是LLM负责将自然语言解析为结构化的“意图”Intent和“槽位”Slots例如{intent: “pick_up”, object: “red block”, location: “table”}。然后由一个更可靠、经过严格验证的“技能引擎”或“动作规划器”来执行这个结构化指令。LLM不直接发控制指令而是发高级任务描述。未来可以考虑以下几个方向深化多模态输入 为reComputer Mini连接一个摄像头让LLM不仅能“听”还能“看”。结合视觉大模型VLM可以实现“拿起你看到的那个红色的东西”这类指令。本地知识库 利用Ollama的Modelfile功能为模型注入关于Reachy Mini特定技能、物体名称的私有知识让它更专业。流式响应与打断 实现LLM的流式输出并合成语音让回复可以被打断交互更自然。更轻量的模型 持续关注3B甚至1.5B参数级别的新模型在精度损失可接受的前提下追求更快的响应速度。在资源受限的边缘设备上部署LLM并实现实时交互是一次充满挑战但也极具成就感的探索。它打破了“大模型必须上云”的思维定式为构建真正私密、低延迟、可定制的嵌入式智能体打开了大门。希望这篇详尽的记录能给想在类似硬件上玩转AI的伙伴们一些切实的参考。