结论:modeld 一轮里会依次发布 modelV2、drivingModelData、cameraOdometry 三路消息,名义节拍 20 Hz,但它们是三条独立消息,不是一次原子多主题提交。三者的顶层 valid 语义也各不相同:modelV2 只反映「外参标定曾到达」,drivingModelData 直接复制它,cameraOdometry 还额外要求本轮主相机零丢帧。把三个 valid 合并理解成「模型可靠」是错的。以下为固定源码快照 c8fb906 的静态代码契约分析,不代表模型预测精度。
三路消息与发布节拍
manager 进程表让 modeld 在 onroad 运行,服务表把 cameraOdometry、drivingModelData、modelV2 设为名义 20 Hz。真正发布发生在 main() 成功取得相机帧、且模型输出非 None 之后:同轮依次发布三个主题。若外置 Chestnut 大模型推理失败,本轮回退小模型且 model_output=None,这一轮就不发布三路输出,后续才由小模型继续。
这意味着下游看到的「三路输出」在时间上并非同源同刻,而是同一轮内先后写入;消费进程各自按自己的频率与 valid 判断是否使用。
组件与关键函数
ModelState.__init__() 加载 OOB 模型、相机尺寸对应的 warp 与输出切片元数据,建立递归状态输入/输出;这些切片来自编译产物,不是 Python 文件里固定的完整模型 ABI。pack_inputs() 把两路 NV12 帧、变换矩阵、意图、驾驶位与动作时间拼成一次设备上传,连续缓冲布局与 NV12 stride 是模型与硬件的适配契约。
ModelState.run() 复制两路帧、在意图上升沿产生脉冲、运行 warp 与模型并解码输出。一个易被忽略的不对称:只有 Chestnut 路径显式检查整块原始输出是否全为有限值,常规模型路径没有同等检查。解析侧 Parser.parse_outputs() 负责轨迹、车道、路沿、前车的 MDN 解码与概率的 sigmoid/softmax。
| 函数/组件 | 功能 | 边界 |
|---|---|---|
ModelState.__init__() |
加载 OOB 模型、warp 与输出切片元数据 | 切片来自编译产物,不是 Python 文件中的完整模型 ABI。 |
pack_inputs() |
拼两路 NV12 帧、变换矩阵、意图、驾驶位与动作时间 | 连续缓冲布局与 NV12 stride 是硬件适配契约。 |
ModelState.run() |
复制帧、意图上升沿脉冲、warp 与模型、解码输出 | 仅 Chestnut 路径检查整块输出有限性,常规路径无同等检查。 |
Parser.parse_outputs() |
轨迹/车道/路沿/前车 MDN 解码,概率 sigmoid/softmax | parse_mdn() 分出均值/标准差;前车头兼容单/多假设。 |
get_action_from_model() |
优先取 action 头,缺省从 plan 推导动作 | 加速度 0.3 s 平滑;车速低于 0.3 m/s 保持上次曲率。 |
预测字段、时序与有效位
ModelConstants 定义 33 个非线性时间采样点,T_IDXS 最远 10 秒,距离采样 X_IDXS 最远 192 米。道路模型对轨迹、四条车道线、两条路沿、三组前车预测与意图/驾驶元信息分别解码。这里有两个必须澄清的常见误读:车道线概率取原始概率头的奇数槽;三条 leadsV3 带 probTime 对应 0/2/4 秒,是三个时间假设,不能理解为三辆已确认的真实车辆。
帧新鲜度方面,main() 从 narrowRoadCameraState.frameId 与 VisionIPC 主帧 ID 计算 frameAge,从相邻主帧 ID 估计丢帧率;若窄路相机状态尚未出现,frameAge 不足以证明帧新鲜度。模型输入的横纵向 action_t 包含车型/在线估计延迟、约 50 ms 帧时延与半帧预测间隔,这两项帧延迟是固定近似,不是逐帧由采集/执行时钟实测得到,更换自制相机或计算平台时必须重新验证。
| 消息 | 顶层 valid 的真实条件 | 易误判点 |
|---|---|---|
modelV2 |
只反映 extrinsics_calibration_seen,即收到外参更新且见过设备/窄路相机状态 |
更新该标志时没有显式检查外参已校准。 |
drivingModelData |
直接复制 modelV2.valid |
是同源精简消息,不是独立第二次推理。 |
cameraOdometry |
标定曾到达,且本轮主相机没有跳帧 | 比 modelV2 多一个丢帧门。 |
此外 modelV2.frameDropPerc 被 selfdrived 用于超过 1% 的掉帧事件,但该比例是 10 秒低通且前 10 次暖机后的运行指标,不是每帧硬性失效门。
告警、控制与融合消费者
get_action_from_model() 输出 desiredCurvature、desiredAcceleration 与 shouldStop。controlsd 在没有有效 lateralManeuverPlan 时取模型目标曲率,LongitudinalPlanner 消费目标加速度与停车建议;RadarD.update() 取前车预测与模型车速做雷达轨迹融合;LaneDepartureWarning 使用车道线概率;locationd/calibrationd 使用 cameraOdometry 观测而非 modelV2 的行驶动作。它们都仍受状态机、车辆能力、限幅与硬件网关约束,模型不是 CAN 发送者。
告警侧 fill_model_msg() 在连续硬制动概率缓冲上计算 hardBrakePredicted,由 selfdrived 与驾驶员制动、原车纵向制动状态结合成 FCW 事件——它是告警信号,不是直接制动请求。modelV2.confidence 由预测脱离概率的滚动评分映射绿/黄/红,不是 modelV2.valid 的替代。可选 SEND_RAW_PRED 把数组字节写进消息,但该数组是在解析之后才复制的,softmax() 对其切片做过原地归一化,因此不能无条件把 rawPredictions 当作完全未经后处理的原始张量。
验证缺口与插件边界
process_replay 有 modeld 三路输出的回放配置,model_replay 对比参考结果时会忽略时间和部分数值差异。但当前归档缺少 LFS 模型实体及常规模型产物,不能据这些测试定义宣称预测、光学或实车时序已通过;本轮仅为静态核对,没有运行。
插件化应把图像源、warp、模型、解码器与安全消费分层:影子模型只能发布独立候选主题,权威三路输出仍需单写者;契约至少固定相机内外参、NV12 布局、两路同步阈值、单调时间戳、输出切片 schema、MDN 分布含义、非有限值处理、帧丢失/年龄与三种有效位;控制与 FCW 不可由插件直接绕过 selfdrived 与 panda。
常见问题
三个有效位能合并成「模型可靠」吗?
不能。modelV2.valid 只反映外参标定曾到达,drivingModelData.valid 直接复制它,cameraOdometry.valid 还要求本轮主相机零丢帧。三者语义不同,不能合并为一个可靠性结论。
三组前车预测是不是三辆车?
不是。三条 leadsV3 带 probTime 对应 0/2/4 秒的时间假设,不能理解为三辆已确认的真实车辆。车道线概率取的是原始概率头的奇数槽。
为什么一轮里可能没有模型输出?
当外置 Chestnut 大模型推理失败时,本轮回退小模型且 model_output=None,这一轮就不发布三路输出,后续才由小模型继续。常规模型路径失败则抛出异常。
rawPredictions 是未经处理的原始张量吗?
不完全是。sendraw 复制数组字节发生在解析之后,而 softmax() 已对其切片做过原地归一化,因此不能无条件把它当作完全未经后处理的模型原始张量;它还涉及日志体积与数据隐私。
延伸阅读
- 横向目标曲率到执行器:来源仲裁、时序与限幅 —— 模型输出的目标曲率怎么被选用与限幅。
- 车载信号为什么会说谎:TTL、源时间与 500 ms 新鲜度阈值 —— frameAge 与新鲜度为什么不能只看当前值。
- 传感器融合的「健康位」在说什么 —— 有效位、alive 与更新的差别。
- 标定状态的持久化与恢复 —— 外参标定曾到达为何能左右有效位。
参考资料
- 项目 02 ——
openpilot-research/ROAD_MODEL_OUTPUT_CONTRACTS_DEEP_DIVE.md(modeld 三路消息、MDN 解码、三种有效位与消费链)。 - 项目 02 ——
openpilot-research/同目录探针记录:两项意图脉冲契约假推理隔离探针结论。
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11
发表回复