수학의 역사, 그리고 이를 계승한 컴퓨팅 아키텍처의 진화 과정에서 '0'의 발명은 단순한 수의 발견을 넘어선 '시스템적 혁명'으로 정의된다. 본 보고서는 업로드된 'AI 수학 가이드북'의 철학에 기반하여, 0이라는 개념이 어떻게 인류 최초의 데이터 압축 기술이자 위치값 체계(Positional System)의 기원이 되었는지, 그리고 현대 AI 시스템인 트랜스포머(Transformer)와 거대 언어 모델(LLM)에서 어떻게 **패딩(Padding)**과 **마스킹(Masking)**이라는 엔지니어링 기법으로 재탄생했는지를 심층 분석한다.
우리는 0을 더 이상 '없음(Nothingness)'으로 해석하지 않는다. 대신 0을 **시스템을 지탱하는 구조적 공백(Structural Void)**이자, 무한한 스케일 확장을 가능케 하는 아키텍처의 버팀목으로 재정의한다. 이 관점은 데이터의 가변적 길이를 고정된 실리콘 기하학(GPU Tensor Cores)에 맞추는 과정에서 발생하는 막대한 비효율과 수학적 오류를 해결하는 핵심 열쇠가 된다.
인류가 0을 발명하기 전, 수(Number)는 위치가 아닌 상징에 의존했다. 가장 대표적인 예가 로마 숫자 시스템이다. 로마 숫자는 덧셈 기반의 표기법(Additive Notation)을 따르며, 문자가 가진 고유한 값(V=5, X=10)을 나열하여 수를 표현한다. 이 시스템에서 '위치'는 큰 의미를 갖지 않는다. 숫자 는 어디에 놓이든 1을 의미한다(단, 감산 표기법 제외).
이러한 구조는 정적인 수량을 기록하는 데에는 적합했으나, **연산(Computation)**과 **확장(Scaling)**에 치명적인 한계를 드러냈다. 예를 들어 과 같은 간단한 곱셈조차 로마 숫자()로 수행하려면, 각 자릿수를 분해하고 수십 번의 중간 단계를 거쳐야 하는 악몽 같은 복잡성을 유발했다.3
더욱 본질적인 문제는 스케일의 확장성이다. 로마 시스템에서 더 큰 수를 표현하려면 새로운 문자를 계속 발명해야 했다(L, C, D, M...). 이는 시스템의 규칙이 데이터의 크기에 따라 계속 변해야 함을 의미하며, 이는 현대 컴퓨터 과학 관점에서 볼 때 '확장 불가능한 아키텍처'의 전형이다.6 당시 로마인들은 계산을 위해 주판(Abacus)과 같은 물리적 도구를 사용해야 했는데, 이는 곧 '데이터 저장(표기)'과 '데이터 처리(계산)'가 분리되어 있었음을 시사한다. 표기법 자체가 계산 엔진이 되지 못했던 것이다.
0의 도입은 이 모든 것을 바꾸어 놓았다. 힌두-아라비아 숫자 체계와 마야 문명에서 독립적으로 발명된 0은 '아무것도 없음'을 나타내는 수단이 아니라, **빈 자리를 지키는 수단(Placeholder)**으로 기능했다.7
숫자 205를 생각해보자. 여기서 가운데의 0은 십의 자리에 아무런 값이 없음을 나타내지만, 동시에 백의 자리에 있는 2가 '2'가 아닌 '200'의 가치를 갖도록 위치를 강제한다.9 0이 없다면 25와 205를 구분할 수 없게 되며, 위치에 따른 가치 부여(Place Value System) 자체가 붕괴한다.
이것이 바로 시스템적 가치이다. 0이라는 단 하나의 기호를 도입함으로써, 인류는 단 10개의 심볼(0~9)만으로 우주의 모든 수를 표현할 수 있는 무한한 확장성을 획득했다. 이는 정보 이론적으로 볼 때 엄청난 **데이터 압축(Compression)**이다. 0은 "이 자리는 비어 있지만, 이 자리가 존재함으로써 다른 숫자들의 위상이 결정된다"는 메타 정보를 담고 있다.
이러한 위치값 체계의 철학은 현대 컴퓨터의 근간인 이진법(Binary System)으로 직결된다. 전압이 없음(0)과 있음(1)의 상태, 그리고 그 상태가 놓인 '위치(Bit Position)'가 결합되어 모든 디지털 정보를 구성한다.9 현대 AI 시스템에서 0은 단순한 숫자가 아니다. 그것은 데이터가 존재하지 않는 공간을 채우는 **구조적 충전재(Structural Filler)**이자, 연산의 차원을 맞추는 인터페이스이다.
아이가 주판에서 알이 없는 빈 칸을 보며 "아무것도 없다"고 말할 때, 아키텍트는 그 빈 칸이 "자릿수를 지켜주는 버팀목"임을 본다. AI 수학 가이드북은 독자들에게 이 '빈 의자'가 없다면 거대 언어 모델의 행렬 연산도, 수십억 개의 파라미터도 존재할 수 없음을 직관적으로 이해시킨다.9
추상적인 수학의 세계를 떠나 현실의 엔지니어링 현장으로 들어오면, 아키텍트는 **'데이터의 가변성'**과 '하드웨어의 고정성' 사이의 거대한 충돌에 직면한다. 이를 래그드 텐서(Ragged Tensor) 딜레마라 부른다.
현실 세계의 데이터, 특히 언어 데이터는 본질적으로 길이가 제각각이다.
사용자 A의 입력: "안녕" (토큰 길이: 1)
사용자 B의 입력: "트랜스포머 아키텍처의 어텐션 메커니즘에 대해 상세히 설명해줘." (토큰 길이: 15)
이처럼 들쭉날쭉한(Ragged) 데이터를 처리해야 하는 반면, 이를 계산하는 하드웨어인 GPU(Graphics Processing Unit)와 TPU(Tensor Processing Unit)는 고도로 정형화된 기하학을 요구한다. GPU는 SIMD(Single Instruction, Multiple Data) 병렬 처리에 최적화되어 있어, 모든 데이터가 직사각형(Rectangular) 형태의 행렬로 줄 맞춰 들어올 때 최고의 성능(TFLOPS)을 발휘한다.11
만약 가변 길이 데이터를 있는 그대로 처리하려 한다면, GPU는 병렬 처리를 포기하고 각 데이터를 순차적으로 처리해야 하며, 이는 막대한 연산 속도 저하를 초래한다. 여기서 아키텍트는 결단을 내려야 한다. 데이터를 하드웨어에 맞출 것인가, 하드웨어를 데이터에 맞출 것인가?
가장 보편적인 해결책은 데이터를 하드웨어의 형태에 맞추는 것이다. 이를 위해 도입된 기법이 바로 **패딩(Padding)**이다. 패딩은 짧은 시퀀스의 뒷부분을 의미 없는 '0'(또는 특수 토큰 <PAD>)으로 채워, 배치의 모든 시퀀스 길이를 가장 긴 시퀀스()에 맞추는 작업이다.14
Table 2.1: 패딩 적용 전후의 데이터 형태 비교
| 시퀀스 ID | 원본 데이터 (Ragged) | 패딩 적용 후 (Rectangular) |
|---|---|---|
| Seq 1 | `` (Len: 2) | `` |
| Seq 2 | `` (Len: 5) | `` |
| Batch Shape | 불가능 (N/A) | (2, 5) 텐서 |
이렇게 0으로 빈 공간을 채움으로써, 데이터는 GPU가 선호하는 완벽한 직사각형 텐서가 된다. 여기서 '0'은 정보가 아니라, 시스템 호환성을 위한 구조적 희생이다.
패딩은 형상의 문제를 해결하지만, **'패딩 오버헤드(Padding Overhead)'**라는 새로운 비용을 청구한다. 연구에 따르면, 최적화되지 않은 훈련 시나리오에서 전체 연산의 **50%에서 70%**가 아무런 의미 없는 패딩 토큰(0)을 계산하는 데 낭비될 수 있다.11
GPU는 0이 패딩인지 유의미한 숫자인지 알지 못한 채, 성실하게 가중치와 곱하고(Multiplication), 메모리에 저장하며, 심지어 이에 대한 그라디언트(Gradient)까지 계산한다. 이는 전력 낭비이자, 추론 비용(Latency & Cost)의 증가로 직결된다.
이 딜레마를 해결하기 위해 아키텍트는 다음과 같은 전략적 선택지를 고민해야 한다:
정적 배칭 (Static Batching): 모든 데이터를 모델의 최대 길이(예: 4096)로 강제 패딩한다. 구현은 쉽지만, 짧은 문장 처리에 막대한 자원이 낭비된다.18
버킷팅 (Bucketing): 비슷한 길이의 문장끼리 모아서 처리한다. 예를 들어 길이 100~200인 문장들끼리 배치(Batch)를 구성하면 패딩을 최소화할 수 있다. 그러나 데이터 로더(Data Loader)의 복잡도가 증가하고, 특정 길이의 데이터가 모일 때까지 기다려야 하므로 대기 시간(Latency)이 발생할 수 있다.19
시퀀스 패킹 (Sequence Packing / Seam Separation): 여러 개의 짧은 문장을 이어 붙여 하나의 긴 시퀀스로 만들고, 꽉 채워서 처리한다. GPU 효율을 극대화(100%에 근접)할 수 있지만, 문장 간의 경계가 섞이지 않도록 어텐션 마스크를 정교하게 조작해야 하는 구현 난이도가 있다.11
0의 시스템적 필요성은 소프트웨어를 넘어 하드웨어 깊숙한 곳까지 영향을 미친다. NVIDIA의 텐서 코어(Tensor Core)와 같은 최신 가속기는 행렬 연산을 수행할 때 차원이 8 또는 16의 배수일 때만 가속 모드가 활성화되는 경우가 많다.21
시퀀스 길이가 127이라면, 아키텍트는 1개의 '시스템 제로'를 추가하여 128로 만들어야 한다. 그렇지 않으면 GPU는 느린 연산 경로(Scalar Path)를 타게 되어 성능이 급격히 저하된다. 이처럼 0은 하드웨어의 물리적 특성에 데이터를 정렬(Alignment)시키는 쐐기(Shim) 역할을 수행한다.
패딩을 통해 데이터의 형태(Shape)를 맞추었다면, 이제 수학적 정합성(Correctness)을 검증해야 한다. 여기서 초보 아키텍트가 가장 흔하게 저지르는 실수가 발생한다. 바로 **"0은 계산 결과도 0일 것"**이라는 오해이다. 특히 트랜스포머의 핵심인 **어텐션 메커니즘(Attention Mechanism)**에서 이 오해는 치명적인 버그를 낳는다.
어텐션 메커니즘은 쿼리()와 키()의 내적(Dot Product)을 통해 연관성 점수(Logits)를 구하고, 이를 소프트맥스(Softmax) 함수에 통과시켜 확률값으로 변환한다.
소프트맥스 함수는 다음과 같이 정의된다:
문제는 패딩 토큰의 값인 '0'이 이 함수에 들어갈 때 발생한다. 우리는 0이 '없음'을 의미하므로 결과도 0이 되어 무시되기를 기대한다. 하지만 지수 함수(Exponential)의 세계에서 0은 1이 된다.
만약 패딩 토큰의 점수가 0이라면, 소프트맥스 분자는 1이 되고, 이는 전체 확률 분포에서 양수(Positive)의 확률값을 할당받게 된다.
예를 들어, [실제단어, 패딩]의 점수가 [2.0, 0.0]이라고 가정하자.
모델은 전체 주의력(Attention)의 12%를 아무 의미 없는 패딩 토큰에 쏟게 된다. 이는 모델이 "빈 공간"을 문맥으로 착각하게 만들어, **환각(Hallucination)**을 유발하거나 성능을 심각하게 저하시키는 원인이 된다.
이 문제를 해결하기 위해 우리는 '수학적 0'이 아닌 '함수적 0'을 찾아야 한다. 소프트맥스 함수의 출력이 0이 되게 하려면, 입력값은 무엇이어야 하는가?
즉, 어텐션 메커니즘 내부에서 패딩 토큰의 위치에는 0이 아니라 **음의 무한대()**를 대입해야 한다. 실제 구현에서는 float('-inf') 또는 -1e9와 같은 매우 작은 수를 사용한다.
수정된 연산 흐름:
mask = [0, 1] (1이 패딩 위치)[2.0, 0.0] [2.0, -\infty]이제서야 비로소 패딩 토큰은 수학적으로 "보이지 않는 투명인간"이 된다. 이것이 바로 **어텐션 마스크(Attention Mask)**의 원리이다.
마스킹은 순전파(Forward Pass)뿐만 아니라 역전파(Backward Pass)에서도 중요한 역할을 한다. 패딩 토큰의 확률이 0이 되면, 해당 경로를 통해 흐르는 오차(Loss)의 기울기(Gradient) 또한 0이 된다.
이는 학습 과정에서 모델이 "빈 공간"의 패턴을 학습하려 시도하는 것을 원천적으로 차단한다.
만약 마스킹을 소프트맥스 이전에 하지 않고 이후에 결과값에 0을 곱하는 식으로 처리한다면, 소프트맥스의 분모(합계) 계산에 이미 패딩 값이 포함되어 전체 확률 분포가 왜곡되므로 수학적으로 틀린 접근이 된다. 반드시 Logit 단계에서 를 주입해야 한다.
이제 앞서 다룬 개념들이 실제 코드와 아키텍처 다이어그램으로 어떻게 구현되는지 확인한다.
아래 다이어그램은 가변 길이 입력이 패딩을 통해 텐서가 되고, 마스킹을 통해 안전하게 연산되는 전체 과정을 보여준다.
다음은 PyTorch를 사용하여 가변 길이 시퀀스를 패딩하고, 어텐션 연산 시 마스킹을 적용하는 핵심 코드이다.
import torch
import torch.nn.functional as F
from torch.nn.utils.rnn import pad_sequence
# ---------------------------------------------------------
# 1. Basecamp: Variable-length input sequences (Ragged Data)
# ---------------------------------------------------------
# Token sequences with different lengths (e.g., text token IDs)
sequences = [
torch.tensor([10, 20, 30]), # Length 3
torch.tensor([40, 50, 60, 70]), # Length 4
torch.tensor([80, 90, 100, 110, 120]) # Length 5
]
# ---------------------------------------------------------
# 2. Architect's Challenge: Structural Zero (Padding)
# ---------------------------------------------------------
# Align sequences to the maximum length (5) with zeros
# batch_first=True -> Output Shape: (Batch, Seq_Len)
padded_batch = pad_sequence(sequences, batch_first=True, padding_value=0)
print(f"Rectangular Batch Shape: {padded_batch.shape}")
# Output: torch.Size([3, 5]) -> Ready for parallel GPU computation
# ---------------------------------------------------------
# 3. Math Debugging: Generating the Mask (Find Zeros)
# ---------------------------------------------------------
# Create a boolean mask where padding tokens (0) are marked as True
padding_mask = (padded_batch == 0)
print(f"Mask for the first sequence:\n{padding_mask[0]}")
# Output: tensor([False, False, False, True, True])
# Note: Data positions are False, Padding positions are True
# ---------------------------------------------------------
# 4. Solution: Masked Softmax Implementation
# ---------------------------------------------------------
def attention_softmax_with_mask(scores, mask):
"""
Applies attention masking and computes softmax probabilities.
"""
# Key Logic: Fill padding positions with negative infinity (-inf)
# masked_fill: Replace values where mask is True
masked_scores = scores.masked_fill(mask, float('-inf'))
# Compute Softmax: exp(-inf) becomes 0, ignoring the padding
probs = F.softmax(masked_scores, dim=-1)
return probs
# Mock attention logits (Assuming all scores are initialized to 1.0)
raw_scores = torch.ones_like(padded_batch, dtype=torch.float)
# Execute Masked Softmax
attention_probs = attention_softmax_with_mask(raw_scores, padding_mask)
print(f"Final Attention Probabilities (Sequence 1):\n{attention_probs[0]}")
# Output: tensor([0.3333, 0.3333, 0.3333, 0.0000, 0.0000])
# Analysis: Only valid tokens share the probability (1/3 each).
위 코드는 0이라는 숫자가 AI 시스템 내에서 두 가지의 서로 다른 역할을 수행함을 보여준다.
**pad_sequence**에서의 0: 텐서의 모양(Shape)을 유지하기 위한 물리적 채움재.
**masked_fill**에서의 : 확률 계산에서 해당 위치를 배제하기 위한 논리적 차단막.
아키텍트는 이 두 가지 0의 변환 과정을 정확히 이해하고 제어해야 한다. 이를 간과할 경우, 모델은 존재하지 않는 데이터(패딩)를 학습하게 되고, 이는 곧 지능형 시스템의 논리적 결함으로 이어진다.
Chapter 2를 통해 우리는 초등학교 수학에서 배운 0과 자리값의 개념이 어떻게 현대 AI 아키텍처의 가장 중요한 엔지니어링 문제인 **스케일링(Scaling)**과 **정렬(Alignment)**의 기초가 되는지 확인했다.
역사적으로 0은 수의 표기법을 덧셈(Additive)에서 위치(Positional) 기반으로 전환시켜 무한한 수의 확장을 가능케 했다.
엔지니어링적으로 0은 가변적인 세상의 데이터를 고정된 **실리콘 기하학(Tensor)**에 담을 수 있게 하는 **유연성의 도구(Padding)**이다.
수학적으로 0은 **마스킹(Masking)**을 통해 모델의 주의력을 제어하고, 노이즈를 차단하는 논리 게이트 역할을 수행한다.
결국 AI 시스템을 설계한다는 것은 **'데이터가 있는 곳'**을 처리하는 것만큼이나, **'데이터가 없는 곳(0)'**을 어떻게 정의하고 관리할 것인가를 결정하는 예술이다. 0은 비어 있는 것이 아니라, 시스템의 질서를 유지하는 가장 강력한 **구조(Structure)**이다.
다음 장 예고 (Chapter 3. 변수/일반화):
0을 통해 시스템의 구조를 잡았다면, 이제 그 구조 위에서 변하는 값들을 다루는 법을 배워야 한다. 다음 장에서는 라는 문자가 어떻게 개별적인 사실들을 **일반화된 법칙(Generalization)**으로 승화시키는지, 그리고 이것이 어떻게 머신러닝의 파라미터(Parameter) 개념으로 진화하는지 탐구한다.