标签: modeld

  • modeld 构建:6 通道 NV12 warp 与 256 字节对齐的带外权重

    结论:modeld 的构建产物链把三件事固定在一起:ONNX 模型编译(compile_onnx.py,--out-of-band)、相机布局相关的 warp 编译(道路 YUV420 双帧、座舱 luma 单帧)与运行期输入打包。道路 warp 把 NV12 拆成 Y 四相位加两个色度面,拼成模型的 6 通道张量;座舱 warp 只取 Y 平面并用 16 填充边框。要记住两个边界:构建脚本的种子 42/43 只是自洽检查,没有与独立 ONNX Runtime 或实车真值比较;工件是可执行对象序列化而非纯数据,不能把未认证的工件交给 pickle.load()。道路与座舱两条路径的缓冲所有权与过期帧风险不同,不能用道路双帧上传的验证代替座舱零拷贝验证。

    构建入口:三条后端路径与产物矩阵

    SConscript 在非 comma ARM64、非 Darwin 平台缺 libLLVM 时跳过模型编译。comma ARM64 用 DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1,Darwin 用 DEV=METAL JIT=2,其他平台用 DEV=CPU:LLVM。Darwin 分支的注释明确说明 JIT=2 是为了禁用在 buffer 变化后可能产生错误输出的 graph batching。这些标志写入构建动作和依赖,不能在不同后端之间只重命名 .pkl 产物。

    产物 构建调用 运行期消费者
    道路模型 driving_tinygrad.pkl 与座舱模型 dmonitoring_model_tinygrad.pkl compile_model() 调 compile_onnx.py,使用 --out-of-band --benchmark-runs 1。 modeld.ModelState.__init__() 与 dmonitoringmodeld.ModelState.__init__() 经 load_oob() 载入。
    每种相机尺寸的道路 warp SConscript 调 compile_warp.py --layout yuv420 --frames 2,frame size 按 stride 与 Y/UV 分配高度求得。 modeld 以 img/big_img 两帧和两个逆变换调用捕获图。
    每种相机尺寸的座舱 warp SConscript 调 --layout luma --border-fill 16 --transform-device NPY。 dmonitoringmodeld.ModelState.run() 用 VisionIPC 缓冲指针和主机变换矩阵执行。

    这里有一个显式的产物缺口:该 SConscript 没有定义 Chestnut 的 big_driving_tinygrad.pkl 或 big_driving_warp_* 构建动作,chestnut_compiled() 只检查这些文件是否存在。它们的来源和与当前模型的兼容性,不能由普通模型构建链推定。

    ONNX 捕获与序列化:自洽检查不等于数值正确

    onnx_metadata() 只扫描 ONNX 顶层 metadata 和 output 声明,不读取权重节点,计算由 OnnxRunner 负责。主程序从 graph input 推出 input_specs,先用随机填充输入运行一次得到 output_specs,再把 metadata、input_shapes、output_shapes、捕获的 run 与规格一起写入工件。运行时道路模型还从 metadata 的 output_slices 取切片,因此模型元数据和解析器版本属于同一接口契约。

    run() 以 TinyJit(prune=True) 捕获,对传入的 output_buffers 执行赋值并物化。构建脚本用种子 42 的固定随机输入作捕获、两次重复一致性和反序列化后一致性检查,并要求种子 43 的不同输入使输出发生变化,以避免完全忽略输入。但这些都只是自洽检查:没有与独立 ONNX Runtime、车辆标注或实车真值比较,--benchmark-runs 1 也不构成尾延迟统计。

    序列化层面,dump_pickle() 对默认设备上的 Buffer 使用 persistent_id,把内容按 256 字节对齐存到带外区域;load_pickle() 建立目标设备 arena,并按 32 MiB 逻辑块复制后恢复视图。这里的 pickle 是可执行对象序列化而非纯数据格式,自制插件不能把未认证的工件交给 pickle.load()。--retargetable 可去掉 CPU 程序中的二进制以便重新编译,但常规模型动作未传此参数。

    NV12 warp 的几何:逆 3×3、最近邻与越界填充

    warp_perspective_tinygrad() 对目标像素用逆 3×3 矩阵求源坐标,以 Tensor.round 做最近邻采样,先把索引裁到图像边界;若传 border_fill_val,再依取整后的坐标是否越界决定填充。它没有在本函数中显式处理齐次分母 src_w=0、NaN 或非法矩阵,上游标定/变换有效性必须单独校验。普通道路 warp 不给 border_fill,因此越界像素采用夹边采样;座舱 warp 明确用 16 填充。

    make_frame_prepare() 用 frame stride 与 Y 分配高度确定 UV 偏移,把 NV12 交错 UV 拆成 U/V,按缩放后的逆矩阵各自透视采样,再拼成 YUV420 面;frames_to_tensor() 把四个 Y 相位面和两个色度面排列成模型的 6 通道张量。它依赖偶数模型尺寸、相机 stride 与 Y/UV 分配高度同 get_nv12_info() 一致。道路运行时 ModelState.pack_inputs() 把两个帧的 Y/UV 有效分配区和浮点辅助输入放在一个 host upload 中,而不是复制包含尾部 padding 的整个 VisionIPC 缓冲;因此更换相机、NV12 pitch 或 buffer 长度须连同构建 warp、输入切片和运行时复制大小一起更新。

    两条运行路径的缓冲所有权不同

    make_luma_warp() 只取 NV12 的 Y 平面;座舱运行时在 ModelState.run() 中缓存 Tensor.from_blob(),让它指向 VisionIPC 帧内存,不复制完整帧。这与道路双帧的 host upload 路径在所有权和过期帧风险上都不一样,见下表。

    维度 道路模型(双帧 YUV420) 座舱模型(单帧 luma)
    输入布局 四个 Y 相位面 + 两个色度面,共 6 通道 仅 Y 平面,边框用 16 填充
    越界处理 不给 border_fill,夹边采样 明确 --border-fill 16
    上传方式 把两帧 Y/UV 有效分配区与浮点辅助输入合并为一次 host upload 缓存 Tensor.from_blob() 指向 VisionIPC 帧内存,零拷贝
    主要风险 复制大小与 NV12 pitch/stride 强耦合 帧内存所有权与过期帧

    移植边界

    源文档给出的做法是:目标硬件应固定 ONNX、元数据、相机尺寸/stride、逆变换、tinygrad 提交和设备编译器版本,先建立独立 CPU 数值基准;再比较道路 YUV420 六通道、座舱 luma 填充、模型输入/输出形状、跨帧状态与不同后端输出,最后测帧级尾延迟及断流/非法矩阵回退。核心原则是一句话:算法产物可插件化,但工件加载信任、结果有效性和车辆安全控制许可不能交由插件自证。本轮仅核对构建命令、源码和运行期消费,没有执行 ONNX/warp 构建,也未取得 LFS 模型实体。

    常见问题

    构建脚本里的数值一致性检查能证明模型正确吗?

    不能。种子 42 的捕获与一致性检查、种子 43 的输入敏感性检查都只是自洽检查,源文档明确它们没有与独立 ONNX Runtime、车辆标注或实车真值比较,--benchmark-runs 1 也不构成尾延迟统计。

    道路 warp 和座舱 warp 的关键差别是什么?

    道路 warp 用 --layout yuv420 --frames 2,把 NV12 拆成四个 Y 相位面加两个色度面凑成 6 通道,越界走夹边采样;座舱 warp 用 --layout luma --border-fill 16,只取 Y 平面并用 16 填充边框。两者的缓冲所有权与过期帧风险不同。

    warp 函数会处理非法变换矩阵吗?

    不会。warp_perspective_tinygrad() 用逆 3×3 矩阵求源坐标、Tensor.round 最近邻采样并裁到边界,但源文档明确指出它没有在本函数中显式处理齐次分母 src_w=0、NaN 或非法矩阵,上游标定/变换有效性必须单独校验。

    模型工件可以直接 pickle 加载吗?

    不可以随意加载。源文档说明这里的 pickle 是可执行对象序列化而非纯数据格式,自制插件不能把未认证的工件交给 pickle.load()。权重经 dump_pickle() 按 256 字节对齐存到带外区域,由 load_pickle() 建立 arena 后按 32 MiB 逻辑块复制恢复。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/MODELD_BUILD_ARTIFACT_WARP_CONTRACT_DEEP_DIVE.md(取用了构建后端标志与产物矩阵、ONNX 捕获与序列化边界、NV12 warp 几何与两条运行路径的缓冲所有权)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • modelV2 的三个 valid 位不能合并:modeld 三路输出各差一道门

    结论: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() 已对其切片做过原地归一化,因此不能无条件把它当作完全未经后处理的模型原始张量;它还涉及日志体积与数据隐私。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/ROAD_MODEL_OUTPUT_CONTRACTS_DEEP_DIVE.md(modeld 三路消息、MDN 解码、三种有效位与消费链)。
    • 项目 02 —— openpilot-research/ 同目录探针记录:两项意图脉冲契约假推理隔离探针结论。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11