
1. 缘起为什么要在4GB小板子上折腾大模型手里这块Jetson Orin Nano 4GB是我去年从二手渠道淘来的。当时想法很简单想做一个离线运行的语音助手能听懂人话、能调用本地工具、能记住上下文最好还能自己规划任务。市面上现成的方案要么依赖云端API要么需要至少16GB显存4GB这个档位基本属于“被遗忘的角落”。但恰恰是这个“被遗忘的角落”才是大多数个人开发者和学生党真正能摸到的硬件门槛。Jetson Orin Nano 4GB的官方标称AI算力是20 TOPSINT8GPU部分有512个CUDA核心和16个Tensor Core内存带宽68GB/s。这些数字放在桌面级显卡面前不值一提但它的功耗只有7W到15W体积比手掌还小价格在千元级别。对于想入门边缘AI部署、想理解大模型推理底层机制、想自己搭一个Agent玩的人来说这块板子是最好的教具。问题也很明显4GB内存是统一内存架构CPU和GPU共享。系统本身吃掉1GB左右留给模型和运行时的空间只有不到3GB。一个7B参数的模型FP16精度下光权重就要14GBINT4量化后也要3.5GB到4GB。这意味着你不可能直接把一个完整的7B模型塞进去跑必须做三件事抠内存、塞模型、养Agent。这三部曲不是随便起的名字而是我在反复失败中总结出的三个核心矛盾。抠内存解决的是“系统能不能活下来”的问题塞模型解决的是“模型能不能跑起来”的问题养Agent解决的是“跑起来之后能不能用”的问题。每一步都有坑每一步都有取舍每一步都需要你对底层原理有足够的理解才能做出正确决策。这篇文章面向的是手里有Jetson Orin Nano 4GB或者类似规格的ARM开发板、想跑本地大模型和Agent的开发者。如果你用的是8GB或16GB版本很多内存相关的技巧可以跳过但模型选型和Agent架构部分依然有参考价值。如果你用的是x86平台加独立显卡这篇文章的硬件部分不适用但量化策略和Agent设计思路是通用的。2. 抠内存让系统先活下来2.1 系统层面的内存瘦身Jetson Orin Nano 4GB出厂预装的是Ubuntu 20.04或22.04的L4T版本默认桌面环境是GNOME。这个桌面环境本身就要吃掉600MB到800MB内存加上各种后台服务开机后可用内存往往只剩2.5GB左右。对于要跑大模型的任务来说这个数字太紧张了。我做的第一件事是关掉图形界面改用纯命令行模式。具体操作是修改GRUB配置把默认启动目标从graphical.target改成multi-user.target。这一步能直接释放500MB以上的内存。如果你偶尔还需要图形界面可以保留但把显示管理器换成轻量级的比如LightDM配合XFCE比GNOME省一半内存。# 查看当前默认启动目标 systemctl get-default # 修改为命令行模式 sudo systemctl set-default multi-user.target # 如果需要临时切回图形界面 sudo systemctl start gdm3第二件事是清理不必要的后台服务。Jetson默认开启了很多与AI推理无关的服务比如蓝牙、打印服务、ModemManager、snapd等。我列了一个清单逐个确认后禁用# 查看内存占用前20的进程 ps aux --sort-%mem | head -20 # 禁用常见无用服务 sudo systemctl disable bluetooth sudo systemctl disable cups sudo systemctl disable ModemManager sudo systemctl disable snapd sudo systemctl disable unattended-upgrades这里有个细节需要注意Jetson的nvargus-daemon和nvpmodel服务不要动前者是摄像头相关的后者是功耗管理关掉可能导致GPU无法正常工作。另外jetson_clocks脚本可以用来锁定最高频率虽然会增加功耗但能避免频率波动导致的推理延迟抖动。2.2 交换空间的正确用法4GB内存跑大模型交换空间是绕不开的。但Jetson的存储是eMMC或NVMe SSD读写速度远低于内存如果模型权重被频繁换出换入推理速度会慢到无法接受。我的策略是把交换空间当作“安全垫”而不是“扩展内存”。具体做法是创建一个8GB的交换文件放在NVMe SSD上如果你用的是eMMC版本建议外接一个USB 3.0的SSD。然后把vm.swappiness参数调到10以下让系统尽量不主动换出内存页。只有在内存真正耗尽时才使用交换空间避免OOM Killer把进程杀掉。# 创建8GB交换文件 sudo fallocate -l 8G /mnt/ssd/swapfile sudo chmod 600 /mnt/ssd/swapfile sudo mkswap /mnt/ssd/swapfile sudo swapon /mnt/ssd/swapfile # 永久生效 echo /mnt/ssd/swapfile none swap sw 0 0 | sudo tee -a /etc/fstab # 调整swappiness echo vm.swappiness10 | sudo tee -a /etc/sysctl.conf sudo sysctl -p实测下来这个配置能让系统在加载模型时不至于直接崩溃但推理过程中如果触发大量交换token生成速度会从每秒几个降到每秒零点几个。所以交换空间只是保底真正的解决方案还是要把模型大小控制在内存容量以内。2.3 内存监控与预警在跑模型之前我习惯开一个内存监控窗口实时观察free -h和tegrastats的输出。tegrastats是Jetson特有的工具能看到GPU、CPU、内存、功耗的实时数据。特别是GR3D_FREQ这一项如果它频繁掉到0说明GPU在等待内存瓶颈在内存带宽而不是算力。# 每2秒刷新一次内存和GPU状态 watch -n 2 free -h tegrastats --interval 2000 | tail -1我还会写一个简单的脚本当可用内存低于300MB时自动发送警告避免在推理过程中突然OOM。这个脚本用cron每30秒跑一次逻辑很简单#!/bin/bash AVAILABLE$(free -m | awk /Mem:/ {print $7}) if [ $AVAILABLE -lt 300 ]; then echo 警告可用内存仅 ${AVAILABLE}MB /var/log/mem_alert.log # 可以在这里触发清理缓存或杀掉非关键进程 sync echo 3 | sudo tee /proc/sys/vm/drop_caches fi注意drop_caches会清空文件系统缓存短期内可能让IO变慢但在内存极度紧张时能救急。不要频繁执行否则会影响整体性能。3. 塞模型量化、格式与推理框架选型3.1 为什么是GGUF和llama.cpp在Jetson上跑大模型推理框架的选择直接决定了你能不能跑起来。我试过TensorRT、ONNX Runtime、PyTorch原生、llama.cpp、Ollama等方案最终稳定在llama.cpp加GGUF格式的组合上。原因有三第一GGUF是单文件格式模型权重、分词器、配置全部打包在一个文件里部署时只需要拷贝一个文件不需要额外的配置文件或依赖。这对于内存紧张的设备来说很重要因为少加载一个文件就少占一份内存。第二llama.cpp对ARM架构和CUDA的支持非常成熟。它原生支持Jetson的GPU加速通过CUDA后端可以把矩阵运算卸载到GPU上同时CPU端只负责调度和采样。实测在Orin Nano 4GB上7B Q4_K_M模型能跑到每秒5到8个token3B Q4_K_M能跑到每秒15到20个token。第三GGUF的量化方案非常丰富从Q2_K到Q8_0有几十种选择可以根据内存余量精确控制模型大小。而且llama.cpp支持运行时调整上下文长度、批处理大小等参数不需要重新编译模型。安装llama.cpp在Jetson上需要从源码编译因为官方预编译的二进制不包含CUDA后端。编译过程大概需要20到30分钟具体取决于你的散热条件。关键编译选项如下git clone https://github.com/ggerganov/llama.cpp cd llama.cpp mkdir build cd build cmake .. -DLLAMA_CUDAON -DLLAMA_CUBLASON -DCMAKE_CUDA_ARCHITECTURES87 make -j4这里的CMAKE_CUDA_ARCHITECTURES87是针对Orin系列Ampere架构的算力等级。如果你用的是Xavier系列需要改成72。编译完成后build/bin/目录下会生成llama-cli、llama-server等可执行文件。3.2 量化等级的选择与内存计算选量化等级本质上是在内存占用和模型质量之间做权衡。我整理了一个表格列出常见量化等级在7B和3B模型上的实际内存占用和推理速度基于Orin Nano 4GB实测模型规模量化等级文件大小加载后内存推理速度质量评价7BQ2_K2.8GB3.2GB4-6 t/s勉强可用逻辑能力下降明显7BQ3_K_M3.3GB3.7GB3-5 t/s接近OOM边缘不推荐7BQ4_K_M4.1GB4.5GB无法加载超出内存容量3BQ4_K_M1.9GB2.3GB15-20 t/s质量与速度平衡最佳3BQ5_K_M2.2GB2.6GB12-16 t/s质量略好速度可接受3BQ8_03.2GB3.6GB8-10 t/s质量最好但内存紧张1.5BQ4_K_M1.0GB1.3GB25-35 t/s速度快适合简单任务从表格可以看出7B模型在4GB板子上基本没有实用价值即使Q2_K能勉强加载推理速度和质量都难以接受。3B模型是甜点区Q4_K_M在内存和速度之间取得了最佳平衡。1.5B模型适合对速度要求高、任务简单的场景比如意图识别或文本分类。这里有个计算内存占用的经验公式模型加载后的内存 ≈ 文件大小 × 1.1 上下文缓存。上下文缓存的大小取决于你设置的n_ctx参数每个token大约占用n_embd × 2 × n_layer字节。对于3B模型n_embd3200n_layer26每个token大约占用166KB。如果设置n_ctx2048上下文缓存就要340MB。所以实际内存占用会比文件大小多出不少。# 加载3B Q4_K_M模型上下文2048GPU卸载所有层 ./llama-cli -m models/qwen2.5-3b-instruct-q4_k_m.gguf \ -n 512 -c 2048 -ngl 99 -t 4 --temp 0.7-ngl 99表示把所有层都卸载到GPU上。对于3B模型Orin Nano的4GB内存可以全部卸载GPU利用率能到80%以上。如果内存不够可以调低这个值让部分层留在CPU上计算但速度会明显下降。3.3 模型下载与格式转换GGUF模型可以从Hugging Face上直接下载搜索“模型名 GGUF”就能找到大量社区转换好的版本。我常用的是Qwen2.5系列和Llama3.2系列这两个系列在3B规模上质量最好而且对中文支持不错。下载时要注意区分Q4_K_M和Q4_0前者是K-quants量化质量更好但计算稍慢后者是传统量化速度稍快但质量略差。在Orin Nano上我推荐K-quants因为GPU加速能弥补计算量的增加。如果你手头只有PyTorch格式的模型可以用llama.cpp自带的转换脚本转成GGUF# 转换PyTorch模型为GGUF FP16 python convert_hf_to_gguf.py ./Qwen2.5-3B-Instruct --outfile qwen2.5-3b-fp16.gguf # 量化为Q4_K_M ./llama-quantize qwen2.5-3b-fp16.gguf qwen2.5-3b-q4_k_m.gguf Q4_K_M转换过程需要足够的磁盘空间和内存建议在PC上完成后再拷贝到Jetson上。直接在Jetson上转换FP16模型可能会因为内存不足而失败。提示下载模型时注意检查文件的SHA256校验值避免下载到损坏的文件。GGUF文件损坏的表现是加载时报“invalid magic number”或“tensor data out of bounds”。4. 养Agent从裸模型到可用助手4.1 Agent的基本架构与内存预算模型跑起来只是第一步要让模型变成能用的Agent还需要一套完整的运行时框架。Agent的核心组件包括对话管理、工具调用、记忆存储、任务规划。每个组件都要占内存所以在4GB板子上必须精打细算。我的Agent架构是这样的llama.cpp的llama-server作为推理后端提供一个HTTP接口Python写一个轻量级的Agent框架负责提示词组装、工具调用解析、记忆管理工具层包括系统命令执行、文件读写、网络请求等。整个框架跑起来大概占200MB到300MB内存加上模型本身的2.3GB总内存占用在2.6GB左右留出1GB给系统和缓冲。# 启动llama-server监听本地8080端口 ./llama-server -m models/qwen2.5-3b-instruct-q4_k_m.gguf \ -c 2048 -ngl 99 -t 4 --host 127.0.0.1 --port 8080 \ --chat-template chatml--chat-template chatml指定了对话模板Qwen系列用的是ChatML格式。如果模板不对模型输出的格式会混乱工具调用解析会失败。这是很多人踩过的坑模型明明能对话但Agent就是无法正确调用工具八成是模板没设对。4.2 工具调用的实现与优化工具调用是Agent的核心能力。我的实现方式是在系统提示词里定义工具列表模型输出特定格式的JSON来请求调用工具Agent框架解析JSON后执行工具再把结果拼回对话历史。系统提示词大概长这样你是一个运行在Jetson Orin Nano上的AI助手。你可以使用以下工具 1. execute_command: 执行shell命令。参数{command: 要执行的命令} 2. read_file: 读取文件内容。参数{path: 文件路径} 3. write_file: 写入文件。参数{path: 文件路径, content: 内容} 当你需要调用工具时输出格式为 tool_call{name: 工具名, arguments: {...}}/tool_call 工具执行结果会以tool_result标签返回。这个提示词的设计要点是工具描述要简洁参数格式要明确输出格式要固定。3B模型的理解能力有限提示词越复杂它越容易出错。我试过用更复杂的ReAct格式结果模型经常把思考过程和工具调用混在一起解析成功率不到50%。换成这种简单的JSON格式后解析成功率能到85%以上。解析工具调用的Python代码import json import re def parse_tool_call(text): pattern rtool_call(.*?)/tool_call match re.search(pattern, text, re.DOTALL) if not match: return None try: return json.loads(match.group(1)) except json.JSONDecodeError: return None这里有个细节模型有时会输出不完整的JSON比如少了一个大括号。我的处理方式是先用正则提取再用json.loads解析失败后尝试补全括号再解析。如果还是失败就把错误信息返回给模型让它重新生成。实测下来加上重试机制后工具调用的成功率能到95%以上。4.3 记忆管理的轻量级方案Agent需要记忆才能进行多轮对话和任务规划。但4GB内存不可能把完整对话历史都塞进上下文。我的方案是分层记忆短期记忆保留最近5轮对话长期记忆用向量数据库存储关键信息。短期记忆直接拼在提示词里占用上下文窗口。5轮对话大概500到800个token加上系统提示词和工具定义总共1500个token左右在2048的上下文窗口里还有余量。长期记忆用chromadb或sqlite-vec把对话摘要和重要事实存成向量。当用户提到相关话题时检索出最相似的3条记忆拼到提示词里。这个方案的内存占用很小向量数据库本身只占几十MB检索时加载的向量也只有几KB。import chromadb client chromadb.PersistentClient(path./memory_db) collection client.get_or_create_collection(agent_memory) def save_memory(text, metadata): collection.add( documents[text], metadatas[metadata], ids[str(hash(text))] ) def recall_memory(query, n_results3): results collection.query( query_texts[query], n_resultsn_results ) return results[documents][0] if results[documents] else []注意向量数据库的嵌入模型也需要内存。如果用sentence-transformers最小的模型也要100MB左右。我的做法是用llama.cpp自带的嵌入功能把嵌入计算也交给同一个模型省去额外加载嵌入模型的内存开销。4.4 Agent循环与超时控制Agent的执行循环是用户输入 → 组装提示词 → 模型推理 → 解析输出 → 如果有工具调用则执行 → 把结果拼回提示词 → 再次推理 → 直到模型输出最终答案。这个循环必须有超时控制否则模型可能陷入死循环反复调用同一个工具。我的设置是最多5轮工具调用每轮推理超时30秒总超时120秒。超过限制就强制终止返回错误信息。import time def agent_loop(user_input, max_turns5, timeout120): start_time time.time() messages [{role: system, content: SYSTEM_PROMPT}] messages.append({role: user, content: user_input}) for turn in range(max_turns): if time.time() - start_time timeout: return 任务超时请简化你的请求。 response call_llama_server(messages) tool_call parse_tool_call(response) if tool_call is None: return response result execute_tool(tool_call) messages.append({role: assistant, content: response}) messages.append({role: user, content: ftool_result{result}/tool_result}) return 达到最大工具调用次数任务终止。这个循环里有个容易忽略的点每次工具调用后对话历史会增加上下文窗口会被逐渐填满。当接近2048 token时必须截断最早的对话。我的做法是保留系统提示词和最近3轮对话把更早的内容压缩成摘要。摘要用模型自己生成虽然会损失一些细节但能保证循环继续。5. 常见问题与排查技巧实录5.1 模型加载失败与内存不足问题现象执行llama-cli时提示failed to allocate buffer或out of memory。排查思路先用free -h确认可用内存再检查模型文件大小。如果文件大小加上上下文缓存超过可用内存必然失败。解决方案有三个换更小的量化等级、降低n_ctx、减少-ngl让部分层留在CPU。实操案例我试过在4GB板子上加载7B Q4_K_M模型文件大小4.1GB加载到80%时OOM。换成3B Q4_K_M后顺利加载。后来不死心又试了7B Q2_K文件2.8GB能加载但推理速度只有每秒3个token而且输出质量很差经常答非所问。最终结论是4GB板子的实用上限就是3B Q4_K_M。5.2 工具调用解析失败问题现象模型输出了工具调用的意图但Agent框架解析不出JSON导致工具没有执行。排查思路先检查--chat-template是否设置正确。Qwen系列用ChatMLLlama3系列用Llama3模板设置错了模型输出格式会混乱。然后检查系统提示词里的工具定义是否清晰参数格式是否明确。最后检查模型的温度参数温度太高会导致输出随机性增加JSON格式容易出错。实操案例我用Llama3.2-3B时一开始没设--chat-template模型输出里混入了大量特殊token解析成功率不到30%。设置--chat-template llama3后成功率提升到80%。后来把温度从0.8降到0.3成功率稳定在90%以上。5.3 推理速度突然下降问题现象Agent运行一段时间后token生成速度从每秒15个降到每秒3个。排查思路用tegrastats观察GPU频率和内存使用。如果GR3D_FREQ掉到0说明GPU在等待内存可能是交换空间被触发。如果GPU频率正常但速度慢可能是上下文窗口满了模型在处理超长序列。实操案例我遇到过一次速度骤降tegrastats显示GPU频率正常但free -h显示可用内存只剩100MB交换空间用了2GB。原因是对话历史太长上下文缓存占满了内存。清理对话历史后速度恢复。后来我加了自动截断机制当上下文超过1800 token时自动压缩最早的历史。5.4 常见问题速查表问题现象可能原因解决方案加载模型时OOM模型太大或上下文太长换小量化等级、降低n_ctx、减少ngl工具调用不执行模板错误或提示词不清检查chat-template、简化工具定义推理速度慢内存不足触发交换清理内存、缩短上下文、关闭无用服务输出格式混乱温度太高或模板错误降低温度、检查chat-templateAgent死循环工具返回结果不明确加超时控制、限制最大轮次模型答非所问量化等级太低换更高量化等级或更小模型5.5 独家避坑技巧第一个技巧在Jetson上编译llama.cpp时加上-DLLAMA_CUDA_F16ON可以启用FP16计算速度能提升10%到15%。但要注意这个选项会增加显存占用如果内存紧张就不要开。第二个技巧用llama-server而不是llama-cli来跑Agent。llama-server支持并发请求和KV缓存复用多轮对话时不需要重新计算历史token的KV能省不少时间。实测下来第二轮对话的响应速度比第一轮快30%以上。第三个技巧把模型文件放在NVMe SSD上不要放在eMMC或SD卡上。模型加载时需要大量顺序读取eMMC的读取速度只有NVMe的三分之一加载时间会从10秒变成30秒。如果用的是eMMC版本强烈建议外接一个USB 3.0的SSD。第四个技巧定期用sudo jetson_clocks锁定最高频率。Orin Nano默认会根据负载动态调频推理时频率波动会导致延迟不稳定。锁定后功耗会增加2W到3W但推理速度更稳定。如果散热条件不好可以配合nvpmodel调整功耗模式。6. 实际运行效果与后续扩展这套方案跑通后我的Jetson Orin Nano 4GB能稳定运行一个3B参数的Agent支持工具调用、多轮对话和长期记忆。实测响应速度简单问答1到2秒工具调用3到5秒复杂任务规划8到12秒。这个速度对于个人助手场景完全够用比如查天气、设提醒、控制智能家居、执行简单的系统管理任务。内存占用方面系统加桌面环境我保留了轻量级桌面占1.2GBllama-server加模型占2.4GBAgent框架占0.3GB总共3.9GB刚好卡在4GB边缘。如果关掉桌面环境能多出500MB余量可以跑更大的上下文或更高的量化等级。后续我打算做两个扩展一是接入语音输入输出用Whisper.cpp做语音识别用Piper做语音合成这两个模型都很小加起来不到500MB二是增加多Agent协作用两个3B模型分别负责规划和执行通过消息队列通信。不过多Agent会成倍增加内存占用可能需要换8GB版本的Orin Nano才能跑得舒服。如果你也在用类似的低配硬件跑大模型我的建议是不要追求模型规模3B足够用不要追求量化精度Q4_K_M是甜点不要追求功能大而全先把一个场景跑通。4GB板子的价值不在于跑分而在于让你真正理解大模型推理的每一个环节从内存管理到量化策略从提示词工程到Agent架构。这些经验在换到更大硬件后依然适用而且会让你对资源的利用更加精细。