7주 만에 vector_add 에서 Flash Attention 까지 걸어온 한 엔지니어의 경로를 역순으로 재생해본다 — 그러면 현대 LLM 이 왜 정확히 이 모양으로 진화했는지, 하드웨어와 모델이 어떻게 공진화해왔는지가 드러난다.
vector_add 에서 Flash Attention 까지. 그걸 보다 보니 계속 질문이 맴돌아. 왜 GPGPU 는 하필 이 모양으로 진화했을까?GPU 안에서 숫자 하나 더하는 건 1 나노초. 그걸 HBM 에서 가져오는 건 100 나노초. 이 비율 하나가 GPU 프로그래밍의 모든 결정을 지배한다.
vector_add 커널은 3.5 ms. 그런데 같은 데이터의 PCIe 복사만 60 ms. 커널이 17 배 짧다. pinned memory 를 쓰지 않으면 end-to-end 가 5.6 배 느려진다.
쓰레드가 수만 개니까 원자적 덧셈을 다 같이 하면 빠르겠지? 아니다. 100 배 느려진다. 그래서 GPU 에는 __shfl_down_sync 같은 이상한 명령어가 박혀 있다.
atomicAdd. 결과가 몇 ms 나왔게?__shfl_down_sync 는 1.0 에는 없었어. 사람들이 tree reduction 을 너무 많이 쓰니까 6년 뒤에 넣어준 거지. 지금은 표준이고.5 번을 지우면 29% 빨라진다. 작은 커널에서는 sync 한 번이 실행시간의 1/3. 그래서 마지막 warp 는 sync 없이 shuffle 만 돌리는 게 관용구가 됐다.
레슨 4 가 제일 길었던 이유. tiling 과 register blocking 이 연산 강도를 올리는 소프트웨어의 기술이라면, 그 다음 8 TFLOPS 천장을 뚫은 건 Tensor Core 라는 새 유닛 이었다.
"타일을 크게 하면 AI 가 올라가서 빠르겠지" 는 반만 맞다. 블록 수가 SM 수보다 충분히 많아야 의미가 있다. 그래서 decode 커널과 prefill 커널은 다르게 생겼다.
# merging two partial softmax statistics
new_max = max(m1, m2)
new_sum = s1 * exp(m1 − new_max)
+ s2 * exp(m2 − new_max)
N×N 중간 행렬을 아예 물리적으로 만들지 않는다. 앞서 본 5 가지 기술 — coalesced load, HBM 감각, warp reduce, tiled matmul, online softmax — 이 한 커널 안에 모두 들어간다.
torch.ops.*.torch.ops.* 에 등록된 CUDA 커널.AttentionBlock 을 torch.compile(..., fullgraph=True) 에 통째로 넣었다. 그래프 브레이크 0 건. eager vs compiled err = 0.00e+00 — 비트 단위 동일.
그 친구가 7주 만에 걸어간 경로를 거꾸로 보면, 실은 Flash Attention 이 필연 이었다는 게 보인다.
모든 수치는 cudatraining 레슨 1–9 핸드오프 문서, T4 sm_75 / L4 sm_89 실측.
Replay in reverse the 7-week path of an engineer who walked from vector_add to Flash Attention — and you start to see why the modern LLM evolved into exactly this shape, and how hardware and model co-evolved.
vector_add to Flash Attention. And one question kept circling. Why did GPGPU evolve into exactly this shape?Inside a GPU, adding one number is 1 ns. Fetching it from HBM is 100 ns. This single ratio governs every decision in GPU programming.
The vector_add kernel: 3.5 ms. The PCIe copy of the same data alone: 60 ms. The kernel is 17× shorter. Without pinned memory, end-to-end runs 5.6× slower.
If you have tens of thousands of threads, doing atomic adds together should be fast, right? No. 100× slower. That's why GPUs ship weird instructions like __shfl_down_sync.
atomicAdd on the same address. Guess how many ms?__shfl_down_sync didn't exist in 1.0. People used tree reductions so much we added it six years later. Now it's standard.Remove five of them and you get 29% back. In small kernels, a single sync is a third of runtime. That's why "last warp only shuffles, no sync" became an idiom.
Why Lesson 4 was the longest. Tiling and register blocking are software techniques that raise arithmetic intensity; the final breakthrough past the 8-TFLOPS ceiling came from a new unit called the Tensor Core.
"Bigger tiles raise AI, so they're faster" is only half true. You also need enough blocks to feed all the SMs. That's why decode kernels and prefill kernels look different.
# merging two partial softmax statistics
new_max = max(m1, m2)
new_sum = s1 * exp(m1 − new_max)
+ s2 * exp(m2 − new_max)
It never physically creates the N×N intermediate. All five earlier techniques — coalesced loads, HBM intuition, warp reduce, tiled matmul, online softmax — fit inside one kernel.
torch.ops.*.torch.ops.*.Placed the whole AttentionBlock inside torch.compile(..., fullgraph=True). Graph breaks: 0. eager vs compiled err = 0.00e+00 — bit-for-bit identical.
Running the engineer's 7-week path backward, you can see that Flash Attention was inevitable.
All numbers come from cudatraining lesson 1–9 handoff docs, measured on T4 sm_75 / L4 sm_89.