AI 추론 엔진은 어떻게 만드는가

대부분의 사람은 AI 모델을 다운로드하면 곧바로 AI를 실행할 수 있다고 생각한다. 하지만 모델 파일은 실행 프로그램이 아니다.

모델에는 수많은 가중치와 텐서가 저장되어 있을 뿐이고, 이를 CPU나 GPU에서 실제 연산으로 바꾸어 토큰을 생성하려면 별도의 실행 구조가 필요하다.

나 역시 CPU에서 LLM을 직접 실행하는 Runtime을 개발하면서 이 문제를 처음부터 다시 살펴보게 되었다. 처음에는 Matrix 연산에서 시작했지만, Tensor와 Transformer를 거쳐 실제 LLM의 토큰 생성까지 연결하면서 하나의 AI 추론 엔진이 어떤 구조로 만들어지는지 확인할 수 있었다.


AI 추론 엔진의 기본 구조

일반적인 AI 추론 엔진은 다음과 같은 흐름으로 구성할 수 있다.

모델 파일 → 모델 로더 → 텐서 → 연산 그래프 → 연산 커널 → 메모리 관리 → 실행 스케줄러 → 결과 출력

LLM이라면 여기에 토크나이저와 KV Cache, 샘플러 등이 추가된다.

각각의 역할은 명확하다.

  • 모델 로더: 모델 파일에서 가중치와 설정 정보를 읽는다.
  • 토크나이저: 입력 문장을 모델이 처리할 수 있는 토큰으로 변환한다.
  • 텐서: 모델의 가중치와 연산 데이터를 저장한다.
  • 연산 그래프: 어떤 연산을 어떤 순서로 수행할지 표현한다.
  • 연산 커널: 실제 행렬곱과 벡터 연산 등을 수행한다.
  • 메모리 관리자: 가중치와 중간 결과, 캐시 등을 관리한다.
  • 실행 스케줄러: 여러 연산을 효율적으로 실행한다.
  • 샘플러: LLM에서 다음 토큰을 선택한다.

결국 추론 엔진을 만든다는 것은 이 구성 요소들을 하나의 실행 흐름으로 연결하는 작업이다.


1. 먼저 모델 파일을 읽어야 한다

가장 먼저 필요한 것은 모델 파일이다.

LLM 추론 엔진을 직접 만들려면 먼저 실제 모델의 가중치를 확보해야 한다. 처음부터 모델을 학습할 필요는 없다. 공개되어 있는 사전학습 모델을 내려받아 AI 추론 엔진에서 직접 읽으면 된다.

예를 들어 실습 대상으로 Qwen3-1.7B를 사용할 수 있다. 모델은 Hugging Face와 같은 모델 저장소에서 받을 수 있으며, Qwen3의 경우 모델 파일과 설정 정보가 함께 제공된다.

Qwen3-1.7B 모델 페이지

모델을 내려받으면 내부에는 대략 다음과 같은 정보가 존재한다.

  • 모델 설정 파일
  • Tokenizer 파일
  • 모델 가중치 파일
  • 각종 메타데이터

가중치 파일에는 수많은 Tensor가 저장되어 있으며, Tensor마다 이름과 크기, 데이터 형식이 다르다.

따라서 첫 번째 단계에서는 모델 파일을 읽어 다음과 같은 정보를 확인해야 한다.

  • 모델 구조
  • 레이어 수
  • Hidden Size
  • Attention Head 수
  • KV Head 수
  • Vocabulary 크기
  • 가중치의 데이터 형식
  • 각 Tensor의 이름과 크기
  • 모델의 설정 정보

그리고 읽어온 가중치를 추론 엔진이 사용할 수 있는 내부 Tensor 자료구조로 변환한다.

여기서 중요한 점이 하나 있다.

모델 파일을 읽는 것과 모델을 실행하는 것은 전혀 다른 문제다.

모델 파일에는 주로 “무엇을 계산해야 하는가”를 구성하는 가중치와 설정 정보가 들어 있다. 추론 엔진은 이것을 읽은 뒤 CPU나 GPU에서 실제 연산을 수행할 수 있는 형태로 연결해야 한다.

나 역시 CPU 기반 LLM Runtime(AI 추론 엔진)을 직접 구현할 때 가장 먼저 이 부분부터 시작했다. 실제 모델을 읽어 구조와 Tensor를 확인하고, 이후 Matrix 연산과 Tensor 연산, Transformer 연산을 연결하는 방식으로 범위를 확장했다.


2. 텐서 시스템을 만든다

