1서브워드 토큰화(subword tokenization)에 대해 설명하고, 현대 언어 모델에서 단어 단위나 문자 단위 토큰화보다 이를 선호하는 이유는 무엇인지 설명해 주세요.
서브워드 토큰화는 텍스트를 전체 단어나 개별 문자로 분절하는 대신 가변 길이의 형태학적 덩어리 또는 빈도 기반 부분 문자열('un', 'break', 'able' 등)로 나누는 하이브리드 텍스트 분절 방식입니다. BPE(Byte-Pair Encoding), WordPiece, Unigram LM과 같은 알고리즘은 훈련 말뭉치로부터 고정된 크기의 어휘집을 학습하여, 빈출 단어는 단일 토큰으로 온전히 유지하고 희귀 단어나 미등록 단어는 이미 알고 있는 서브워드 단위로 분해합니다. 현대 언어 모델에서 서브워드 토큰화가 선호되는 이유는 어휘집 크기, 시퀀스 길이, 그리고 OOV(Out-Of-Vocabulary, 미등록 어휘) 문제에 대한 견고성 사이에서 균형을 잡기 때문입니다. 순수 단어 단위 토큰화는 지나치게 큰 어휘집을 필요로 하여 거대한 임베딩 행렬을 초래하며, 여전히 범용 '[UNK]' 토큰으로 매핑되는 OOV 문제가 발생합니다. 반대로 순수 문자 단위 토큰화는 OOV 문제를 없애지만 시퀀스 길이가 매우 길어져 시퀀스 길이에 따라 계산량이 2차 함수 형태로 증가하는 어텐션 메커니즘의 연산 복잡도를 급격히 증가시키고 토큰당 의미 밀도를 희석합니다. 서브워드 토큰화는 시퀀스 길이를 다루기 쉽게 유지하고 어휘집 크기를 실용적인 수준(통상 32k~128k 토큰)으로 통제하며, 바이트 수준 폴백과 결합될 경우 OOV 비율을 사실상 0으로 만들어 최적의 절충안을 제공합니다.
2정적 단어 임베딩(Static Word Embedding), 문맥 임베딩(Contextual Embedding), 트랜스포머 은닉 상태(Transformer Hidden State)의 차이점을 설명해 주세요.
정적 단어 임베딩, 문맥 임베딩, 트랜스포머 은닉 상태는 텍스트 표현이 의미와 구문론적 문맥을 포착하는 방식의 점진적인 발전을 보여줍니다.
1. 정적 단어 임베딩(예: Word2Vec, GloVe, FastText)은 문장 내 문맥과 무관하게 어휘 사전의 각 토큰에 고정된 단일 벡터를 할당합니다. 이 패러다임에서는 정적 룩업 테이블(lookup table)에 의존하므로, 'bank'(강둑 vs. 금융 은행)나 'apple'(과일 vs. IT 기업)과 같은 다의어가 모든 문맥에서 완전히 동일한 벡터 표현을 갖습니다.
2. 문맥 임베딩(예: 초기 ELMo, BERT 토큰 표현, Bi-Encoder 기반의 문장 임베딩)은 주변 문맥에 따라 동적으로 달라지는 토큰 벡터 표현을 생성합니다. BERT나 ELMo에서 'river bank'의 'bank'는 'deposit money at the bank'의 'bank'와 완전히 다른 임베딩 벡터를 부여받습니다.
3. 트랜스포머 은닉 상태는 순전파(forward pass) 과정에서 트랜스포머 네트워크의 각 개별 레이어가 생성하는 중간 벡터 표현을 의미합니다. 레이어 0의 입력 토큰 임베딩이 주어지면, 이어지는 각 트랜스포머 레이어는 셀프 어텐션(self-attention)과 피드포워드(feed-forward) 변환을 적용하여 레이어 l에서 일련의 은닉 상태 벡터 $h_l$을 생성합니다. 최종 레이어의 은닉 상태가 고수준의 문맥 임베딩 역할을 하는 반면, 하위 및 중간 은닉 상태는 저수준의 구문적, 어휘적, 구조적 특징을 포착합니다. 따라서 트랜스포머 은닉 상태는 네트워크 전반에 걸친 레이어별 표현의 수직적 연속체 전체를 아우릅니다.
import torch
from transformers import AutoTokenizer, AutoModel
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
model = AutoModel.from_pretrained('bert-base-uncased', output_hidden_states=True)
text1 = 'River bank'
text2 = 'Bank deposit'
inputs1 = tokenizer(text1, return_tensors='pt')
inputs2 = tokenizer(text2, return_tensors='pt')
with torch.no_grad():
out1 = model(**inputs1)
out2 = model(**inputs2)
# Layer 0 (Input token embeddings - static lookup before self-attention)
static_bank_1 = out1.hidden_states[0][0, 2] # 'bank'
# Layer 12 (Final contextual hidden state after all attention layers)
contextual_bank_1 = out1.hidden_states[12][0, 2]
contextual_bank_2 = out2.hidden_states[12][0, 1]
print('Cosine similarity of bank in different contexts (Layer 12):',
torch.cosine_similarity(contextual_bank_1, contextual_bank_2, dim=0).item())
3임베딩 공간(embedding space)이 무엇을 나타내는지 설명하고, 텍스트 임베딩에서 코사인 유사도(cosine similarity)가 어떻게 해석되는지 설명해 주세요.
임베딩 공간은 단어, 문장, 문서와 같은 이산적인 텍스트 개체가 매핑되는 연속적인 고차원 벡터 공간 R^d를 의미합니다. 이 공간에서는 의미론적, 구문론적 또는 관계적 유사성이 기하학적 근접성과 방향적 관계로 표현되며, 거리와 각도가 의미상의 연관성을 반영합니다. 코사인 유사도는 두 벡터 u와 v 사이의 각도 theta에 대한 코사인 값을 측정하며 다음과 같이 계산됩니다.
Cosine Similarity(u, v) = (u . v) / (||u|| ||v||)
텍스트 임베딩에서 코사인 유사도는 다음과 같이 해석됩니다.
- 범위 및 방향성: 일반적으로 [-1, 1] 범위(음수가 아닌 임베딩의 경우 [0, 1])의 스칼라 값을 가집니다. 1.0에 가까운 값은 두 벡터가 거의 같은 방향을 가리키고 있음을 나타내며, 높은 의미적 유사도나 주제의 일치성을 반영합니다. 0.0에 가까운 값은 직교성(의미적 독립성 또는 무관함)을 의미하고, 음수 값은 서로 반대되는 방향성을 나타냅니다.
- 크기 불변성(Magnitude Invariance): 유클리드 거리(L2 distance)나 내적과 달리, 코사인 유사도는 벡터의 길이를 정규화합니다. 텍스트 임베딩에서 벡터의 크기는 시퀀스 길이, 토큰 빈도, 용어의 구체성 등과 상관관계가 있을 수 있습니다. 코사인 유사도는 오직 방향적 정렬에만 집중함으로써 벡터 크기 차이와 무관하게 순수한 의미론적 방향을 분리해 냅니다.
4트랜스포머(transformer) 입력에서 토큰 임베딩(token embedding), 위치 임베딩(positional embedding), 세그먼트 또는 타입 임베딩(segment or type embedding)의 차이점을 설명해 주세요.
트랜스포머 입력(특히 BERT 계열 아키텍처)에서 각 토큰의 입력 표현은 일반적으로 세 가지 서로 다른 임베딩 벡터를 요소별 합산(element-wise sum)하여 생성됩니다.
1. 토큰 임베딩(Token Embeddings): 이산적인 어휘 토큰 ID를 토큰 고유의 핵심 의미론적·어휘적 정체성을 나타내는 밀집 벡터(dense vector)로 매핑합니다.
2. 위치 임베딩(Positional Embeddings): 셀프 어텐션(self-attention) 메커니즘이 본질적으로 순서에 무관(permutation-invariant)하다는 점을 보완하기 위해, 표현에 토큰 순서 및 시퀀스 인덱스 정보를 주입합니다.
3. 세그먼트(또는 토큰 타입) 임베딩(Segment or Token Type Embeddings): 단일 입력 시퀀스로 패킹된 서로 다른 텍스트 구간 또는 문장(예: 문장 쌍 분류나 질의응답 작업에서의 문장 A와 문장 B)을 구분합니다.
이러한 임베딩들을 결합하면 첫 번째 트랜스포머 레이어로 전달되기 전에 토큰의 의미, 위치, 시퀀스 그룹화 정보가 모두 인코딩된 단일 밀집 입력 텐서가 생성됩니다.
import torch
import torch.nn as nn
vocab_size, max_seq_len, num_segments, d_model = 30522, 512, 2, 768
tok_embed = nn.Embedding(vocab_size, d_model)
pos_embed = nn.Embedding(max_seq_len, d_model)
seg_embed = nn.Embedding(num_segments, d_model)
input_ids = torch.tensor([[101, 7592, 102, 2023, 102]]) # Token IDs
type_ids = torch.tensor([[0, 0, 0, 1, 1 ]]) # Segment IDs (Sentence A vs B)
positions = torch.arange(input_ids.size(1)).unsqueeze(0) # Indices: [0, 1, 2, 3, 4]
# Final representation is the element-wise sum
input_rep = tok_embed(input_ids) + pos_embed(positions) + seg_embed(type_ids)
print(input_rep.shape)
5쿼리(Query), 키(Key), 값(Value), 그리고 멀티 헤드 어텐션(Multi-Head Attention)을 포함하여 시퀀스 내의 토큰들을 서로 연결하는 메커니즘으로서의 어텐션(Attention)을 설명해 주세요.
어텐션은 시퀀스 내의 토큰들이 문맥적 일치도에 따라 동적으로 정보를 라우팅하고 다른 모든 토큰의 연관도에 가중치를 부여할 수 있게 해주는 메커니즘입니다. 선형 투영(linear projection)을 통해 각 토큰의 입력이 세 개의 벡터로 변환됩니다.
- 쿼리(Query, Q): 현재 토큰이 찾고자 하는 정보가 무엇인지를 나타냅니다.
- 키(Key, K): 쿼리와 매칭될 수 있도록 토큰이 제공하는 속성이나 콘텐츠를 나타냅니다.
- 값(Value, V): 집계될 실제 정보 페이로드를 담고 있습니다.
스케일드 닷 프로덕트 어텐션(Scaled Dot-Product Attention)에서는 쿼리와 키의 곱($Q K^T$)으로 어텐션 점수를 계산하고, 차원이 커질 때 그래디언트 소실(gradient vanishing)이 발생하는 것을 방지하기 위해 $\frac{1}{\sqrt{d_k}}$로 스케일링한 후 소프트맥스(softmax) 함수로 정규화합니다. 최종 출력은 값(Value)들의 가중합입니다.
$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$
멀티 헤드 어텐션(Multi-Head Attention, MHA)은 $Q$, $K$, $V$를 병렬로 실행되는 여러 개의 독립적인 표현 부분 공간(head)으로 투영합니다. 이를 통해 모델은 위치 전반에 걸쳐 다양한 유형의 관계(예: 구문 구조, 상호 참조, 장거리 의존성)에 동시에 주목할 수 있습니다. 각 헤드의 출력은 연결(concatenate)된 후 선형 투영을 거쳐 다시 원래 모델 차원으로 복원됩니다.
6인과적 디코더(causal decoder)와 양방향 인코더(bidirectional encoder)에서 어텐션 마스킹이 어떻게 다른지, 그리고 이것이 어떤 동작을 가능하게 하거나 방지하는지 설명해 주세요.
어텐션 마스킹은 허용되지 않은 토큰 쌍의 어텐션 로짓(소프트맥스 이전 점수)을 $-\infty$로 설정하여 소프트맥스 이후의 어텐션 가중치를 정확히 0으로 만들어 어떤 토큰이 다른 어떤 토큰을 참조할 수 있는지 제어합니다. 1. 인과적 디코더(예: GPT, LLaMA): 하삼각 인과적(자기회귀적) 마스크를 사용합니다. 위치 $i$에 있는 토큰은 $j \le i$인 위치에만 어텐션을 적용할 수 있습니다. 이는 미래 토큰을 참조하는 것을 방지하여, 추론 시 자기회귀적인 토큰 단위 생성을 가능하게 하고 병렬화된 학습 도중 미래 토큰의 레이블 누출을 방지합니다. 2. 양방향 인코더(예: BERT): 인과적 마스크를 사용하지 않으며, 모든 토큰이 시퀀스 전체에 걸쳐 이전 및 이후의 모든 토큰을 참조할 수 있습니다. 대신 배치 처리된 시퀀스에서 유효한 토큰이 빈 `[PAD]` 토큰을 참조하지 않도록 패딩 마스크를 사용합니다. 양방향 어텐션은 이해 작업에 이상적인 풍부한 전방위 문맥 표현을 생성하지만, 직접적인 단일 패스(single-pass) 자기회귀 텍스트 생성은 불가능하게 합니다.
import torch
import torch.nn.functional as F
scores = torch.randn(3, 3)
# Create upper-triangular mask for future positions
causal_mask = torch.triu(torch.ones(3, 3, dtype=torch.bool), diagonal=1)
# Mask future positions with -inf before softmax
masked_scores = scores.masked_fill(causal_mask, float('-inf'))
atten_weights = F.softmax(masked_scores, dim=-1)
print(atten_weights)
세 가지 주요 트랜스포머 아키텍처는 어텐션 마스킹 패턴과 주된 동작 목적에서 근본적인 차이가 있습니다:
1. 인코더 전용 (예: BERT, RoBERTa): 시퀀스 내의 모든 토큰이 다른 모든 토큰을 동시에 참조할 수 있는 양방향 셀프 어텐션(bidirectional self-attention)을 사용합니다. 전체 입력 시퀀스에 대한 풍부한 문맥 표현을 생성하므로 분류, 추출형 질의응답(extractive QA), 특징 표현 등에 적합합니다. 다만 자기회귀적(autoregressive) 텍스트를 자연스럽게 생성하지는 못합니다.
2. 디코더 전용 (예: GPT-3, Llama, Mistral): 토큰 $i$가 $j \le i$ 위치의 토큰만 참조할 수 있는 인과적(causal, 단방향) 셀프 어텐션을 사용합니다. 다음 토큰 예측(next-token prediction)을 통해 자기회귀 방식으로 학습되며, 생성형 언어 모델, 코드 생성, 자유 대화의 표준 아키텍처로 사용됩니다.
3. 인코더-디코더 (예: T5, BART): 양방향 인코더와 자기회귀적 인과 디코더를 결합한 구조입니다. 디코더는 생성된 토큰에 대한 인과적 셀프 어텐션 외에도 인코더의 출력 표현을 쿼리하는 크로스 어텐션(cross-attention) 레이어를 사용합니다. 이 아키텍처는 번역 및 요약과 같은 시퀀스 투 시퀀스(sequence-to-sequence) 변환 태스크에 특화되어 있습니다.
8바이트 수준, 유니코드 인식, 다국어 토크나이저 선택이 언어 전반의 모델 품질, 비용, 공정성에 어떤 영향을 미치는지 설명해 주세요.
바이트 수준(byte-level)과 유니코드 인식(Unicode-aware) 분할, 다국어 어휘 할당 등 토크나이저의 설계 방식은 다운스트림 모델의 품질, 추론 및 학습 비용, 언어적 공정성에 직접적인 영향을 미칩니다.
비용과 공정성 측면에서, 주로 영어 또는 라틴 문자 말뭉치로 학습된 토크나이저는 어휘집의 대부분을 영어 단어와 형태소에 할당합니다. 그 결과 영어는 높은 압축률(예: 단어당 약 1.3개 토큰)을 달성하지만, 비라틴 문자(예: 아랍어, 데바나가리, 태국어, 한국어/중국어 등)나 저자원 언어는 여러 서브워드 또는 순수 UTF-8 바이트(단어당 3~6개 토큰인 경우가 흔함)로 잘게 쪼개집니다. 이러한 격차를 종종 '토큰 세금(token tax)' 또는 '출산율 불균형(fertility rate imbalance)'이라고 부릅니다. 비영어권 사용자는 API 요금 체계에서 동일한 의미 단위당 훨씬 더 많은 비용을 지불하고, 컨텍스트 윈도 한도를 훨씬 빠르게 소진하며, 더 긴 지연 시간을 겪게 됩니다.
품질 측면에서 바이트 수준 토크나이저(예: GPT-2/GPT-4의 Byte-level BPE, LLaMA의 바이트 폴백이 포함된 SentencePiece)는 유효한 모든 UTF-8 문자열을 바이트 토큰으로 분해할 수 있으므로 미등록 문자 충돌 및 OOV(Out-Of-Vocabulary, 미등록 어휘) 오류를 완전히 방지합니다. 그러나 지나친 바이트 단위 파편화는 트랜스포머 모델이 고차원 추론을 수행하기 전에 여러 바이트 조각을 의미 단위 개념으로 재조합하는 데 레이어를 소모하게 만들어 표현 품질을 저하시킵니다. 다국어 어휘 크기를 늘리면(예: 32k에서 128k 이상으로 확장) 토큰 생성 비율(fertility rate)의 균형을 맞추고 다양한 언어에 걸친 다운스트림 태스크 성능을 개선할 수 있지만, 입력/출력 임베딩 계층의 크기가 다소 증가하는 비용이 수반됩니다.
9수치 추론, 코드 생성 또는 희귀 유니코드(Unicode) 텍스트 처리 시 어떤 토큰화 아티팩트(artifact)가 발생하며, 특화된 토크나이저는 이를 어떻게 완화할 수 있나요?
토큰화 아티팩트는 서브워드 토크나이저가 구조화된 텍스트, 숫자, 또는 희귀 텍스트를 일관성 없이 분할하여 모델이 내재된 의미적·구문적 규칙성을 파악하지 못할 때 발생합니다. 주요 아티팩트는 다음과 같습니다.
1. 수치 추론 아티팩트: 일반 텍스트로 학습된 표준 BPE (Byte Pair Encoding) 토크나이저는 빈도에 따라 숫자를 임의의 청크 길이로 분할합니다(예: '12345'는 ['12', '345']로 토큰화되는 반면 '12346'은 ['123', '46']으로 토큰화됨). 이러한 불일치한 그룹화는 자릿수(일, 십, 백의 자리) 정렬을 깨뜨려 산술 추론을 방해합니다.
2. 코드 생성 아티팩트: 들여쓰기(앞쪽 공백/탭)와 다중 문자 연산자(예: '==', '!=', '->')가 공백 문자 경계를 가로질러 불규칙하게 분할되는 경우가 많으며, 이로 인해 들여쓰기 오류, 깊게 들여쓴 코드에서의 토큰 수 급증, 구문 손상이 발생합니다.
3. 희귀 유니코드 및 이모지 아티팩트: 제로 너비 조이너(zero-width joiner)가 포함된 복합 이모지나 희귀 문자 체계와 같은 다중 바이트 UTF-8 시퀀스가 개별적인 의미를 갖지 못하는 순수 바이트 토큰으로 분할되어 생성 시 글자 환각이나 잘못된 글리프 렌더링을 유발합니다.
특화된 토크나이저는 맞춤형 사전 토큰화 규칙과 어휘집 제약을 통해 이러한 아티팩트를 줄입니다.
- 숫자 분할: 단일 숫자 토큰화를 강제(예: 정규식을 통해 `0-9` 각 숫자를 개별 토큰으로 분할)하여 수학적 추론을 위한 자릿수 표현의 일관성을 보장합니다.
- 공백/들여쓰기 전용 토큰: 다중 공백 들여쓰기(예: 2, 4, 8칸 공백)를 위한 명시적 토큰을 추가하고 프로그래밍 언어의 키워드 및 연산자를 보존합니다.
- 정규식 기반 사전 토큰화 및 바이트 수준 폴백: BPE 병합을 계산하기 전에 문장 부호, 문자, 숫자를 엄격한 범주로 분리하는 정규식 분할기(예: GPT-4/tiktoken 정규식)를 사용하여 범주 간 병합(예: 'a=10'이 단일 토큰으로 병합되는 현상)을 방지합니다.
import tiktoken
# tiktoken cl100k_base (GPT-4 / ChatGPT) enforces digit and whitespace handling
enc = tiktoken.get_encoding('cl100k_base')
num1 = '12345'
num2 = '12346'
code_indent = ' def foo():'
print('Tokens num1:', [enc.decode([t]) for t in enc.encode(num1)])
print('Tokens num2:', [enc.decode([t]) for t in enc.encode(num2)])
print('Tokens code:', [enc.decode([t]) for t in enc.encode(code_indent)])
10이차 완전 어텐션, 슬라이딩 윈도우 어텐션, 희소 또는 글로벌 어텐션, KV(Key-Value) 캐시 비용, 어텐션 희석을 포함한 롱 컨텍스트 어텐션의 트레이드오프를 설명해 주세요.
긴 컨텍스트 윈도우로 어텐션을 확장할 때는 연산량, 메모리 점유율, 모델 품질 전반에 걸친 트레이드오프가 발생합니다.
1. 이차 완전 어텐션 vs. 슬라이딩 윈도우 / 희소 어텐션: 표준 완전 어텐션은 시퀀스 길이 $N$에 대해 연산량과 액티베이션 메모리가 이차적으로($O(N^2)$) 증가합니다. 슬라이딩 윈도우(지역) 어텐션은 어텐션을 고정된 크기 $W$의 주변 토큰으로 제한하여 복잡도를 $O(N \cdot W)$로 줄이지만, 거리가 먼 토큰 간에 정보를 전달하려면 여러 레이어가 필요합니다. 희소 또는 글로벌 어텐션 패턴은 지역 윈도우와 선별된 글로벌 앵커 토큰을 결합하여 $O(N)$의 확장성을 유지하면서도 장거리 통신을 가능하게 합니다.
2. KV 캐시 메모리 비용: 자기회귀(autoregressive) 생성 과정에서는 중복 연산을 피하기 위해 이전의 모든 토큰에 대한 키와 값을 캐싱합니다. KV 캐시 메모리는 시퀀스 길이에 따라 선형적으로($O(B \cdot L \cdot H_{KV} \cdot D \cdot N)$) 증가합니다. 매우 긴 컨텍스트(32k~128k+ 토큰)의 경우, KV 캐시가 배치당 수십 기가바이트의 GPU VRAM을 소비하여 최대 배치 크기와 메모리 대역폭의 병목이 됩니다.
3. 어텐션 희석(Lost-in-the-Middle): 컨텍스트가 길어질수록 소프트맥스 분모에 수만 개 토큰의 합이 들어가므로, 관련 없는 컨텍스트 전반으로 확률 질량이 얇게 분산됩니다. 이러한 엔트로피 증가는 어텐션의 선명도를 떨어뜨려, 긴 프롬프트 중간에 포함된 특정 정보를 안정적으로 회상하는 모델의 능력을 저하시킵니다.
11MHA(Multi-Head Attention), MQA(Multi-Query Attention), GQA(Grouped-Query Attention)를 비교하고, 이들이 KV 캐시(Key-Value Cache) 메모리와 디코딩 처리량에 어떤 영향을 미치는지 설명해 주세요.
Multi-Head Attention (MHA), Multi-Query Attention (MQA), Grouped-Query Attention (GQA)는 Query($Q$) 헤드 간에 Key($K$) 및 Value($V$) 헤드를 어떻게 공유하는지에 차이가 있습니다: 1. Multi-Head Attention (MHA): $Q$, $K$, $V$ 헤드의 수가 동일합니다($H_Q = H_{KV}$, 1:1 비율). 각 쿼리 헤드는 독립적인 키/값 표현에 집중(attend)합니다. 표현력은 높지만, 모든 헤드에 대해 별도의 KV 행렬을 캐싱해야 합니다. 2. Multi-Query Attention (MQA): 여러 $Q$ 헤드($H$)를 사용하지만 단 1개의 공유된 $K$ 헤드와 1개의 공유된 $V$ 헤드만 사용합니다($H:1$ 비율). 이를 통해 KV 캐시 크기를 $H$배 줄일 수 있지만, 약간의 품질 손실이나 학습 불안정성이 발생할 수 있습니다. 3. Grouped-Query Attention (GQA): $Q$ 헤드를 $G$개의 그룹으로 묶고, 각 그룹이 단일 $K$ 및 $V$ 헤드를 공유합니다(예: KV 헤드당 8개의 $Q$ 헤드). GQA는 최적의 트레이드오프를 제공하며, MQA의 메모리 절감 이점을 유지하면서 MHA의 모델링 품질을 사실상 거의 회복합니다. KV 캐시 및 디코딩 처리량에 미치는 영향: 자기회귀적 토큰 생성(디코딩)은 메모리 대역폭에 제약을 받습니다(memory-bandwidth bound). GPU가 토큰을 하나 생성할 때마다 KV 캐시 전체를 HBM(High-Bandwidth Memory)에서 칩 내부의 SRAM으로 전송해야 하기 때문입니다. KV 헤드 수를 $H/G$로 줄임으로써(예: GQA에서는 $4\times$ ~ $8\times$, MQA에서는 $32\times$ 이상): - KV 캐시 메모리 사용량이 비례하여 감소하므로 GPU VRAM에서 훨씬 더 큰 서빙 배치 크기를 처리할 수 있습니다. - 토큰당 HBM 메모리 읽기 트래픽이 크게 줄어들어 디코딩 토큰 처리량이 비약적으로 증가합니다.
# Model with 32 Query Heads
num_q_heads = 32
mha_kv_heads = 32 # 1:1 ratio
gqa_kv_heads = 8 # 4:1 ratio (4 query heads per KV head)
mqa_kv_heads = 1 # 32:1 ratio (1 shared KV head)
print(f"KV Cache Size Relative to MHA:")
print(f"MHA: {mha_kv_heads / mha_kv_heads * 100:.1f}%")
print(f"GQA: {gqa_kv_heads / mha_kv_heads * 100:.1f}%")
print(f"MQA: {mqa_kv_heads / mha_kv_heads * 100:.1f}%")
12FlashAttention이 어텐션 출력을 변경하지 않으면서 정확한(exact) 어텐션 연산 속도를 높이는 원리를 설명해 주세요.
FlashAttention은 전체 산술 FLOP 수를 줄이려 하기보다 알고리즘을 IO 인식(IO-aware) 방식으로 설계하여, 속도가 느린 GPU의 HBM (High Bandwidth Memory)과 빠른 온칩 SRAM 사이의 메모리 읽기/쓰기 트래픽을 최소화함으로써 어텐션 연산 속도를 높입니다.
표준 어텐션은 N x N 크기의 중간 어텐션 점수 및 확률 행렬을 HBM에 구체화(materialize)하므로 심각한 메모리 대역폭 병목을 유발합니다. FlashAttention은 세 가지 핵심 메커니즘을 통해 이를 해결합니다:
1. 타일링(Tiling): Query, Key, Value 행렬을 GPU 온칩 SRAM에 완전히 들어가는 크기의 블록 단위로 분할합니다.
2. 온라인 소프트맥스(Online Softmax): 누적 최댓값과 정규화 합계를 추적하여 블록 단위로 소프트맥스를 점진적으로 계산함으로써, 메모리에 완전한 N x N 행렬을 구체화하지 않고도 부분 출력을 갱신합니다.
3. 정확한 재계산(Exact Recomputation): 역전파(backward pass) 단계에서 HBM에 저장된 중간 어텐션 행렬을 읽지 않고, 저장된 누적 통계량을 바탕으로 SRAM에서 즉석으로 행렬을 다시 계산합니다.
근사치, 저차원 분해(low-rank factorization), 토큰 드롭 휴리스틱을 전혀 사용하지 않기 때문에, 부동소수점 수치 정밀도 오차 범위 내에서 출력이 수학적으로 정확하며, HBM 메모리 사용량을 O(N^2)에서 O(N)으로 크게 줄입니다.
13자유 형식의 에이전트 루프 대신 플래너, 상태 머신, DAG (Directed Acyclic Graph), 타입이 정의된 중간 상태, 제한된 재시도, 도구 실행 결과 검증을 활용하여 결정론적 에이전트 워크플로를 설계해 보세요.
운영 환경에서 자유 형식의 에이전트 루프(예: 제약 없는 자율형 ReAct 루프)는 비결정론적 분기, 무한 루프, 통제 불가능한 토큰 비용 소모, 상태 드리프트(state drift)와 같은 문제를 자주 겪습니다. 결정론적 에이전트 워크플로는 이러한 자유 형식 루프를 구조화되고 관측 가능한 제어 흐름으로 대체합니다:
1. 상태 머신 및 DAG: 제어 흐름을 명시적인 비순환 유향 그래프(DAG)나 유한 상태 머신(예: LangGraph, Temporal, AWS Step Functions)으로 정의합니다. 노드 전이는 모델의 임의적인 결정이 아니라 명시적인 조건과 타입이 지정된 결과에 따라 이루어집니다.
2. 타입이 정의된 중간 상태: 노드 간에 공유되는 상태는 엄격한 스키마(예: Pydantic 모델이나 dataclass)로 모델링합니다. 노드는 유효성 검증을 거친 읽기 및 쓰기 작업을 수행하여 스키마 드리프트나 잘못된 형식의 상태가 발생하는 것을 방지합니다.
3. 플래너: 구조화된 플래너는 제약 없이 다음 동작을 자유롭게 결정하는 대신, 사전에 제약된 계획(예: enum 기반 단계들의 정렬된 목록)을 생성하거나 제한된 유효 상태 전이 집합 중에서 선택합니다.
4. 도구 실행 결과 검증: 도구가 반환한 출력은 상태를 업데이트하거나 후속 LLM 단계로 전달되기 전에 스키마 및 비즈니스 규칙에 따라 결정론적으로 유효성을 검증합니다.
5. 제한된 재시도 및 폴백: 모든 단계에서 명시적인 재시도 예산, 지수 백오프, 타임아웃, 폴백 전이(예: 사람의 검토로 에스컬레이션하거나 안전한 보류 유도)를 적용하여 워크플로의 종료를 보장합니다.
from pydantic import BaseModel
from typing import Optional, Literal
class WorkflowState(BaseModel):
user_query: str
extracted_id: Optional[str] = None
verification_status: Literal["PENDING", "VERIFIED", "FAILED"] = "PENDING"
retry_count: int = 0
max_retries: int = 3
def execute_validation_node(state: WorkflowState) -> WorkflowState:
if state.retry_count >= state.max_retries:
state.verification_status = "FAILED"
return state
try:
result = call_verification_service(state.extracted_id)
state.verification_status = "VERIFIED" if result.is_valid else "FAILED"
except Exception:
state.retry_count += 1
return state
14규제 대상 도메인의 질문에 답변하는 LLM(Large Language Model) 어시스턴트를 위한 신뢰도 및 답변 보류 정책을 어떻게 설계하시겠습니까?
의료, 금융, 법률, 컴플라이언스 등 규제 대상 도메인에서는 잘못된 답변이 규제 처벌, 법적 책임 및 안전 문제로 이어질 수 있습니다. 견고한 신뢰도 및 답변 보류(abstention) 정책은 다중 신호 기반의 보정된 신뢰도 점수 산출, 계층화된 응답 임계값, 결정론적 에스컬레이션 워크플로를 결합해야 합니다.
1. 다중 신호 신뢰도 보정:
LLM의 원시 로그 확률(logprobs)은 도메인 외(out-of-domain) 질의에 대해 보정이 잘 맞지 않는 경우가 많습니다. 따라서 신뢰도 점수는 여러 독립적인 신호를 종합하여 산출해야 합니다.
- 검색 기반 근거 점수(Retrieval Grounding Score): 검색된 근거 청크(chunk)의 시맨틱 유사도 및 재순위화(re-ranking) 신뢰도.
- 주장 단위 함의 검증(Claim-Level Entailment, NLI): 추출된 모든 주장이 검색된 소스 컨텍스트에 의해 뒷받침되는지 NLI(Natural Language Inference) 모델을 통해 검증.
- 시맨틱 엔트로피 및 자기 일관성(Self-Consistency): 여러 번 샘플링하여 생성된 결과물 간의 시맨틱 일관성 측정.
- 모델 토큰 로그 확률: 주요 개체명(named entity) 및 사실적 토큰에 대한 최소 및 평균 로그 확률.
2. 계층화된 답변 보류 정책:
- 높은 신뢰도 (점수 >= 높은 임계값): 인라인 인용 출처와 함께 생성된 답변을 바로 제공.
- 중간 신뢰도 / 모호한 경우 (낮은 임계값 <= 점수 < 높은 임계값): 명시적인 주의 문구와 면책 조항을 포함하여 보수적인 답변을 제공하거나, 사용자에게 추가 명확화를 요구.
- 낮은 신뢰도 / 범위 외 질의 (점수 < 낮은 임계값): 표준화된 거절 메시지를 출력하며 답변을 완전히 보류.
3. 에스컬레이션 및 규제 준수 감사 가능성:
- 결정론적 에스컬레이션: 답변 보류나 심각한 불일치가 발생할 경우 전체 컨텍스트와 함께 HITL(Human-In-The-Loop) 검토 큐나 상담원 티켓 시스템으로 자동 라우팅.
- 감사 로그 및 계보(Lineage): 프롬프트 해시, 검색된 문서 ID, 개별 신뢰도 구성 점수, 최종 라우팅 결정을 포함한 전체 원격 측정 데이터를 규제 감사용으로 기록.
15요청의 복잡도, 비용, 위험도 및 품질 요구 사항에 따라 소형, 중형, 대형 모델 중 하나를 선택하는 모델 라우팅 전략을 설계해 주세요.
프로덕션 모델 라우팅 아키텍처는 복잡도, 지연 시간, 위험도 및 연산 비용의 균형을 고려하여 들어오는 요청을 소형(예: 1B~8B SLM), 중형(예: 14B~70B 모델), 대형(예: 프론티어 모델 또는 대규모 MoE 모델) 계층으로 전달합니다. 라우팅 워크플로는 일반적으로 정적 규칙, 예측 기반 라우팅, 동적 폴백 캐스케이드를 결합합니다:
1. 결정론적/정적 정책 게이트(Deterministic/Static Policy Gates): 고객 등급, 엄격한 지연 시간 SLA(Service Level Agreement), 규제 및 도메인 위험도(예: 의료 진단이나 법률 문서 작성은 즉시 최상위 모델로 라우팅), 또는 단순 규칙 매칭 작업(예: 기본 정규식/포맷팅은 소형 모델로 라우팅)을 기준으로 요청을 필터링합니다.
2. 예측 기반 복잡도 라우팅(Predictive Complexity Routing): 빠르고 가벼운 분류기(임베딩 유사도 검색, 크로스 인코더, 소형 SLM 라우터 등)가 요청의 복잡도, 추론 깊이, 도메인 모호성을 점수화하여 초기에 가장 비용 효율적인 계층을 선택합니다.
3. 동적 실행 및 에스컬레이션 캐스케이드(Dynamic Execution & Escalation Cascades): 프롬프트를 먼저 소형 모델에 전송한 뒤, 출력 신뢰도(토큰 logprobs/엔트로피, 구조화된 스키마 유효성, 가드레일 검사 등)를 평가합니다. 신뢰도가 임계값 미만이거나 검증에 실패하면 라우터가 중형 또는 대형 모델로 단계를 상향(escalate)합니다.
주요 시스템 트레이드오프에는 라우터 지연 시간 오버헤드와 연산 비용 절감 간의 균형, 트래픽 급증 시의 폴백 타임아웃 예산, 지속적 평가(예: 계층 간 출력 품질 저하를 추적하는 섀도우 평가) 등이 있습니다.
class DynamicModelRouter:
def __init__(self, small_client, medium_client, large_client, classifier, guardrail):
self.small = small_client
self.medium = medium_client
self.large = large_client
self.classifier = classifier
self.guardrail = guardrail
async def route_and_execute(self, request):
# 1. Deterministic Risk Gate
if request.risk_level == "high" or request.domain in ["legal", "medical_compliance"]:
return await self.large.generate(request.prompt)
# 2. Predictive Complexity Classifier
complexity = self.classifier.predict_complexity(request.prompt) # 0.0 to 1.0
if complexity < 0.35:
response = await self.small.generate(request.prompt)
if self.guardrail.is_acceptable(response):
return response
return await self.medium.generate(request.prompt) # Fallback
if complexity < 0.75:
response = await self.medium.generate(request.prompt)
if self.guardrail.is_acceptable(response):
return response
return await self.large.generate(request.prompt) # Fallback
# 3. High complexity frontier execution
return await self.large.generate(request.prompt)