Workspace IndexAlgorithms › Day 97

Transformer compute structure, KV cache, and inference serving (continuous batching, PagedAttention), plus quantization/distillation/LoRA trade-offs TODO

Algorithms · Day 97 / 100 · G. AI Engineering (Day 97-100)

Concept

Transformer inference splits into a prefill stage, which processes the whole prompt at once, and a decode stage, which generates tokens one at a time — the two stages bottleneck on different things. During decode, the key/value vectors of past tokens are kept in a KV cache so they don't need to be recomputed every step; this cache grows with sequence length and the number of concurrent requests, so GPU memory itself becomes the concurrency limit. PagedAttention manages the KV cache in fixed-size pages instead of one large contiguous block, eliminating fragmentation and over-reservation so more requests can be served concurrently. Continuous batching doesn't fix the batch composition per request — at every token-generation step it drops finished requests and admits new ones, cutting GPU idle time. On the model side, quantization lowers weights and activations to lower precision, distillation trains a small model to mimic a large model's outputs, and LoRA freezes the original weights and trains/swaps only small low-rank matrices — each has a different trade-off across quality, memory, and serving flexibility.

LLM serving cost and latency are shaped far more by KV cache management and batching strategy than by model choice, so without understanding this structure, the only lever you have left is buying more GPUs.

Code & Formula

# 트랜스포머 계산 구조·KV 캐시 — 단일 헤드 self-attention을 numpy로 구현하고,
# 자기회귀 decode 시 K/V를 매 스텝 재계산하지 않고 캐시에 append 만 하는 것을 시연.

import numpy as np

np.random.seed(0)
d_model, d_k = 8, 4

Wq = np.random.randn(d_model, d_k) * 0.1
Wk = np.random.randn(d_model, d_k) * 0.1
Wv = np.random.randn(d_model, d_k) * 0.1

def softmax(x):
    e = np.exp(x - x.max(axis=-1, keepdims=True))
    return e / e.sum(axis=-1, keepdims=True)

def attention(q, K, V):
    scores = (q @ K.T) / np.sqrt(d_k)          # (1, seq_len)
    weights = softmax(scores)
    return weights @ V, weights                # (1, d_k), (1, seq_len)

# prefill: 초기 프롬프트 3토큰을 한 번에 처리하며 K/V 캐시를 채운다
tokens = np.random.randn(3, d_model) * 0.1
K_cache = tokens @ Wk
V_cache = tokens @ Wv
print("prefill 후 KV 캐시 길이:", len(K_cache))

# decode: 새 토큰마다 Q만 새로 계산하고, K/V는 "재계산 없이" 캐시에 append 만 한다
for step in range(3):
    new_token = np.random.randn(1, d_model) * 0.1
    q = new_token @ Wq
    k_new, v_new = new_token @ Wk, new_token @ Wv
    K_cache = np.vstack([K_cache, k_new])       # O(1) append, 과거 K 재계산 없음
    V_cache = np.vstack([V_cache, v_new])
    out, weights = attention(q, K_cache, V_cache)
    print(f"decode step {step}: KV 캐시 길이={len(K_cache)}, "
          f"attention weights={np.round(weights[0], 3).tolist()}")

print("\n캐시가 없다면 매 decode 스텝마다 전체 시퀀스의 K/V를 O(n) 재계산해야 한다.")

Exercise

Serving the same model, ramp up concurrent request count and prompt length, measure throughput and p95 latency, and find the point where the estimated KV cache memory hits its limit and requests start queuing.

Practical Connection

The direct blockchain connection is weak, but as a low-latency service that caches and batches per-request state, this calls for the same mindset as batching and memory-budget design in an order-book engine or an indexer.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

트랜스포머 계산 구조·KV 캐시·추론 서빙(연속 배칭·PagedAttention)… TODO

Algorithms · Day 97 / 100 · G. AI 엔지니어링 (Day 97–100)

개념