AI 연산은 텐서(Tensor) 데이터의 계산이다.

텐서는 쉽게 말하면 AI에서 사용하는 다차원 숫자 배열이다.
1차원 배열은 벡터, 2차원 배열은 행렬이며, 여기에 차원이 더해지면 모두 텐서라고 부른다. LLM에서는 입력 데이터와 가중치, Attention 결과 등 대부분의 데이터가 텐서 형태로 처리된다.

예를 들어 행렬곱을 수행하려면 입력 텐서와 가중치 텐서가 필요하다.

따라서 추론 엔진 내부에는 텐서의 크기와 데이터 형식, 메모리 위치 등을 관리하는 시스템이 필요하다.

단순한 배열을 사용하는 것과 AI 추론용 텐서 시스템을 만드는 것은 차이가 크다.

AI 추론 엔진은 추론 과정에서는 수많은 텐서가 생성되고 사라지기 때문에 메모리를 어떻게 할당하고 재사용할 것인지도 중요한 문제가 된다.


3. AI 연산을 구현한다

다음 단계는 실제 AI 연산이다.

LLM을 예로 들면 다음과 같은 연산들이 필요하다.

  • Matrix Multiplication(행렬 곱셈) — 여러 숫자 행렬을 곱하는 연산
  • Addition(덧셈) — 텐서의 값을 더하는 연산
  • Multiplication(곱셈) — 텐서의 값을 곱하는 연산
  • RMSNorm(Root Mean Square Normalization) — 신경망의 값의 크기를 안정적으로 조정하는 정규화 연산
  • Softmax — 여러 출력값을 확률 형태로 변환하는 연산
  • RoPE(Rotary Positional Embedding) — 토큰의 위치 정보를 표현하는 방법
  • Attention(어텐션) — 입력 데이터에서 서로 어떤 부분이 중요한지 계산하는 연산

AI 추론 엔진의 핵심은 이러한 연산을 실제 CPU나 GPU에서 수행하는 것이다.

특히 행렬곱은 LLM 추론에서 매우 큰 비중을 차지하기 때문에 하드웨어에 맞는 효율적인 연산 커널을 사용하는 것이 중요하다.


4. 연산을 실행하는 엔진이 필요하다

연산을 각각 구현했다고 해서 바로 AI 모델이 실행되는 것은 아니다.

어떤 연산을 먼저 수행하고 그 결과를 어디로 전달할 것인지 결정해야 한다.

예를 들어 다음과 같은 흐름이 만들어질 수 있다.

Input(입력 문장)
→ Tokenization(토큰화)
→ Embedding(임베딩)
→ Transformer 연산
→ Output Logits(출력값)
→ Decoding(디코딩)
→ Token → Text

AI 추론 엔진의 LLM의 추론 과정은 단순히 입력을 받아 출력하는 과정이 아니다.

입력 문장을 Tokenization(토큰화) 과정을 통해 토큰으로 변환하고, 이를 Embedding(임베딩)하여 모델이 처리할 수 있는 숫자 데이터로 만든다. 이후 Transformer의 Attention과 Feed Forward 등의 연산을 거쳐 다음에 생성할 토큰의 확률을 계산한다.

선택된 토큰은 다시 다음 입력으로 사용되며, 이 과정이 반복되면서 문장이 생성된다. 마지막으로 생성된 토큰을 Decoding(디코딩)하여 사람이 읽을 수 있는 텍스트로 변환한다.


5. 메모리 관리가 중요하다 — 특히 KV Cache

AI 추론 엔진을 만들면서 생각보다 많은 시간을 사용하게 되는 부분이 메모리 관리다.

LLM은 모델 가중치 자체가 크기 때문에 단순히 모든 데이터를 메모리에 올리는 것만으로 끝나지 않는다.

추론 과정에서는 다음과 같은 데이터가 함께 사용된다.

가중치 + 입력 데이터 + 중간 연산 결과 + KV Cache

특히 KV Cache(Key-Value Cache)는 긴 문맥을 처리할수록 계속 커진다.

LLM은 한 번에 전체 문장을 다시 계산하는 것이 아니라, 토큰을 하나씩 생성하면서 이전 토큰의 Attention 정보를 활용한다. 이때 이전 토큰에 대해 계산한 Key와 Value를 저장해 두었다가 다음 토큰을 생성할 때 재사용한다. 이것이 KV Cache다.

예를 들어 입력이 1,000개의 토큰이라면 처음에는 1,000개의 토큰에 대한 Key와 Value가 필요하다. 이후 새로운 토큰이 생성될 때마다 새로운 Key와 Value가 추가된다.

