NVIDIA 的 coding 面试和大多数互联网公司不太一样:纯 LeetCode-style 算法题只占 30%-40%,其余的题目偏向 CUDA 内核语义、C++ 内存模型、数值稳定性、以及编译器/驱动级的系统题。如果你按"FAANG 算法刷题"路线准备,进了 onsite 很容易在第二三轮 freeze。
这篇文章把 NVIDIA HackerRank OA 与 VO 中真正高频的 coding 题按五大类归并,逐类给一道代表题 + 解法 + 岗位线侧重对照。读完你应该能根据自己的目标岗位(Compiler / DL Frameworks / Driver / Robotics / SWE)做精准刷题。
五大题型分布
| 题型 | 占比 | 平台 | 决策权 |
|---|---|---|---|
| CUDA 内核语义 | 25% | HackerRank C++ + 白板 | DL Frameworks 主轮 |
| C++ 内存模型 | 30% | CoderPad C++ | Driver / Compiler 主轮 |
| 数值计算稳定性 | 15% | HackerRank Python/C++ | DL Frameworks 副轮 |
| 系统级算法题 | 20% | HackerRank | SWE / Robotics 主轮 |
| 字符串与编译器题 | 10% | HackerRank | Compiler 副轮 |
关键差异:和站内已有的 NVIDIA HackerRank 文聚焦"四类高频题型"不同,本篇按岗位线决策权重重新切片,方便定向投递。
题型一:CUDA 内核语义
代表题:Block 内 reduce sum
实现
__device__ float blockReduceSum(float val),对一个 block 内所有线程的val求和,使用 warp shuffle 优化。
思路:
- 先用
__shfl_down_sync在 warp 内 reduce - 再用 shared memory 把每个 warp 的结果聚合
- 最后再做一次 warp reduce
__inline__ __device__ float warpReduceSum(float val) {
for (int offset = 16; offset > 0; offset /= 2)
val += __shfl_down_sync(0xffffffff, val, offset);
return val;
}
__inline__ __device__ float blockReduceSum(float val) {
static __shared__ float shared[32];
int lane = threadIdx.x % 32;
int wid = threadIdx.x / 32;
val = warpReduceSum(val);
if (lane == 0) shared[wid] = val;
__syncthreads();
val = (threadIdx.x < blockDim.x / 32) ? shared[lane] : 0;
if (wid == 0) val = warpReduceSum(val);
return val;
}
关键点:
__shfl_down_sync的 mask 必须包含所有参与线程__syncthreads()不能放在条件分支里- block 大小不是 32 倍数时,需要补 0
题型二:C++ 内存模型
代表题:实现一个无锁 SPSC queue
实现 single-producer single-consumer 无锁队列,支持
push/pop,禁止使用 mutex。
#include <atomic>
#include <vector>
template <typename T>
class SPSCQueue {
public:
explicit SPSCQueue(size_t cap)
: data_(cap), cap_(cap), head_(0), tail_(0) {}
bool push(const T& v) {
size_t t = tail_.load(std::memory_order_relaxed);
size_t next = (t + 1) % cap_;
if (next == head_.load(std::memory_order_acquire)) return false;
data_[t] = v;
tail_.store(next, std::memory_order_release);
return true;
}
bool pop(T& out) {
size_t h = head_.load(std::memory_order_relaxed);
if (h == tail_.load(std::memory_order_acquire)) return false;
out = data_[h];
head_.store((h + 1) % cap_, std::memory_order_release);
return true;
}
private:
std::vector<T> data_;
size_t cap_;
std::atomic<size_t> head_, tail_;
};
关键点:
- producer 用
release,consumer 用acquire,配对形成 happens-before - relaxed 读自己的索引,acquire 读对端的索引
- false sharing:head_ 和 tail_ 应该放在不同 cache line
题型三:数值计算稳定性
代表题:Log-Sum-Exp 数值稳定实现
给一个 float 数组,求
log(sum(exp(x))),避免 overflow / underflow。
import math
def logsumexp(xs):
if not xs:
return float("-inf")
m = max(xs)
if m == float("-inf"):
return m
s = sum(math.exp(x - m) for x in xs)
return m + math.log(s)
关键点:
- 减最大值是经典 stability trick
- 全 -inf 输入时短路,避免 0 * inf
- C++ 实现要用
std::log1p在小输入时减少误差
题型四:系统级算法题
代表题:Driver 内存分配器(Buddy System)
实现一个 Buddy Allocator,支持
alloc(size)/free(ptr),最大空间 2^N,分配按 2 的幂向上取整。
思路:
- 每个 size class 维护一个 free list
- alloc 时从最接近的 size class 取,没空闲就 split 大块
- free 时检查 buddy,可合并就向上合并
#include <vector>
#include <set>
class BuddyAllocator {
public:
BuddyAllocator(int n): N(n) {
free_lists.resize(n + 1);
free_lists[n].insert(0);
}
int alloc(int size) {
int k = 0;
while ((1 << k) < size) k++;
int j = k;
while (j <= N && free_lists[j].empty()) j++;
if (j > N) return -1;
int addr = *free_lists[j].begin();
free_lists[j].erase(free_lists[j].begin());
while (j > k) {
j--;
free_lists[j].insert(addr + (1 << j));
}
used[addr] = k;
return addr;
}
void free(int addr) {
int k = used[addr];
used.erase(addr);
while (k < N) {
int buddy = addr ^ (1 << k);
auto it = free_lists[k].find(buddy);
if (it == free_lists[k].end()) break;
free_lists[k].erase(it);
addr = std::min(addr, buddy);
k++;
}
free_lists[k].insert(addr);
}
private:
int N;
std::vector<std::set<int>> free_lists;
std::map<int, int> used;
};
关键点:
- buddy 通过 XOR 求得:
buddy = addr ^ (1 << k) - 合并时取地址较小的那块作为新地址
- 实际 production 用 bitmap 而不是 set,更快
题型五:字符串与编译器题
代表题:Token 流的简单 expression evaluator
输入
["3", "+", "4", "*", "(", "2", "+", "1", ")"],求值。
def evaluate(tokens):
def precedence(op):
return {"+": 1, "-": 1, "*": 2, "/": 2}.get(op, 0)
def apply(a, b, op):
return {"+": a+b, "-": a-b, "*": a*b, "/": a//b}[op]
nums, ops = [], []
for t in tokens:
if t == "(":
ops.append(t)
elif t == ")":
while ops and ops[-1] != "(":
b, a = nums.pop(), nums.pop()
nums.append(apply(a, b, ops.pop()))
ops.pop()
elif t in "+-*/":
while ops and precedence(ops[-1]) >= precedence(t):
b, a = nums.pop(), nums.pop()
nums.append(apply(a, b, ops.pop()))
ops.append(t)
else:
nums.append(int(t))
while ops:
b, a = nums.pop(), nums.pop()
nums.append(apply(a, b, ops.pop()))
return nums[0]
关键点:
- shunting yard 思路,运算符栈 + 数字栈
- 优先级表是 dict,新增运算符不改主逻辑
- Compiler 岗位常追问 left-associative vs right-associative
岗位线 × 题型权重
| 岗位 | CUDA | C++ 内存 | 数值 | 系统 | 编译器 |
|---|---|---|---|---|---|
| DL Frameworks | ★★★ | ★★ | ★★★ | ★ | ★ |
| Driver | ★ | ★★★ | ★ | ★★★ | ★★ |
| Compiler | ★ | ★★ | ★★ | ★★ | ★★★ |
| Robotics | ★ | ★★ | ★★ | ★★★ | ★ |
| SWE General | ★ | ★ | ★ | ★★★ | ★ |
备考策略
| 阶段 | 内容 |
|---|---|
| 第 1 周 | 锁定目标岗位,按权重表挑出主刷题型 |
| 第 2 周 | 把 LeetCode "NVIDIA" tag 与 CUDA samples 各刷 30 道 |
| 第 3 周 | 系统级题型 + 岗位特定 follow-up |
| 模考 | Karat-style C++ mock × 2 |
FAQ
Q1:NVIDIA OA 是 HackerRank 还是 Karat? HackerRank 是 OA 主平台,Karat 是 phone screen 阶段。两者题风差异很大。
Q2:CUDA 不熟能投 NVIDIA SWE 吗? 能。SWE General 岗位 CUDA 题很少,主要看 C++ 与系统题。但 DL Frameworks / Driver 必须会 CUDA。
Q3:Coding 面试是 C++ 强制吗? DL Frameworks / Driver 强制;Compiler 偏好 C++/Rust;SWE 与 Robotics 接受 Python,但 C++ 加分。
Q4:白板 CUDA 题怎么准备? 先把 reduce / scan / matmul / convolution 四个经典内核手写一遍。面试时面试官会逐行追问 sync 与 race condition。
Q5:从国内投 NVIDIA 美国岗,OA 难度会变吗? 题目相同,但时区与英语沟通 是隐性门槛。建议先做 1-2 次英文 mock。
正在准备 NVIDIA Coding 面试?
如果你想做 CUDA 内核题逐行 review、目标岗位精准定位,或希望面试日有真人 OA代面 / VO代面 全程陪跑,可以聊聊看完整的 OA辅助 / VO辅助 方案。
联系方式
需要面试真题与定制备战计划?立刻联系微信 Coding0201,获取真题。
Email: [email protected] Telegram: @OAVOProxy