识渊科技 AI Infra 实习 一面
Q: 介绍一个CUDA算子优化过程?
CUDA算子优化是一个系统化的迭代过程,核心方法论是Profile → 定位瓶颈 → 针对性优化 → 验证效果。
完整优化流程:
Step 1: Baseline实现 + Profiling
1 | # 使用Nsight Compute分析kernel |
关键指标:
Memory ThroughputSOL%:实际带宽占理论带宽百分比Compute (SM) ThroughputSOL%:计算利用率Achieved Occupancy:活跃warp比例Warp State Statistics:stall原因分析
Step 2: 根据瓶颈类型优化
| 瓶颈类型 | 判断依据 | 优化方向 |
|---|---|---|
| Memory-bound | Memory SOL% > Compute SOL% | 优化访存 |
| Compute-bound | Compute SOL% > Memory SOL% | 优化计算 |
| Latency-bound | 两者都低 | 增大并行度 |
Memory-bound优化实例(如Softmax kernel):
- 合并访问:确保连续线程访问连续地址 → 一次128字节事务服务32个线程
- 向量化加载:
float4* p = (float4*)input; float4 val = p[tid];→ 4倍带宽利用 - 减少全局内存访问:online softmax一遍扫描替代两遍
- 共享内存中间缓存:如果需要两遍扫描则第一遍的结果存shared memory
Compute-bound优化实例(如GEMM kernel):
- 利用Tensor Core:
wmma::mma_sync()调用硬件矩阵乘 - 提高ILP:
#pragma unroll展开内层循环 - 寄存器级Tiling:每个线程计算8x8输出元素
Step 3: 验证优化效果
- 对比优化前后的SOL%变化
- 检查是否出现新瓶颈(消除memory瓶颈后可能变为compute瓶颈)
- 确认数值正确性(优化不能改变结果)
性能目标参考:
- Memory-bound kernel目标:达到理论带宽的80%+
- Compute-bound kernel目标:达到理论算力的70%+
- A100 HBM带宽2039 GB/s,FP16 Tensor Core算力312 TFLOPS
Q: KNN算法的流程?
KNN(K-Nearest Neighbors,K近邻)是一种基于实例的懒学习(Lazy Learning)算法,不需要显式训练过程。
算法流程:
- 计算距离:待预测样本与训练集中所有样本的距离
- 排序选择:按距离升序排列,选取最近的K个邻居
- 决策:
- 分类任务:K个邻居中多数投票(majority voting)
- 回归任务:K个邻居目标值的(加权)平均
距离度量选择:
| 距离度量 | 公式特点 | 适用场景 |
|---|---|---|
| 欧氏距离 | sqrt(Σ(xi-yi)^2) | 通用,连续值特征 |
| 曼哈顿距离 | Σ | xi-yi |
| 余弦距离 | 1 - cos(x,y) | 文本/稀疏高维(关注方向不关注幅度) |
| 闵可夫斯基距离 | (Σ | xi-yi |
K值选择的影响:
- K太小:过拟合,对噪声敏感(一个噪声点就可能改变分类结果)
- K太大:欠拟合,将远处不相关样本纳入投票,决策边界过于平滑
- 经验值:sqrt(n)或通过交叉验证选择最优K
加速搜索的数据结构:
- KD-Tree:空间划分树,平均O(log n)查询(高维退化严重,d>20时接近线性)
- Ball-Tree:球形划分,高维表现优于KD-Tree
- HNSW:近似最近邻(ANN),通过图结构加速,牺牲精度换取O(log n)查询
- Faiss/ScaNN:GPU加速的向量检索库,支持亿级数据
KNN的优缺点:
- 优点:简单直观、无需训练、对非线性边界适应好
- 缺点:预测慢O(nd)(d为维度)、内存占用大、对特征尺度敏感(需标准化)、高维失效(维度灾难)
Q: 数据集有问题时训练怎么解决?
根据数据集问题的类型采取不同策略:
1. 类别不平衡:
| 方法 | 原理 | 适用场景 |
|---|---|---|
| 过采样(SMOTE) | 对少数类合成新样本(插值) | 少数类样本太少时 |
| 欠采样 | 随机移除多数类样本 | 多数类样本充足时 |
| Focal Loss | γ参数降低易分类样本的loss权重 | 目标检测(正负样本1:1000+) |
| Class-weighted Loss | 按类别频率反比加权 | 通用 |
| 过采样+欠采样结合 | 两者混合使用 | 严重不平衡 |
2. 噪声标签(Label Noise):
- 数据清洗:用已训练模型预测,找出与标签不一致的样本人工复核
- **置信学习(Confident Learning)**:cleanlab库估计noise matrix识别错误标签
- 噪声鲁棒损失:Symmetric Cross Entropy、Generalized Cross Entropy
- Co-teaching:两个模型互相筛选干净样本训练对方
- Early Stopping:模型先拟合干净样本再拟合噪声,提前停止可避免学习噪声
3. 数据量不足:
- 数据增强:几何变换、颜色抖动、Mixup/CutMix、RandAugment
- 迁移学习:预训练模型微调(大模型+小数据最有效的方案)
- 半监督学习:利用大量无标注数据(如FixMatch、Pseudo-labeling)
- 合成数据:用生成模型(Diffusion/GAN)生成训练数据
4. 分布偏移(Distribution Shift):
- Domain Adaptation:对齐源域和目标域的特征分布(DANN、MMD)
- Test-Time Adaptation:推理时在线适应目标域分布(如Tent)
- Domain-specific augmentation:模拟目标域的数据变换
Q: 图像算子了解哪些?
传统图像处理算子:
1. 边缘检测:
- Sobel:一阶导数近似,分别计算x/y方向梯度,简单快速
- Canny:多步骤(高斯模糊→梯度计算→非极大值抑制→双阈值→连接),效果最好
- Laplacian:二阶导数,检测零交叉点,对噪声敏感
2. 模糊/平滑(去噪):
- 高斯模糊:加权平均(权重为高斯分布),保持边缘相对清晰
- 均值滤波:等权平均,计算最快但模糊边缘
- 中值滤波:取窗口内中值,对椒盐噪声效果最好
- 双边滤波:同时考虑空间距离和像素值差异,保持边缘
3. 形态学操作:
- 膨胀(Dilation):扩大白色区域
- 腐蚀(Erosion):缩小白色区域
- 开运算(Opening):先腐蚀后膨胀,去小噪点
- 闭运算(Closing):先膨胀后腐蚀,填小孔
4. 深度学习核心算子:
- 卷积(Conv2d):特征提取,学习局部模式
- 池化(Pooling):降采样,Max/Average Pooling
- BatchNorm:归一化激活值,稳定训练
- ReLU/GELU/SiLU:非线性激活函数
GPU优化角度:
传统图像算子(如Sobel、高斯模糊)天然适合GPU并行——每个像素的计算独立,可以分配一个线程。CUDA实现时利用纹理内存(texture memory)的空间局部性和硬件插值加速。
Q: 如何保证系统的高性能?
高性能系统设计需要在多个层面综合优化:
1. 算法与计算优化(根基):
- 选择渐进复杂度最优的算法(O(n log n) vs O(n^2))
- SIMD向量化:一条指令处理多个数据(如AVX-512处理16个float)
- GPU并行:大规模数据并行计算
- 近似计算:用精度换速度(如近似最近邻替代精确KNN)
2. 内存优化(常见瓶颈):
- 减少拷贝:零拷贝技术、move语义、引用传递
- **内存池(Memory Pool)**:避免频繁malloc/free的系统调用和碎片
- 缓存友好的数据布局:SoA替代AoS、连续内存访问
- 预分配:
vector::reserve()避免多次扩容拷贝 - 对象复用:对象池模式减少创建/销毁开销
3. IO优化:
- 异步IO(AIO/io_uring):不阻塞计算线程
- 批量处理(Batching):合并小IO为大IO减少系统调用次数
- 零拷贝:sendfile、mmap避免用户态-内核态拷贝
- 预取(Prefetch):预判下一步数据需求提前加载
4. 并发优化:
- 无锁数据结构(Lock-Free Queue、CAS操作)
- 减少锁粒度(分段锁、读写锁分离)
- 线程池复用:避免频繁创建销毁线程
- 减少同步点:异步通知替代轮询等待
5. 系统架构层面:
- 流水线化(Pipelining):多阶段重叠执行
- 计算通信重叠(Overlap)
- 负载均衡:Work-stealing调度
- 性能隔离:NUMA-aware内存分配
6. 持续性能工程:
- 定期Profiling定位瓶颈(perf、Nsight、火焰图)
- 建立性能基线和回归检测
- Amdahl定律指导优化优先级(先优化占比最大的部分)