따라서 생성 과정이

1,000 → 1,001 → 1,002 → 1,003 → …

처럼 진행되면서 KV Cache도 함께 증가한다.

문제는 문맥 길이가 길어질수록 필요한 메모리도 커진다는 것이다. 여러 요청을 동시에 처리하는 서버 환경에서는 각각의 요청마다 KV Cache가 필요하기 때문에 메모리 사용량이 더욱 빠르게 증가할 수 있다.

따라서 AI 추론 엔진에서는 KV Cache를 단순히 계속 메모리에 추가하는 방식으로 처리해서는 안 된다. 미리 필요한 메모리를 확보하거나 효율적으로 재사용하고, 현재 사용 중인 문맥의 범위를 관리하며, 더 이상 필요하지 않은 Cache는 적절한 시점에 해제해야 한다.

또한 긴 문맥을 무조건 모두 유지할 필요가 없는 경우에는 Context Window(컨텍스트 윈도우)를 제한하거나, 모델과 Runtime이 지원한다면 KV Cache 양자화 등을 활용해 메모리 사용량을 줄일 수 있다.

결국 KV Cache 관리에서 중요한 것은 얼마나 빠르게 계산하느냐뿐만 아니라, 증가하는 Cache를 어떻게 관리하면서 제한된 메모리 안에서 추론을 계속 수행하느냐이다.


6. 양자화(Quantization)와 품질 문제를 고려해야 한다

모델의 가중치를 그대로 사용하면 메모리 사용량이 상당히 커질 수 있다.

그래서 실제 추론 엔진에서는 FP32(32비트 부동소수점), FP16(16비트 부동소수점), BF16(16비트 부동소수점)뿐만 아니라 INT8(8비트 정수), INT4(4비트 정수)와 같은 다양한 데이터 형식을 사용할 수 있다.

양자화의 목적은 단순히 모델 파일의 크기를 줄이는 것만이 아니다.

메모리에 필요한 데이터량을 줄이고 메모리 이동량을 감소시켜 추론 효율을 높일 수 있다. 특히 CPU 추론에서는 메모리 대역폭과 캐시 효율이 중요하기 때문에 가중치를 작은 데이터 형식으로 표현하는 것이 상당한 의미를 가진다.

하지만 정밀도를 낮춘다고 해서 아무 문제 없이 동일한 결과가 나오는 것은 아니다.

가중치를 FP16이나 INT8, INT4 등으로 변환하면 원래의 실수값을 완전히 동일하게 표현할 수 없기 때문에 양자화 오차(Quantization Error)가 발생한다. 이 오차가 누적되면 모델의 출력이 달라지고, 특정 작업에서는 답변 품질이나 추론 정확도가 떨어질 수 있다.

특히 INT4처럼 정밀도가 크게 낮아지는 경우에는 모든 가중치를 동일한 방식으로 단순하게 줄이는 것보다 어떤 가중치가 양자화에 민감한지 고려하는 것이 중요하다.

이를 해결하기 위해 실제 AI 추론 엔진을 만들때 양자화 과정에서는 여러 가지 방법을 사용한다.

  • Calibration(보정) — 실제 데이터를 사용해 적절한 양자화 범위를 결정
  • Per-Tensor / Per-Channel Quantization — 전체 Tensor 또는 채널별로 서로 다른 범위를 적용
  • Group-wise Quantization — 가중치를 일정한 그룹으로 나누어 양자화
  • Outlier 처리 — 다른 값보다 지나치게 큰 값을 별도로 처리
  • 혼합 정밀도 — 민감한 연산은 높은 정밀도를 유지하고 나머지만 낮은 정밀도로 처리
  • 양자화 후 검증 — 양자화 전후의 모델 출력과 실제 작업 성능을 비교

좋은 양자화는 단순히 FP32를 INT4로 바꾸는 작업이 아니다.

메모리 사용량, 연산 속도, 정확도 사이에서 적절한 균형점을 찾는 작업에 가깝다.

실제로 AI 추론 엔진을 개발할 때도 데이터 형식은 단순한 저장 형식의 문제가 아니다. 가중치를 어떤 형식으로 저장하고, 메모리에서 어떻게 읽으며, CPU의 어떤 연산 방식으로 계산할 것인지까지 함께 설계해야 한다.


7. 마지막으로 토큰을 생성한다

Transformer 연산이 끝나면 Vocabulary 전체에 대한 확률값이 나온다.

AI 추론 엔진은 이 결과를 이용해 다음 토큰을 선택한다.

