蔚来 AI Infra 实习 一二三面
Q: TensorRT的理解?TensorRT和OpenVINO的区别?
TensorRT核心优化流程:
1 | ONNX/TF/PyTorch模型 |
TensorRT关键优化技术:
| 优化 | 原理 | 收益 |
|---|---|---|
| 层融合 | Conv+BN+ReLU合并为一个kernel | 减少kernel launch和中间IO |
| INT8量化 | 校准集统计分布→KL散度找最优threshold | 速度2-4x, 精度损失<1% |
| Kernel Auto-Tuning | 运行时测试所有可用kernel实现 | 选择当前硬件上最快的 |
| Dynamic Tensor Memory | 分析tensor生命周期做in-place复用 | 减少30-50%显存 |
| Multi-Stream Execution | 独立路径分配到不同stream | 提高SM利用率 |
TensorRT vs OpenVINO对比:
| 维度 | TensorRT | OpenVINO |
|---|---|---|
| 厂商 | NVIDIA | Intel |
| 目标硬件 | NVIDIA GPU(仅) | Intel CPU/GPU/VPU/FPGA |
| 核心优势 | GPU推理性能极致 | 跨Intel硬件统一部署 |
| 量化方案 | INT8(entropy/MinMax校准) | INT8/INT4(NNCF工具) |
| 开源程度 | 核心kernel闭源 | 大部分开源 |
| 灵活性 | 自定义plugin扩展 | Model Optimizer灵活转换 |
| 动态shape | 支持(需指定范围) | 支持 |
| 延迟 | GPU上最低 | CPU上很优(Winograd/VNNI) |
| 典型场景 | 云端/边缘GPU推理 | 边缘设备(无GPU)/CPU服务器 |
| LLM支持 | TensorRT-LLM | OpenVINO GenAI |
选择建议:
- 有NVIDIA GPU且追求极致性能 → TensorRT
- Intel硬件部署(含CPU推理) → OpenVINO
- 需要模型快速上线且不限硬件 → ONNX Runtime(支持TRT/OpenVINO后端)
Q: C++面向对象的三大特性如何体现?
封装(Encapsulation):
1 | class KVCache { |
继承(Inheritance):
1 | class Attention { // 基类: 通用attention接口 |
多态(Polymorphism):
1 | // 运行时多态(虚函数): |
Q: C++继承的分类和特点?
| 继承类型 | 基类public成员变为 | 基类protected成员变为 | 语义 |
|---|---|---|---|
| public | public | protected | is-a关系(Dog is an Animal) |
| protected | protected | protected | 实现继承(限制对外暴露) |
| private | private | private | has-a替代(实现细节) |
菱形继承问题:
1 | class Device {}; // 基类 |
虚继承的代价:
- 额外的vbptr(虚基类指针)存储
- 访问虚基类成员需要间接寻址(多一次指针解引用)
- 对象布局更复杂(虚基类放在对象末尾)
- 实际C++项目中建议避免深层多继承,改用组合+接口
Q: C++虚函数的原理和作用?
vtable完整机制:
1 | class Shape { |
1 | Circle对象内存布局: |
虚函数调用的代码生成:
1 | Shape* s = get_shape(); // 可能是Circle或Rectangle |
虚函数vs纯虚函数的设计意图:
- 虚函数:提供合理的默认行为,派生类可以选择性重写
- 纯虚函数:强制派生类必须实现,基类只定义接口契约
- 含纯虚函数的类=抽象类:不可实例化,只能作为接口使用
Q: vector、map、unordered_map的底层实现?
三种容器的完整对比:
| 维度 | vector | map | unordered_map |
|---|---|---|---|
| 底层结构 | 动态数组(连续内存) | 红黑树 | 哈希表(开链法) |
| 有序性 | 插入顺序 | key有序 | 无序 |
| 随机访问 | O(1) | O(log n) | 平均O(1) |
| 插入(尾部/一般) | O(1)均摊/O(n) | O(log n) | 平均O(1), 最坏O(n) |
| 删除 | O(n)(中间)/O(1)(尾) | O(log n) | 平均O(1) |
| 内存布局 | 连续 | 每个节点堆分配 | 桶数组+链表节点 |
| Cache友好 | 极好 | 差(节点分散) | 中等 |
| 迭代器失效 | 插入扩容时全失效 | 只有被删除的失效 | rehash时全失效 |
vector扩容机制:
1 | // capacity不够时: |
map(红黑树)的性质:
- 每个节点红或黑色
- 根节点和叶子(NIL)为黑
- 红节点的子节点必须为黑
- 从根到叶的路径黑节点数相同
- 保证:最长路径不超过最短路径2倍 → O(log n)
unordered_map内部结构:
1 | 桶数组(bucket array): |
选择指南:
- 频繁随机访问、尾部增删 →
vector - 需要有序遍历、范围查询 →
map - 只需快速查找/插入/删除 →
unordered_map - 小数据量(<100) →
vector(即使需要查找,cache优势可能胜过算法优势)
Q: 多进程间通信方式有哪些?
| IPC方式 | 速度 | 方向 | 适用场景 | 关键限制 |
|---|---|---|---|---|
| 管道(pipe) | 快 | 半双工 | 父子进程 | 只能亲缘进程 |
| 命名管道(FIFO) | 快 | 半双工 | 任意进程 | 文件系统可见 |
| 共享内存(shm) | 最快 | 双向 | 大数据量高频通信 | 需自行同步 |
| 消息队列 | 中 | 双向 | 结构化消息 | 有大小限制 |
| 信号量 | - | - | 进程同步 | 只能做同步不能传数据 |
| 信号(signal) | 快 | 单向 | 异步通知 | 只能传少量信息 |
| Socket | 中 | 全双工 | 网络/跨机器 | 开销最大(协议栈) |
| 内存映射文件(mmap) | 快 | 双向 | 文件共享/大数据 | 需文件系统 |
深度学习中常用的IPC:
1 | # PyTorch DataLoader: 共享内存传递tensor |
Q: 多线程如何进行资源共享和同步?
同步原语的完整对比:
| 原语 | 适用场景 | 性能 | 特点 |
|---|---|---|---|
| mutex | 保护临界区 | ~25ns(无竞争) | 同一时刻只一个线程 |
| shared_mutex | 读多写少 | 略高于mutex | 多读单写 |
| spinlock | 极短临界区 | 极快(无竞争) | 忙等(消耗CPU) |
| condition_variable | 等待/通知 | 无忙等 | 配合mutex使用 |
| atomic | 简单变量操作 | 最快(无锁) | 只适合简单操作 |
| semaphore | 控制并发数 | 中等 | 允许N个线程同时进入 |
| barrier | 同步点(所有线程到齐) | 中等 | C++20 std::barrier |
典型使用模式:
1 | // 1. 生产者-消费者(condition_variable) |
避免死锁的策略:
std::lock(m1, m2): 同时获取多把锁(内部使用try-and-back-off)std::scoped_lock(m1, m2): C++17 RAII版本- 固定加锁顺序: 所有线程按相同顺序获取锁
- 超时机制:
try_lock_for()
Q: TVM的原理和作用?
TVM的核心设计——计算与调度分离:
1 | # 1. 描述计算(WHAT to compute): |
TVM编译栈全景:
1 | PyTorch/TensorFlow/ONNX模型 |
自动调度(AutoScheduler/Ansor):
| 维度 | AutoTVM | Ansor(AutoScheduler) |
|---|---|---|
| 搜索空间 | 手写模板定义 | 自动生成(sketch-based) |
| 工程量 | 每个算子写模板 | 几乎零手工(定义计算即可) |
| 搜索效率 | 模板内搜索(空间小) | 更大空间但更智能(cost model引导) |
| 性能 | 好 | 通常更好 |
TVM vs 其他编译器:
| 维度 | TVM | TensorRT | Triton |
|---|---|---|---|
| 目标 | 通用编译器(多后端) | NVIDIA GPU推理 | GPU kernel开发 |
| 优势 | 支持NPU/DSA等多硬件 | GPU极致性能 | 开发效率高 |
| 劣势 | 编译时间长 | 只支持NVIDIA | 只能写kernel |
| 适用 | 部署到多种硬件 | 生产GPU推理 | 自定义算子 |
Q: 图优化和算子调度方法?
图优化(Graph-level Optimization):
1. 算子融合(最重要的优化):
1 | 融合前: 融合后: |
融合分类:
| 类型 | 模式 | 示例 |
|---|---|---|
| Element-wise融合 | 逐元素操作串联 | ReLU+Add+Mul |
| Reduction+Element-wise | reduce后接逐元素 | Softmax内部(max+sub+exp+sum+div) |
| 复杂融合 | 多种pattern混合 | Conv+BN+ReLU |
| 注意力融合 | 整个attention块 | FlashAttention |
2. 常量折叠:
1 | # 优化前: 运行时计算 |
3. 布局变换:
1 | 不同硬件偏好不同数据布局: |
算子调度(Operator Scheduling):
1 | 计算图: |
Q: C++多继承的问题和解决?
菱形继承问题详解:
1 | class Animal { public: int age; void breathe(); }; |
虚继承的解决方案:
1 | class Animal { public: int age; void breathe(); }; |
虚继承的代价:
- 每个虚继承路径增加一个vbptr(虚基类指针)
- 访问虚基类成员多一次间接寻址
- 构造时最派生类负责初始化虚基类(中间类的初始化被忽略)
- 对象大小增加
实际建议:
- 优先使用组合(composition)而非多继承
- 如果需要多个接口,使用纯虚基类(无数据成员的抽象接口)
- Java/Go等语言直接不允许多继承(只允许多接口)
Q: C++智能指针有哪些?用过吗?
1 | // 1. unique_ptr — 工厂模式返回(最常用) |
常见陷阱:
shared_ptr循环引用 → 内存泄漏(用weak_ptr打破)shared_ptr的原子引用计数在高并发下有竞争开销unique_ptr不能直接传值(需要std::move)- 从
this构造shared_ptr需要enable_shared_from_this
Q: static和const的区别?
| 维度 | static | const |
|---|---|---|
| 核心作用 | 控制生命周期和可见性 | 控制可修改性 |
| 修饰局部变量 | 延长到程序结束,只初始化一次 | 不可修改 |
| 修饰全局变量 | 限制为内部链接(当前文件可见) | 不可修改(C++中默认内部链接) |
| 修饰类成员变量 | 属于类,所有对象共享 | 初始化后不可修改 |
| 修饰成员函数 | 无this指针,只能访问static成员 | 不修改对象状态(const this) |
| 存储位置 | 静态存储区(.data/.bss) | 取决于原始声明 |
1 | class Config { |
Q: C++异步实现方法?
1 | // 1. std::async — 最简单(自动管理线程) |
在AI Infra中的异步应用:
- Prefill和Decode异步调度(不同stream)
- 数据预取和计算overlap(cp.async)
- 多请求异步处理(async serving)
- 权重加载和推理并行
Q: Python列表切片反转?
1 | # 三种方式: |
Q: Python装饰器的原理?
1 | # 装饰器的本质: 接受函数,返回新函数的高阶函数 |
Q: 手撕:反转链表?
(编程题)
Q: 手撕:C++实现NMS + IOU?
(编程题)