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>
#include <map>
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