가장 확률이 높은 토큰을 선택할 수도 있고, Temperature나 Top-K, Top-P 등의 방법을 적용할 수도 있다.

선택된 토큰을 다시 모델의 입력으로 넣고 같은 과정을 반복한다.

결국 LLM의 텍스트 생성은 다음과 같은 반복 구조가 된다.

입력 토큰 → Transformer 연산 → 다음 토큰 선택 → 다시 입력 → 반복

이 과정을 얼마나 효율적으로 수행하느냐가 실제 LLM 추론 속도를 결정한다.


8. AI 추론 엔진은 결국 하드웨어와 연결된다

여기까지 구현하면 기본적인 추론 엔진의 형태가 만들어진다.

하지만 같은 모델이라도 CPU(중앙처리장치), GPU(그래픽처리장치), NPU(신경망처리장치) 중 어떤 하드웨어에서 실행하느냐에 따라 최적의 구현 방식은 크게 달라진다.

CPU에서 실행하는 경우

CPU는 GPU처럼 대규모 병렬 연산에 특화된 장치는 아니지만, 범용적인 연산을 수행할 수 있다는 장점이 있다.

CPU 추론에서는 다음과 같은 요소가 중요하다.

  • Multi-Core(멀티코어)를 이용한 병렬 처리
  • SIMD를 이용한 벡터 연산
  • CPU Cache(캐시)의 효율적인 활용
  • 메모리 대역폭
  • 데이터 복사 최소화
  • 메모리 접근 패턴 최적화

CPU가 항상 GPU보다 불리한 것도 아니다.

모델이 작거나, 한 번에 처리하는 요청이 많지 않거나, GPU를 별도로 사용할 필요가 없는 환경에서는 CPU가 오히려 적합할 수 있다.

특히 개인 PC, 서버의 백그라운드 작업, Edge AI(엣지 AI), 로컬 AI, 임베디드 시스템처럼 별도의 GPU를 사용하기 어려운 환경에서는 CPU만으로 추론할 수 있다는 것 자체가 중요한 장점이 된다.

또한 CPU는 시스템 메모리를 직접 활용할 수 있기 때문에 대형 모델을 GPU 메모리에 모두 올리기 어려운 상황에서도 활용할 수 있다.

GPU에서 실행하는 경우

GPU는 수많은 연산을 동시에 처리하는 병렬 연산에 특화되어 있다.

LLM에서는 Matrix Multiplication과 같은 대규모 행렬 연산이 반복되기 때문에 GPU의 병렬 처리 구조가 매우 유리하다.

GPU 추론에서는 다음과 같은 요소가 중요하다.

  • GPU 병렬 연산 구조
  • GPU 메모리 용량과 대역폭
  • 연산 Kernel(커널)
  • CPU와 GPU 사이의 데이터 이동
  • 여러 요청을 묶는 Batch Processing(배치 처리)

특히 여러 사용자의 요청을 동시에 처리하는 서버에서는 GPU의 높은 병렬 처리 능력을 효과적으로 활용할 수 있다.

그렇다면 GPU가 없어도 되는 경우는?

모든 AI 추론 엔진 추론 과정에 고성능 GPU가 필요한 것은 아니다.

작은 모델을 실행하거나, 동시에 처리해야 할 요청이 많지 않거나, 로컬 환경에서 개인적으로 AI를 사용하는 경우에는 CPU만으로도 충분할 수 있다.

예를 들어 문서 요약, 간단한 질의응답, 코드 보조, 로컬 검색 시스템처럼 처리량보다 비용, 전력, 설치 환경, 데이터 보안이 중요한 경우에는 GPU 없이 CPU 기반 추론을 선택할 수 있다.

또한 GPU를 사용하면 높은 성능을 얻을 수 있지만 GPU 자체의 가격과 전력 소비, 메모리 용량이라는 제약도 고려해야 한다.

결국 중요한 것은 GPU가 있느냐 없느냐가 아니라 어떤 환경에서 어떤 모델을 어떤 방식으로 실행하느냐이다.

환경적합한 하드웨어
소형 모델CPU도 충분
개인 PC의 로컬 AICPU / GPU
대규모 LLMGPU
많은 동시 사용자GPU
저전력 Edge AICPU / NPU
GPU를 사용할 수 없는 환경CPU
높은 처리량이 필요한 서버GPU
비용과 전력 효율이 중요한 경우CPU / NPU


직접 AI 추론 엔진을 만들 수 있을까?

가능하다.

