把 125B 的 MoE 大模型塞进一块 Jetson AGX Thor听起来像把 F1 发动机装进卡丁车。第一次跟朋友提这个部署计划对方第一反应是“显存都不够吧”等我跑通以后他第二个问题是“MoE 的参数是不是不用全进显存”。这两个问题正好覆盖了整件事的两个核心——硬件怎么选、模型怎么拆。这篇文章就把我这轮部署的完整过程、关键参数和踩过的坑一次说清楚。适合手里有 Jetson AGX Thor或类似大内存边缘设备的开发者、做边缘 AI 落地的工程师以及单纯想搞明白 MoE 部署成本的人。1. Jetson AGX Thor 的硬件底子统一内存才是 MoE 的甜点1.1 从 Orin 到 Thor升级的不只是算力我最早是在 Jetson AGX Orin 上折腾大模型的。Orin 那会儿的 204GB/s 内存带宽、64GB/128GB 统一内存跑 7B 到 14B 的稠密模型已经比较吃力跑到 32B 基本是在走钢丝。Thor 这批工程样板拿到手后最大的感受是它的定位变了——NVIDIA 这次明显是冲着机器人、自动驾驶这类需要“感知-决策-大模型”一体化的场景去的所以它不再是一个只能跑视觉模型的 NPU 盒子而是一个能真正托管百亿级参数大模型的高吞吐边缘计算平台。按照 NVIDIA 官方在 GTC 上披露的口径Thor 的算力相比 Orin 有数倍提升稀疏算力达到了 2000 TOPS 级别。这个数字对纯视觉任务来说其实过剩对大模型推理也很有意义但我后面会说到真正让 125B MoE 模型能在 Thor 上落地的不是峰值算力而是它那套统一内存设计。很多做服务器推理的同事一听到“边缘设备跑大模型”第一反应是看显存。但 Jetson 系列从来不是独立显存架构它把 CPU、GPU、DLA深度学习加速器以及大容量内存封装在同一套系统里CPU 和 GPU 访问的是同一块物理内存。到了 Thor 这一代内存容量进一步提升工程样板给了 256GB 的统一内存这个容量恰好跨过了百亿级 MoE 模型的驻留门槛。顺带说一句拿到板子后不要急着装环境第一步先确认你手上的固件版本和 JetPack 版本。Thor 对应的 JetPack 6.x 在基础系统、CUDA 版本和内核驱动上和 Orin 不完全一样如果拿 Orin 的老镜像硬刷会出各种奇怪问题。官方 SDK Manager 刷机流程走一遍确认nvcc -V和nvidia-smi都能正常输出再接外设。1.2 统一内存CPU 和 GPU 在同一张桌上干活要理解为什么统一内存对 MoE 这么关键得先看传统服务器是怎么跑大模型的。一台 8 卡 H100 服务器每张卡有 80GB HBM 显存CPU 侧还有几百 GB 内存。GPU 要算某个张量得先把数据从 CPU 内存拷进显存计算完再拷回去。这个搬运动作走 PCIe带宽和延迟都不小。Jetson 的统一内存等于把“显存”和“内存”合并成了一个池子CPU 和 GPU 共用同一份物理地址空间。用生活里的例子说传统架构像厨房和餐厅分开菜做好了还得从厨房端到餐桌统一内存像一家人坐在同一张桌子边吃饭菜就在桌上谁都能直接夹。对大模型推理来说这个差异几乎是决定性的。因为 125B 模型的所有权重必须常驻在内存里而推理时每一层都要频繁读取权重矩阵。如果权重放在“远程”内存里每次计算都要跨 PCIe 搬运吞吐会低到没法用统一内存则让 GPU 直接用物理地址访问少了数据搬移的开销。Thor 的内存带宽虽然不能跟 H100 的 HBM 比但在边缘设备里已经足够支撑 125B MoE 模型以可用速度生成。1.3 算力和带宽的账要分开算跑大模型的人容易犯一个错只看 TOPS不看内存带宽。MoE 模型恰恰是内存带宽敏感型的。因为每个 token 生成时要读取被激活的那部分专家权重而不是像稠密模型那样把所有参数都算一遍所以“单位 token 需要的访存量”直接决定生成速度。这里有个简单公式可以心里估算生成速度 ≈ 内存带宽 ÷ 每个 token 读取的参数量。假设我们跑的 125B MoE 模型每个 token 激活约 20B 参数用 FP8 量化后每个 token 要读 20GB 权重如果 Thor 的可用内存带宽在 200GB/s 左右那单序列生成速度就是 10 token/s 上下。这个数字听着不高但已经是边缘设备的正常水平了。所以别指望 125B MoE 在 Thor 上跑出数据中心那种 100 token/s 的速度。我们的目标不是跟服务器比绝对吞吐而是让一个足够聪明的模型能部署在车上、机器人上、产线边上在断网环境下也能工作。理解了带宽这笔账后面调参时就不会被“算力这么高怎么才这么慢”迷惑。2. 125B MoE 模型的参数真相显存要装全部算力只动一部分2.1 MoE 是怎么“以小博大”的MoEMixture of Experts混合专家的核心思路是把一个 Transformer 模型里的前馈网络FFN层替换成多个并行的“专家”子网络再加一个路由器router/gating network来决定每个 token 激活哪些专家。拿一个典型的 8 专家 MoE 来说输入 token 经 router 计算出一个分数分布选出 top-2 专家然后把 token 送到这两个专家里计算最后把结果按权重融合。这样每个 token 实际经过的计算路径只有专家总数的一小部分但模型总的参数量是全部专家加起来的总和。打个比方一家咨询公司有 100 个领域专家但每位客户上门时只安排其中最对口的 2 位。公司照样要养 100 个人参数驻留内存但每个客户只付 2 个人的咨询工时计算量只占 2/100。MoE 用同样的道理用“大总参数量”换“模型容量”用“小激活参数量”控制“单次计算成本”。2.2 总参数和激活参数的区别决定部署策略部署 MoE 之前必须先分清两个数字总参数量和激活参数量。总参数量是全部专家、注意力、router 的权重总和决定你要准备多少内存。激活参数量是每个 token 实际参与计算的权重总和决定推理时的计算量和访存量。例如一个 125B 总参数、8 专家 top-2 的 MoE如果每个专家约 10B、注意力和其他层约 25B那么单个 token 激活约 25B 2×10B 45B不实际模型会把注意力参数也算进去激活参数量往往是总参数的 1/4 到 1/3。这个区别直接影响两件事第一内存规划按总参数算第二延迟估算按激活参数算。有人在论坛问“MoE 是不是可以只把被调用的专家加载进显存”看到这儿应该明白理论上可以但实际不可行原因下面细说。2.3 人人都问的MoE 要全部参数进显存吗结论很明确要全部权重必须常驻内存。原因有三层。第一router 在做决策时需要同时评估所有专家的得分也就是说每个 token 都要看一遍专家列表哪怕最后只选中 2 个。第二token 是流式到达的你无法提前知道下一个 token 会路由给谁如果把专家“换进换出”随机访存会直接击穿内存带宽。第三统一内存虽然大但不是无限大反复搬专家的开销远大于收益。所以实战中我们不会去“聪明地”只加载部分专家而是老老实实把全部 125B 权重放进统一内存。这也回答了一个常见的困惑本地部署大模型时为什么显存占用总是按总参数算而不是按激活参数算。注意这里说的“进显存”在 Jetson 上就是进统一内存CPU 和 GPU 共享不存在“放不下显存改放内存”的绕过方案——因为对 GPU 来说它们本来就是同一块地方。如果你身边有朋友用 NVIDIA 消费级显卡跑 Mixtral 8x7B总参数 46.7B激活 12.9B看到 48GB 显存不够用别奇怪这不是模型“虚胖”而是 MoE 的架构特性如此。理解了这一点125B MoE 的选型思路就清晰了——你必须有一块至少能装下 125B 权重并留出 KV cache 余量的设备Thor 的 256GB 完美卡位。2.4 量化和 KV cache 的空间规划确定了“全部驻留”的原则后下一步是算清楚 256GB 怎么分配。125B 模型在不同精度下的权重体积如下表精度每参数字节数125B 权重体积256GB 统一内存下可行性FP16/BF162250GB极限几乎无余量不推荐FP81125GB可行剩余约 100GB 给系统/缓存INT4AWQ/GPTQ0.562.5GB最宽裕可追求更高并发和更长上下文我们实测下来FP16 在这个容量下没有实操价值加载完权重后系统只剩几 GB 可用随便跑一个并发请求就会 OOM。FP8 是最均衡的选择精度损失很小125GB 权重加 10-20GB KV cache再留 15% 给系统整体很稳。INT4 适合追求吞吐和长上下文的场景62.5GB 权重意味着你可以把 KV cache 配到很大甚至跑 32K 上下文。KV cache 这块经常被新手忽略。它的体积随序列长度和并发数线性增长公式大致是2K和V× 层数 × KV头数 × 头维度 × 字节数 × 总token数。一个 50 层左右、8192 上下文、8 并发的中型配置KV cache 大概占用 10-20GB。所以部署前一定要先算总账权重 KV cache 系统保留三块加起来不能超过 256GB。3. 部署前夜的准备刷机、环境、推理框架三选一3.1 刷机与系统初始化别在最基础的环节翻车Thor 刷机跟 Orin 类似用 SDK Manager 连接宿主机和设备走 USB-C 线刷。这里有几个容易踩的细节第一宿主机的 SDK Manager 版本要和板子的 JetPack 匹配不匹配时设备会卡在“USB 设备识别”阶段半天没有反应。第二刷完后默认的 root 文件系统分区可能不大大模型权重动辄上百 GB建议刷机后的第一件事就是查看磁盘分区必要时扩容否则后面huggingface-cli download会下载到一半报磁盘满。第三Jetson 的系统服务默认会分配一部分内存给图形桌面如果是无头部署不带显示器建议把桌面服务关掉能省出几个 GB 内存。环境初始化只装必要组件JetPack 自带的 CUDA、cuDNN、TensorRT 已经够用不要重复安装其他 CUDA 版本容易把系统库搞乱。Python 环境我用的是 Miniconda创建独立虚拟环境跑 vLLM避免和系统 Python 三方库冲突。3.2 推理框架选型vLLM 是主力Ollama 做快速验证目前主流的本地大模型推理框架有 vLLM、Ollama、TensorRT-LLM、SGLang 等。我最终选择 vLLM 作为正式服务框架Ollama 作为快速验证工具。对比如下框架优势劣势适合场景vLLM连续批处理、PagedAttention、MoE 调度优化完善、OpenAI 兼容 API对 jetpack 某些系统库版本敏感需要编译正式服务、高并发Ollama安装简单、模型一行拉取、GGUF 量化支持好吞吐低于 vLLM精细控制弱快速验证、个人使用TensorRT-LLM极致性能、能榨干 TensorRT 加速编译慢、MoE 支持不稳定、调试成本高有充足时间打磨的团队SGLang调度策略激进、适合高并发社区相对小边缘设备兼容性未知探索阶段vLLM 对 MoE 的支持目前是最成熟的。它在调度层面对“专家”做了特殊处理能把属于同一层的专家分组管理减少随机访存和显存碎片。Ollama 则因为直接把模型封装成了 GGUF一行命令就能跑起来非常适合在部署初期验证“这个量化版本的模型质量是否可接受”。TensorRT-LLM 我不是不用而是在这个项目里试过一次就放弃了。它在服务器上确实能把性能拉满但在 Thor 上需要手动构建 engineMoE 模型构建耗时很长而且版本一更新就得重新折腾对于交付周期紧的项目不划算。我的原则是先让服务能跑再去追求极致性能。3.3 模型下载与量化方案前置部署前要把模型决定好。我们跑的是 8 专家、总参数约 125B 的 MoE 开源模型激活参数约 20-30B。主流的公开 MoE 模型如 Mixtral 8x22B、DBRX 等架构逻辑都类似本文的部署步骤可以平移。模型来源我优先选 Hugging Face 上的官方仓库。下载命令很简单huggingface-cli download 你的用户名/你的模型名 --local-dir /data/models/your-model有几个经验值得分享下载时建议加--local-dir-use-symlinks false避免软链接导致的文件移动问题下载完成后核对一下config.json里的num_experts、num_experts_per_tok字段确认和你要部署的 MoE 配置一致如果网络条件受限也可以从镜像站拉但一定要校验 sha256防止权重损坏。量化策略在下载前就要定因为直接决定你要下什么格式的 checkpoint。两种常用方案FP8 量化版精度高权重体积 125GB适合追求回答质量的场景。INT4 AWQ 量化版权重体积 62.5GB几乎无损压缩适合需要高并发和长上下文的场景。如果你下载的是原生 FP16/BF16 权重也别慌后续可以在推理框架里直接量化但我不推荐在边缘设备上现场量化 125B 模型太吃内存和时间。更好的方式是直接下载社区已经量化好的版本省掉一个环节。4. 完整部署链路从模型下载到 vLLM 把 125B 跑起来4.1 模型目录结构与启动前的校验模型下载完成后目录里通常包含config.json、tokenizer.json、多个*.safetensors权重文件以及可能的量化配置文件。推荐用如下结构管理/data/models/your-125b-moe/ ├── config.json ├── generation_config.json ├── tokenizer.json ├── tokenizer.model ├── quant_config.json └── model-00001-of-000xx.safetensors启动服务前我会先跑一段 Python 脚本用 Transformers 库检查模型能否被正确加载并打印关键字段from transformers import AutoConfig config AutoConfig.from_pretrained(/data/models/your-125b-moe) print(num_experts:, config.num_experts) print(num_experts_per_tok:, config.num_experts_per_tok) print(hidden_size:, config.hidden_size) print(torch_dtype:, config.torch_dtype)这一步能提前暴露 90% 的配置问题。比如量化和 config 里的torch_dtype不一致vLLM 加载时会报“找不到 quantization config”之类的错误。注意num_experts_per_tok如果大于 1说明是 top-k 路由对内存和算力的消耗你要有预期。4.2 量化权重FP8 还是 INT4 怎么选我在这轮部署里最终跑的是INT4 AWQ量化版本原因是它在 256GB 统一内存上留出了太多余量我可以把并发和上下文长度拉满同时回答质量差距很小。如果你的场景对回答质量极度敏感就选 FP8。如果你的权重还是 FP16需要先量化。有两种做法用 vLLM 的离线量化工具把 FP16 转成 AWQpython -m vllm.entrypoints.quantize \ --model /data/models/your-125b-moe-fp16 \ --quantization awq \ --output-format safetensors \ --output-dir /data/models/your-125b-moe-awq用 AutoAWQ 库也能达到同样效果而且可以调整校准数据集。无论用哪种量化完成后都要看报告里的“量化误差”AWQ 的量化误差通常能控制在 1% 以内。INT4 的精度损失从我的实际观测来看在开放域对话、代码生成等任务上感受不是很明显但如果你要做数学推理或抽取式任务还是 FP8 更保险。4.3 vLLM 启动参数逐项拆解环境就绪后正式启动服务。以 OpenAI 兼容 API 模式为例python -m vllm.entrypoints.openai.api_server \ --model /data/models/your-125b-moe-awq \ --quantization awq \ --max-model-len 8192 \ --gpu-memory-utilization 0.85 \ --max-num-seqs 8 \ --trust-remote-code逐个解释这些参数因为它们直接决定稳定性和性能--quantization awq告诉 vLLM 模型的量化格式是 AWQ。如果模型是 FP8改成--quantization fp8。--max-model-len 8192最大序列长度。这个值直接影响 KV cache 预留大小设太大容易 OOM设太小影响长文本能力。8192 是 125B MoE 在 Thor 上的一个稳妥起点。--gpu-memory-utilization 0.85vLLM 默认会用 90% 的 GPU 内存做权重和 KV cache。但在 Jetson 上我强烈建议留出 15% 给系统因为统一内存同时还要跑操作系统、Python 运行时和网络服务。这里设成 0.85 是实测后最稳的值。--max-num-seqs 8最多同时处理 8 个序列。并发太高时KV cache 会迅速膨胀拖垮整体吞吐8 这个值在 125B 模型下比较平衡。启动后看到Uvicorn running on http://0.0.0.0:8000就说明服务起来了。用一条命令验证curl -X POST http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d {model:/data/models/your-125b-moe-awq,messages:[{role:user,content:你好请简单介绍一下你自己}],max_tokens:128}能正常返回内容说明链路已经通了。我在实际部署中遇到过一个坑模型路径如果包含特殊符号vLLM 会无法正确注册模型名导致请求报 404。建议模型目录名保持纯字母数字配合短横线即可。4.4 Ollama 快速验证模型质量vLLM 适合做正式服务但用来验证模型质量有点重。我这边用 Ollama 做“量化版本质量快速评估”配合 GGUF 格式的量化模型。Ollama 部署 MoE 模型特别简单。如果你下载了 GGUF 文件需要写一个 ModelfileFROM /data/models/your-125b-moe-q4_k_m.gguf然后构建并运行ollama create your-125b-moe -f Modelfile ollama run your-125b-moeOllama 的优势在于它把量化、服务、API 全部封装好了一条命令就能交互式对话。我会拿同一组测试问题分别问 vLLM 服务里的 AWQ 版本和 Ollama 里的 GGUF 版本对比回答质量再决定最终生产用哪个。GGUF 的 q4_K_M 量化等级和 AWQ 的 INT4 在推理质量上接近但格式不同不能通用。5. 实测性能与调优延迟、吞吐与发热的平衡术5.1 首 token 延迟和生成速度的实测数据服务跑起来后第一件事是量化真实指标。我重点测三个数据首 token 延迟TTFT、单序列生成速度token/s、并发下的整体吞吐throughput。在 125B MoE激活约 22B、8 并发、8192 上下文的配置下我们的实测结果是单请求、无并发时生成速度约 8-12 token/s首 token 延迟约 1-2s。4-8 并发时单用户生成速度略有下降但整体吞吐能从 12 token/s 涨到 30 token/s。FP8 权重比 INT4 权重生成速度低约 10-15%但回答质量更稳。这个速度跟消费级显卡跑 7B 模型没法比但考虑到模型规模差了 10 倍以上已经是“能正常对话”的水平了。如果你的应用是智能客服、机器人本地大脑这个速度完全够用。5.2 连续批处理才是吞吐提升的关键跑大模型推理的人应该都听过 vLLM 的 continuous batching。它的核心思想是“动态组批”——不同请求的 prefill 和 decode 阶段可以穿插在同一批次里不需要等整个 batch 全部完成才释放资源。实际效果非常明显。单用户请求时GPU 利用率可能只有 30-40%因为生成阶段是串行的算力大量空闲。但一旦有 4 个以上的并发请求vLLM 会把它们塞进同一个调度队列GPU 利用率能拉到 70% 以上。所以如果你的实际场景是多人同时访问Thor 的性价比会被完全释放。调优时关注--max-num-seqs和--max-model-len的配合。max-num-seqs越大KV cache 占用越高但吞吐越高建议从 4 开始逐步往上加观察显存和延迟的变化找到拐点。我最后停在 8是因为再往上加时 KV cache 占用暴涨反而开始挤占权重驻留空间。5.3 MoE 特化的调度优化和带宽瓶颈在 vLLM 里跑 MoE还有一个特有优化点专家并行expert parallelism。它把不同专家分配到不同的计算单元或内存分区上减少单个计算单元上的访存冲突。在 Thor 这类统一内存设备上内存不会再被物理分割但 vLLM 的调度器仍然会对专家路由做优化——比如把经常被同时激活的专家放在一起减少缓存抖动。这类优化不用手动调了解即可。真正影响体验的是前端参数--gpu-memory-utilization留多少、--max-model-len设多长、--max-num-seqs放多大。我的经验是统一内存设备上不要把它当作独立显存设备来优化要把它当成“一块内存 一块算力”的统一体时刻记住系统也要吃内存。发热方面Thor 的功耗墙比 Orin 高不少满载时散热风扇会明显提速。长时间跑服务时我建议用nvpmodel -m 2把功耗限制在一个适中档位避免板子长期高温降频导致性能抖动。温度控制在 75°C 以内性能表现最稳定。6. 这轮部署踩过的坑OOM、路由不均衡、长时间运行稳定性6.1 权重加载成功首请求却 OOM第一次把服务拉起来时日志显示权重加载成功GPU 利用率为零我心想稳了。结果第一次请求直接报 Out of Memory。排查发现vLLM 在启动阶段会预分配 KV cache如果--gpu-memory-utilization设得太高我第一次设了 0.95预分配时就把统一内存占满了留给系统和运行时执行的空间不够。修复方案是分两步把gpu-memory-utilization从 0.95 降到 0.85同时把--max-model-len从 16384 降到 8192。改完后服务稳定运行。核心经验是边缘设备的统一内存不是纯粹的“显存”你还要喂给 CPU、网络栈、Python 运行时永远不能抱有“全都用完”的想法。6.2 路由不均衡看起来像死机其实是专家过热MoE 模型跑久了会遇到一个很有意思的现象服务没死但响应突然变慢GPU 占用率也很低。查了半天发现是路由器router对某些专家分配了过多 token导致特定专家对应的内存带宽被打满其他专家空闲。这是 MoE 模型的典型问题。训练时模型通常会加一个辅助损失来平衡专家利用率但推理时实际业务里的长尾请求依然可能集中命中某几个专家出现“热专家”和“冷专家”。在 vLLM 的监控指标里可以看到每个专家的 token 分布我后来发现把并发数降低一点热专家的问题会缓解——因为批量内 token 的多样性没那么高路由器倾向集中选择高置信专家。如果你的业务流量分布很窄比如整天都是同一类客服问题可以考虑在模型层面做一步“专家路由微调”或直接选个其他量化版本。但大多数情况下这个问题不用“修”只要了解它存在别在故障排查时误判为死机就行。6.3 长时间运行的稳定性为服务设置守护机制边缘设备不是数据中心没有专门的运维团队盯着。我让 Thor 连续跑了 72 小时的推理服务过程中遇到一次内存碎片导致的 OOM服务进程直接崩了。后来排查发现长时运行下 vLLM 的内存碎片率逐渐上升又没有及时释放最终触发系统级 OOM。解决方式是在系统层面做“守护”。给 vLLM 服务写一个 systemd service设置Restartalways、RestartSec10这样进程崩溃后能自动拉起。另外写一个定时脚本每 6 小时调用一次 vLLM 的健康检查接口连续失败两次就强制重启容器或进程。# 健康检查脚本示例 curl -s http://localhost:8000/health /dev/null || restart_service个人体会是部署大模型和部署普通 Web 服务最大的不同在于模型权重加载时间长一次可能要几分钟所以不能简单依赖“崩溃后重启”策略最好是在崩溃前就发现问题。监控内存占用随时间的变化曲线如果出现阶梯式上涨多半是内存碎片在积累提前重启能避免一个小时的加载等待。写在最后跑通 125B MoE 不是终点稳定才是。我最后的工作状态是Ollama 保留一个 Q4 量化版本用来快速验证新模型质量vLLM 作为生产服务常驻--gpu-memory-utilization压到 0.8把系统余量留足连续运行一周没有再出问题。最后再分享一个小技巧——如果你在 Jetson 上部署过多种模型会发现统一内存设备最忌讳的就是“换模型”。从 125B MoE 换回 7B 模型时内存不会立刻释放干净池子里全是碎片。我后来干脆给每个模型配一个独立容器模型间严格隔离切换成本反而更低。边缘设备跑百亿级 MoE本质上是一场资源规划的游戏。算力、内存带宽、功耗、稳定性——每个变量都得盯着。希望这篇部署记录能帮你少走几步弯路也欢迎在评论区聊聊你在 Jetson 上跑大模型遇到的那些有趣问题。