트랜스포머 추론은 프롬프트 전체를 한 번에 처리하는 prefill 단계와 토큰을 하나씩 만들어내는 decode 단계로 나뉘고, 두 단계의 병목이 다르다. decode에서는 과거 토큰의 key/value를 매번 다시 계산하지 않도록 KV 캐시에 보관하는데, 이 캐시 크기가 시퀀스 길이와 동시 요청 수에 비례해 커져 GPU 메모리가 곧 동시성 한계가 된다. PagedAttention은 KV 캐시를 연속된 큰 블록이 아니라 페이지 단위로 관리해 단편화와 과다 예약을 없애 동시 처리 요청 수를 늘린다. 연속 배칭은 요청 단위로 배치를 고정하지 않고 토큰 생성 스텝마다 끝난 요청을 빼고 새 요청을 넣어 GPU 유휴를 줄인다. 모델 쪽 최적화로는 가중치·활성값을 저정밀도로 낮추는 양자화, 큰 모델의 출력을 작은 모델에 학습시키는 증류, 원 가중치를 고정한 채 저랭크 행렬만 학습·교체하는 LoRA가 있으며 각각 품질·메모리·서빙 유연성의 트레이드오프가 다르다.

LLM 서빙 비용과 지연은 모델 선택보다 KV 캐시 관리와 배칭 전략에서 훨씬 크게 갈리므로, 이 구조를 모르면 GPU를 더 사는 방식으로만 문제를 풀게 된다.

코드 · 수식

# 트랜스포머 계산 구조·KV 캐시 — 단일 헤드 self-attention을 numpy로 구현하고,
# 자기회귀 decode 시 K/V를 매 스텝 재계산하지 않고 캐시에 append 만 하는 것을 시연.

import numpy as np

np.random.seed(0)
d_model, d_k = 8, 4

Wq = np.random.randn(d_model, d_k) * 0.1
Wk = np.random.randn(d_model, d_k) * 0.1
Wv = np.random.randn(d_model, d_k) * 0.1

def softmax(x):
    e = np.exp(x - x.max(axis=-1, keepdims=True))
    return e / e.sum(axis=-1, keepdims=True)

def attention(q, K, V):
    scores = (q @ K.T) / np.sqrt(d_k)          # (1, seq_len)
    weights = softmax(scores)
    return weights @ V, weights                # (1, d_k), (1, seq_len)

# prefill: 초기 프롬프트 3토큰을 한 번에 처리하며 K/V 캐시를 채운다
tokens = np.random.randn(3, d_model) * 0.1
K_cache = tokens @ Wk
V_cache = tokens @ Wv
print("prefill 후 KV 캐시 길이:", len(K_cache))

# decode: 새 토큰마다 Q만 새로 계산하고, K/V는 "재계산 없이" 캐시에 append 만 한다
for step in range(3):
    new_token = np.random.randn(1, d_model) * 0.1
    q = new_token @ Wq
    k_new, v_new = new_token @ Wk, new_token @ Wv
    K_cache = np.vstack([K_cache, k_new])       # O(1) append, 과거 K 재계산 없음
    V_cache = np.vstack([V_cache, v_new])
    out, weights = attention(q, K_cache, V_cache)
    print(f"decode step {step}: KV 캐시 길이={len(K_cache)}, "
          f"attention weights={np.round(weights[0], 3).tolist()}")

print("\n캐시가 없다면 매 decode 스텝마다 전체 시퀀스의 K/V를 O(n) 재계산해야 한다.")

연습

동일 모델을 서빙하면서 동시 요청 수와 프롬프트 길이를 늘려가며 처리량과 p95 지연을 측정하고, KV 캐시 메모리 추정치가 언제 한계에 닿아 대기가 시작되는지 지점을 찾아 보라.

실무 · Verex 연결

직접적인 블록체인 연결은 약하지만, 요청별 상태를 캐싱하며 배치 처리하는 저지연 서비스라는 점에서 오더북 엔진이나 인덱서의 배칭·메모리 예산 설계와 같은 사고방식을 요구한다.

공부한 날 원본 커리큘럼(docs/knowledge/dev-100-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 96. [복습] 검증 가능한 시스템 설계 체크리스트98. RAG 설계·리트리버 품질 지표와 평가 하네스(골든·프로퍼티·회귀) →