宇树机器人嵌入式AI岗面经(27届)
- 轮次
- 多轮合集
- base
- 杭州
- 结果
- 挂
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
一面(技术面,约 55 分钟)
- 自我介绍,然后挑一个最有挑战的项目讲一下。
- 为什么选这个模型?在目标板子上试过哪些方案,最后怎么取舍?
- 模型文件大小和运行时内存占用是一回事吗?中间激活、工作区、输入输出缓冲区算进去了吗?
- 预处理是在 CPU 上做,还是交给硬件加速?具体做了哪些操作?
- 报的耗时包含哪些阶段?第一次运行和预热后的耗时有没有区别?
- 摄像头来的速度比推理快,帧在队列里越堆越多怎么办?
- volatile 用过哪些场景?访问寄存器和中断共享标志时要注意什么?它能不能代替线程同步?
- static 修饰局部变量、全局变量、函数,分别有什么效果?
- 结构体内存对齐怎么算?怎样调整成员顺序减少填充?
- 指针和数组有什么区别?数组作为函数参数时,实际传进去的是什么?
- 堆和栈有什么区别?模型权重和中间结果的内存由谁分配、谁释放?
- malloc 在嵌入式项目里有哪些风险?内存碎片怎么产生,频繁分配释放有什么问题?
- 浮点和整数运算有什么区别?模型使用 INT8 量化,有什么收益和代价?
- 大小端怎么判断?解析二进制数据时,字节序不一致会有什么影响?
- 进程和线程的区别。推理任务放线程里,或者拆成独立进程,各有什么考虑?
- cache 有什么作用?为什么访问同一批数据,换一种访问顺序,耗时可能就不一样?
- 手撕:二维卷积的朴素版本(先把输入、卷积核、输出的关系写对,别下标越界)。
二面(技术面,约 75 分钟)
- INT8 量化具体怎么把浮点值映射成整数?scale 和 zero-point 分别干什么?
- 对称量化和非对称量化有什么区别?按张量量化和按通道量化又有什么区别?
- 量化后精度掉了,从哪几个方向排查?
- 训练后量化和量化感知训练有什么区别,各自有什么代价?
- 剪枝是什么?结构化和非结构化剪枝,对实际部署有什么不同?
- 知识蒸馏大概怎么做?为什么学生模型可能在体积小一些的情况下保留较好的效果?
- 算子融合有什么作用?为什么合并一些操作可能提速?
- ONNX 在部署链路里起什么作用?导出了 ONNX,是不是就能直接在任意板子上跑?
- 用过哪些推理框架?TensorRT、TFLite 或者芯片厂商的工具,选型要看什么?
- NPU、GPU、CPU 各有什么特点?为什么有的模型搬到加速器上,收益没有预想的大?
- 从训练好的模型到目标板子上运行,把部署过程完整讲一遍。
- 转换后电脑上结果正常,板子上结果不对,你先检查什么?
- 一个模型运行很慢,怎么区分瓶颈在计算、内存访问还是数据搬运?
- 怎样减少输入输出和中间数据的搬运?
- 摄像头到模型输入这一路,零拷贝可以做到什么程度?
- 假设推理和实时控制共享计算资源,推理负载突然增大,怎么保证控制任务按时运行?
- 多个模型同时跑,怎么安排执行顺序和资源?
- 推理结果来晚了,或者某一帧没有结果,上层应该怎么处理?
《参考解析》
INT8 量化的映射与两种分类。量化的本质是找一个仿射变换把浮点区间映射到整数区间:q = round(x / scale) + zero_point,反量化是 x ≈ (q − zero_point) × scale。scale 是步长,决定每个整数格代表多宽的浮点范围;zero_point 是浮点 0 对应的整数值,它的存在是为了让浮点 0 被精确表示(ReLU 后的激活大量为 0,若零不能精确表示,边界会产生系统性误差)。对称量化强制 zero_point = 0,scale 由权重绝对值最大值决定,实现简单、整数乘法更快;非对称量化用最小值与最大值一起定标,更贴合 ReLU 激活那种单边分布,但计算里多一个偏移项。按张量量化(per-tensor)全张量共用一组 scale,最省但误差大;按通道量化(per-channel)每个输出通道一组 scale,能显著降低权重侧的精度损失,代价是反量化要按通道取参数、对硬件算子有要求。
精度掉下来怎么排查。先定位是权重还是激活:只量化权重、激活保持浮点,看损失多少;再单独量化激活,通常激活的动态范围更难估计、影响更大。然后逐层比对量化前后的输出(余弦相似度或相对误差),异常层往往有离群值(outlier),常见处理是把极端离群值单独保留为浮点或用更细的粒度吸收。再确认校准集:PTQ 的 scale 依赖校准数据分布,样本必须能代表真实输入,否则 scale 会偏。最后看算子是否不支持导致回退到 CPU 或被拆成多段。如果 PTQ 怎么调都差,就上量化感知训练:训练时插入伪量化节点让模型适应量化误差,精度通常能拉回来,代价是需要训练资源和迭代周期。整数量化还有个容易忽略的收益:INT8 乘加可用 SIMD 一条指令算更多元素,访存带宽占用也降到四分之一,对嵌入式板子往往比算力更关键。
剪枝、蒸馏与算子融合。剪枝分结构化和非结构化:非结构化把单个权重置零,稀疏度可以做得很高,但通用硬件上稀疏矩阵并不会真的变快,通常只是压缩了存储;结构化剪枝以通道、卷积核、注意力头为单位整体删除,能直接减少计算量、在通用算子上就有加速,代价是要重新微调甚至重训,且必须保证删除后维度仍然对齐。蒸馏是让学生在拟合硬标签的同时拟合教师的软输出(带温度的 logits 分布),软标签里包含类别之间的相对关系这类「暗知识」,因此小模型也能保留较好的泛化。算子融合的收益来自减少中间张量的读写:Conv+BN+ReLU 这种链在推理时可以把 BN 参数折叠进卷积权重、激活就地做,省掉几轮内存往返和 kernel 启动开销,这也是同一套权重在不同框架下速度差很多的原因。
ONNX 与部署链路。ONNX 是中间表示层,解决「训练框架与推理引擎之间的格式统一」。但导出 ONNX 不等于能在任意板子上跑:目标平台的引擎可能只支持算子集的一个子集,也可能需要再转一次(转成芯片厂商的私有格式);opset 版本、动态 shape、控制流算子都是常见卡点。完整链路是:训练产出权重 → 导出 ONNX(确定输入输出名与 shape)→ 用目标工具链转换(算子替换、量化、融合)→ 板上加载并验证 → 性能调优(线程数、绑核、内存复用)。电脑上正常、板子上不对,排查顺序是:固定同一份输入与权重比对中间层输出,确认是「全链路偏差」还是「某一层开始偏」;再查预处理是否一致(RGB/BGR、归一化参数、resize 插值、NCHW/NHWC),这类不一致最常见;然后查数值精度(fp16/INT8 舍入、累加位宽)与后处理阈值。把中间张量 dump 出来逐层比对最有效。
性能瓶颈定位与减少搬运。区分计算密集、访存密集和搬运开销可以这样切:先看算子耗时占比与算力利用率(对标理论 FLOPS,利用率低说明不是纯计算问题);再把数据流画出来看中间张量有几份、每份多大,内存带宽是否被打满;最后看端到端里非模型部分占多少(预处理、后处理、拷贝与同步等待)。减少搬运的手段:算子融合减少中间张量、内存复用(一块 workspace 按生命周期复用而不是每次分配)、把预处理搬到加速器上或用 DMA 直写模型输入缓冲、避免「CPU 解码 → 拷到加速器 → 再拷回 CPU」的往返。零拷贝能做到什么程度取决于硬件通路:摄像头到 ISP、ISP 到内存、内存到 NPU 之间有 DMABUF/ION 这类共享缓冲时可以做到设备间零拷贝,前提是格式与对齐满足要求。实时控制与推理共享资源靠优先级与隔离:控制任务放最高优先级且可抢占(或独占一个核),推理放低优先级队列,用事件驱动而不是轮询等待,并给推理设超时与降级路径。多模型共存按截止期排优先级,能并行的小模型合并成批处理、大模型走独立队列。结果迟到或某帧无结果时,上层用「时间戳 + 有效窗口」判断:超出窗口的结果直接丢弃,并明确上报本帧无结果,由业务决定沿用上一帧(配 aging 上限)还是走安全兜底。
内存与 C 基础。模型的内存归属要能说清:权重由加载器在初始化阶段一次性分配(通常用静态大块或 mmap,不放堆),中间激活与工作区由推理引擎的 allocator 按图规划复用,输入输出缓冲由调用方提供或引擎预分配。嵌入式里 malloc 的风险在于碎片与不确定性:堆碎片会让「总量够但找不到连续块」的分配失败,失败时间点不可预测,长时间运行的小块频繁分配释放会持续恶化;常见做法是启动时一次性静态分配或用固定大小的内存池,并把分配失败当作可处理路径。结构体对齐按「成员对齐到自身大小的整数倍、整体对齐到最大成员的对齐值」计算,调整顺序的原则是大对齐成员放前面、小成员聚在后面。数组名在多数表达式中退化为首元素指针,而作为函数参数传进去后只是一个指针,sizeof 拿不到长度,必须额外传。volatile 只保证每次都从内存读写、不被优化掉,但它不保证原子性、也不提供内存序与互斥。