从复现 NanoJev 到 AGX 上的实时随机迷宫
记录如何在 Jetson AGX Orin 上复现 NanoJev 的本地推理能力,并把一个离线迷宫演示改造成模型驱动、实时展示、可导出 JSON 的随机迷宫实验。
从复现 NanoJev 到 AGX 上的实时随机迷宫
最近花了一段时间研究 TianyuCodings/NanoJev。一开始只是想回答一个很实际的问题:这个项目能不能不训练、只在自己的 Jetson AGX Orin 上完成推理复现?后来在看懂它的迷宫和 Snake 演示之后,我又把它扩展成了一个更适合观察的实验:现场随机生成一张保证有通路的迷宫,让 NanoJev 在没有完整地图的情况下探索,浏览器实时显示每一步,并且最后导出完整 JSON 记录。
这件事的价值不在于做出了一个更漂亮的迷宫游戏,而在于把“模型判断”“外部记忆”“环境反馈”和“可视化”拆成了几个可以单独验证的部件。这样才能看清楚:究竟是模型在做什么,控制器在做什么,哪些能力来自训练,哪些能力来自程序设计。
1. 先把目标从“训练”改成“复现”
如果目标是训练一个新的 NanoJev,问题会变成数据集、训练时间、显存、优化器、检查点和评估协议的一整套工程。对于 AGX Orin 这样的边缘设备来说,这条路线并不适合用来做第一次验证。
我这次选择的是更小、更明确的目标:
- 使用项目已有的 checkpoint;
- 在 AGX 上加载模型并完成本地 forward;
- 不调用外部 API,不用教师模型兜底;
- 复现项目中的判断协议和迷宫控制逻辑;
- 把每次决策、碰撞和位置变化记录下来。
这两个目标经常被混在一起,但它们的难度完全不同。训练关心的是模型能不能学会,复现关心的是已有模型能不能在新的硬件和新的入口上稳定运行。先把后者做通,才有必要继续讨论训练或微调。
2. NanoJev 在迷宫里到底负责什么
从表面看,迷宫演示像是让模型“看地图、找终点”。但实际拆开后,模型做的事情更窄:它接收当前位置附近的局部窗口,并对四个方向分别回答“这条边是否安全、是否可以走”。
它并不知道完整迷宫,也不直接输出一条从起点到终点的全局路径。全局行为由外围控制器完成:
局部窗口 ──► NanoJev ──► 四个方向的安全概率
│
▼
EdgeExplorer 选择下一条边
│
▼
迷宫环境返回反馈
│
└── 更新访问记忆和边状态
这个区分非常重要。模型本身更像一个局部选择器,而不是传统意义上的 A*、Dijkstra 或端到端路径规划器。它能不能最终走到目标,取决于三部分共同作用:模型对局部边的判断、控制器如何处理访问过的节点,以及环境是否提供清晰一致的反馈。
这也解释了最初看到的“来回走”:如果没有足够的访问记忆,局部判断会重复面对相同的问题,模型可能反复选择看起来概率相近的方向。单纯把模型换成更大的模型,不一定能解决这个结构性问题。
3. 在 AGX 上搭建最小推理环境
这次部署采用了独立的 Python venv,把 NanoJev 的依赖和系统 Python、其他项目隔离开。推理服务只负责三件事:加载 checkpoint、接收判断请求、返回本地模型结果。
部署时有几个原则:
- 训练依赖和推理依赖分开看,不为了复现把整套训练环境搬到 AGX 上。
- 先验证 checkpoint 能否加载,再验证单条请求,最后才接入迷宫循环。
- 明确禁止外部教师或远程 API 作为失败时的隐藏兜底。
- 对模型调用加锁,避免实时控制线程和普通测试请求同时占用模型。
服务启动后,先用一个最小判断请求检查返回协议,再运行固定种子的 8×8 迷宫。固定 seed 的意义是让每次排错面对同一张地图,避免把地图变化误判成模型变化。
这一步也再次说明了边缘部署里的一个现实:PyTorch 版本并不是孤立变量。checkpoint、CUDA、JetPack、GPU 架构、精度模式和 Python 包版本必须一起匹配。复现阶段最重要的是找到一组能稳定加载和推理的组合,而不是盲目追逐最新版本。
4. 把离线演示改成现场随机生成
原来的演示更接近“已经准备好一组环境,再播放结果”。为了观察模型在新环境中的行为,我增加了一个实时 session:创建迷宫时才生成地图,后端持有 session 状态,浏览器通过事件流接收每一步结果。
一张地图需要同时满足两个条件:随机性和可达性。最简单的做法是先从一个起点生成连通区域,再从可达区域中选择终点;更适合迷宫显示的做法是使用生成树,再按比例增加少量额外通路。
我最终保留了几种拓扑:
Tree:分支清楚,但没有环;Long corridors:直路较多,适合观察长距离移动;Loops:存在回路,能测试重复访问;Random obstacles:更接近障碍物环境;High branching:专门给实时实验增加支路,避免地图大部分都是单一长走廊。
这里的 High branching 是实时迷宫的新拓扑,不去改变原有离线评估数据的含义。这样既能增加实验中的分支,也不会让旧的 benchmark 在不知情的情况下换了地图分布。
5. 模型只看局部,浏览器才看完整地图
实时页面里最容易造成误解的一点是:浏览器画出了完整地图,所以看起来像是模型也知道完整地图。实际上两者使用的是不同的数据路径。
后端环境状态 ───────────────► 浏览器 Canvas
│ │
│ └── 墙、通路、访问轨迹、Agent、Goal
│
└── 当前点附近的 3×3/5×5 窗口 ──► NanoJev
浏览器需要完整地图,才能把动画画出来;模型只接收当前位置附近的 ASCII 局部窗口。导出的 JSON 也会把初始地图和运行轨迹保存下来,但这不代表模型在运行时读取了整张地图。
每次到达一个尚未判断过的位置,控制器才调用一次模型。模型返回四个方向的概率后,EdgeExplorer 会结合已有记录选择边:
- 已确认的开放边可以继续使用;
- 已确认的阻塞边不再重复撞击;
- 新边优先进入候选集合;
- 回到旧节点时,复用已有判断,而不是重新问模型;
- 当当前分支耗尽时,利用访问图回到仍有未探索边的节点。
因此,访问记忆不是模型训练结果的一部分,而是控制器为模型补上的外部状态。它解决的是“同一个局部问题不要无限重复”的问题。
6. 实时网页的实现方式
页面没有把后端计算完的结果一次性下载下来再播放,而是使用了一个有状态的 live session:
- 浏览器提交地图尺寸、seed、拓扑和步进延迟;
- 后端生成可达迷宫并创建 session;
- 浏览器打开该 session 的 SSE 事件流;
- 每次模型判断和环境反馈完成后,后端发布一个 JSON snapshot;
- Canvas 根据 snapshot 重绘地图,右侧面板更新概率、碰撞、访问格数和模型调用次数;
- 运行结束后,页面提供 JSON 导出链接。
这种设计保留了一个很有用的观察边界:后端是真正运行控制器,前端只是展示已经发生的决策。即使动画速度调整为很慢,也不会改变模型的输入或路径,只改变事件到达浏览器的节奏。
为了让 50×50 地图仍然可读,后来又调整了视觉层级:墙体使用更亮的蓝灰色,未探索通路压暗,已访问区域用青绿色标出,Agent 和 Goal 使用高亮颜色。迷宫的图例也直接写出每种颜色对应的状态,避免把“墙”和“还没有走过的通路”混在一起。
7. 实际遇到的三个问题
7.1 地图被顶到页面下方
第一版页面给地图容器设置了较大的 min-height。在右侧控制面板变高时,整个 grid 被拉伸,Canvas 的正方形地图就被挤到了可视区域下方,截图看起来像是顶部空了一大截。
修复方式是把舞台高度从“至少这么高”改成明确的固定高度,并在小屏幕断点下再恢复自适应。布局问题不需要改模型,属于可视化容器和绘图区域没有分离好。
7.2 地图分支太少
传统的随机生成树容易形成长走廊。它能保证连通,却不一定适合作为观察局部决策的实验环境。于是我在生成树基础上增加一定比例的额外开放边,并把这个策略单独命名为 High branching。
这样做的目的不是让地图更难,而是让模型更频繁地遇到“多个方向都看起来可行”的局部选择,从而可以观察访问记忆、碰撞反馈和回溯机制是否真的在工作。
7.3 页面提示 SERVER ERROR
这次错误最容易被误判成模型崩溃。检查 AGX 日志后发现,模型服务本身是健康的,真正的原因是:页面每次加载都会自动 POST 创建一个新的 session,而早期实现最多保留 16 个 session;刷新页面不会自动删除旧 session,于是第 17 次创建收到 400。
最后做了三层修复:
- 前端把当前 session id 放入
localStorage,刷新时优先恢复原 session; - 用户点击“New maze”时,先删除旧 session,再创建新地图;
- 后端在达到上限前回收已经没有 SSE 订阅的断开 session,并停止其后台线程。
修复后重启服务清理了当时已经堆积的内存 session,并用创建、删除、健康检查三个请求验证了生命周期。这个问题和 NanoJev 的决策能力无关,却是把实验做成可长期使用的应用时必须处理的工程细节。
8. 这个实验说明了什么
这套系统并没有证明 NanoJev 已经具备通用的规划能力。它更准确地说明了几件事:
- 一个小模型可以作为局部安全判断器,接入一个有状态的控制器。
- 访问记忆和模型参数是两种不同的能力来源,不能把控制器效果全部归因于模型。
- “比随机好一些”和“能稳定完成任务”之间,还隔着环境设计、回溯、终止条件和异常处理。
- 只看局部窗口的模型,如果外部控制器维护了访问图,也可以在未知地图上形成可观察的探索行为。
- 一个好的复现实验应当把输入、输出、模型调用次数、碰撞和最终状态都记录下来,而不是只展示一段看起来成功的动画。
换句话说,NanoJev 更像一个可以嵌入外部状态机的“判断器”。它有点像把神经网络放进一个小型决策系统:模型负责对当前局部情况给出偏好,程序负责记住历史、执行动作、解释反馈并决定什么时候结束。
9. 下一步可以做什么
有了这个实时 session,后续应用不必局限在迷宫:
- 把局部安全判断接入 AGX 上的机器人避障实验;
- 将“订单是否已经退款”这类状态判断做成可配置的 JSON schema;
- 比较不同模型、不同窗口大小和不同记忆策略对探索效率的影响;
- 对一批随机地图批量运行,统计成功率、碰撞率和平均模型调用次数;
- 将控制器从四方向网格扩展到工具选择、流程节点选择或有限状态任务。
其中最值得继续做的是批量评估。单次动画很容易受到 seed 影响,只有在多个地图、多个起点和多个拓扑上统计,才能回答“模型到底比随机好多少”“访问记忆带来了多少收益”这类更可靠的问题。
结语
从源码阅读到 AGX 部署,再到实时随机迷宫,这次工作的核心不是把一个 demo 搬到另一台机器,而是把 demo 背后的结构拆了出来:
NanoJev:局部判断
EdgeExplorer:访问记忆与边选择
MazeEnvironment:动作执行与反馈
Live session:状态、事件流与导出
Canvas:把结果变成可观察的动画
当这几层边界清楚之后,模型到底看到了什么、走错时是谁负责、成功是怎么产生的,就都可以被检查和复现。对边缘设备上的小模型来说,这种可解释、可记录、可替换的结构,往往比单纯追求一个更大的模型更有实践价值。
Conversation
正在读取留言…