처음부터 모든 기능을 구현할 필요는 없다. 작은 모델 하나를 대상으로 모델 파일을 읽고 → 텐서를 구성하고 → 기본 연산을 구현하고 → 순차적으로 실행하는 것부터 시작할 수 있다. 이후 멀티코어 처리, SIMD, 양자화, 메모리 관리, KV Cache, 실행 스케줄링 등을 하나씩 추가하면서 추론 엔진을 발전시킬 수 있다.

이미 llama.cpp, ONNX Runtime, TensorRT-LLM, vLLM과 같은 다양한 추론 엔진과 런타임이 존재하는 것도 같은 이유다. 단순히 모델을 불러오는 것만으로는 실제 하드웨어에서 AI 모델을 효율적으로 실행할 수 없으며, 모델과 하드웨어 사이에서 데이터를 관리하고 연산을 실행하는 별도의 소프트웨어 계층이 필요하다.

결국 AI 추론 엔진은 특별한 마법 같은 기술이 아니다. 모델 로딩 → 텐서 관리 → 연산 구현 → 메모리 관리 → 실행 → 결과 생성이라는 기본 구조에서 시작한다. 다만 모델이 커지고 하드웨어가 복잡해질수록 메모리, 병렬 처리, 연산 커널, 캐시, 스케줄링 등 고려해야 할 요소가 급격히 증가한다.

따라서 추론 엔진의 핵심은 AI 모델을 얼마나 잘 이해하느냐와 동시에 하드웨어를 얼마나 효율적으로 활용하느냐에 있다. AI의 경쟁이 모델 자체를 넘어 모델을 얼마나 빠르고 효율적으로 실행할 수 있는가로 확대되면서, 추론 엔진의 중요성도 더욱 커지고 있다.


[글에서 사용한 머리 아픈 용어]

  • 추론 런타임(Runtime): 학습이 끝난 AI 모델을 실제 하드웨어에서 실행할 수 있도록 모델 로딩, 연산, 메모리, 실행 과정 등을 관리하는 소프트웨어 환경입니다.
  • Kernel(커널): 실제 하드웨어에서 특정 연산을 수행하는 실행 코드입니다. 행렬곱이나 벡터 연산 등을 CPU나 GPU에 맞게 최적화하여 실행합니다.
  • Attention(어텐션): 입력 데이터에서 어떤 정보가 서로 중요한 관계를 가지는지 계산하는 Transformer의 핵심 연산입니다.
  • RMSNorm: 신경망에서 데이터의 값 크기를 안정적으로 조정하는 정규화 방법입니다.
  • Softmax: 여러 출력 점수를 0~1 사이의 확률 형태로 변환하는 함수입니다.
  • RoPE(Rotary Positional Embedding): Transformer가 토큰의 위치 정보를 이해할 수 있도록 회전 방식으로 위치 정보를 반영하는 방법입니다.
  • KV Cache: LLM이 이전 토큰에서 계산한 Key와 Value를 저장해 두었다가 다음 토큰 생성에서 재사용하는 메모리 구조입니다.
  • Logits: 모델이 다음 토큰을 선택하기 전에 출력하는 원시 점수입니다. Softmax 등의 과정을 거치면 확률로 변환할 수 있습니다.
  • Quantization(양자화): 모델의 가중치를 FP32, FP16, INT8, INT4 등 더 작은 데이터 형식으로 표현해 메모리 사용량과 연산 비용을 줄이는 기술입니다.
  • Calibration(보정): 양자화할 때 실제 데이터를 분석하여 적절한 값의 범위나 변환 기준을 결정하는 과정입니다.
  • Group-wise Quantization: 가중치를 여러 그룹으로 나누고 각 그룹에 별도의 양자화 기준을 적용하는 방식입니다.
  • SIMD: 하나의 명령으로 여러 데이터를 동시에 처리하는 CPU의 벡터 연산 기술입니다.
  • Batch Processing: 여러 개의 입력이나 요청을 하나로 묶어 동시에 처리하는 방식입니다.
  • Context Window: LLM이 한 번의 대화나 추론 과정에서 처리할 수 있는 최대 토큰 범위입니다.
  • Embedding(임베딩): 단어나 토큰 등의 정보를 모델이 계산할 수 있는 숫자 벡터 형태로 변환하는 과정입니다.
  • Vocabulary: LLM이 사용할 수 있도록 미리 정의된 전체 토큰의 집합입니다.

※ 특정 산업이나 자산에 대한 투자 판단은 본인의 책임 하에 신중히 결정하시기 바랍니다.

0%