<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Local AI on kenji.blog</title><link>http://kenji.blog/ko/tags/local-ai/</link><description>Recent content in Local AI on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ko</language><copyright>kenjinote</copyright><lastBuildDate>Fri, 11 Sep 2026 16:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/ko/tags/local-ai/index.xml" rel="self" type="application/rss+xml"/><item><title>온프레미스 환경에서 TinyLLaMA를 가장 빠르게 튜닝하는 방법</title><link>http://kenji.blog/ko/p/tinyllama-on-premises-fast-tuning-guide/</link><pubDate>Fri, 11 Sep 2026 16:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/tinyllama-on-premises-fast-tuning-guide/</guid><description>&lt;img src="http://kenji.blog/p/tinyllama-on-premises-fast-tuning-guide/img/eyecatch.jpg" alt="Featured image of post 온프레미스 환경에서 TinyLLaMA를 가장 빠르게 튜닝하는 방법" />&lt;h2 id="1-시작하며-왜-지금-tinyllama와-온프레미스인가">1. 시작하며: 왜 지금, TinyLLaMA와 온프레미스인가?
&lt;/h2>&lt;p>대규모 언어 모델(LLM)의 진화는 무서운 속도로 진행되고 있지만, 그에 따라 모델의 파라미터 수도 수천억 규모로 계속 팽창하고 있습니다. GPT-4나 Claude 3와 같은 초거대 모델은 비할 데 없는 성능을 자랑하는 반면, 추론이나 학습에 드는 계산 비용, 그리고 외부 API를 이용할 때의 보안 및 데이터 프라이버시 우려가 기업에게 큰 장애물이 되고 있습니다. 특히 기밀성이 높은 사내 데이터나 개인정보를 다루는 업무에서는 클라우드상의 퍼블릭 LLM API로 데이터를 전송하는 것이 컴플라이언스(GDPR이나 APPI 등) 관점에서 허용되지 않는 경우가 많습니다.&lt;/p>
&lt;p>그래서 각광받고 있는 것이 **소규모 언어 모델(SLM: Small Language Models)**과 &lt;strong>온프레미스 환경에서의 로컬 운영&lt;/strong>입니다. 그중에서도 &amp;lsquo;&lt;strong>TinyLLaMA&lt;/strong>&amp;lsquo;는 불과 1.1B(11억) 파라미터라는 콤팩트한 크기이면서도 약 3조 토큰이라는 방대한 데이터 세트로 사전 학습되어, 동급 모델과 비교해 경이로운 성능을 발휘합니다.&lt;/p>
&lt;p>본 기사에서는 이 TinyLLaMA를 온프레미스 환경(로컬 서버나 워크스테이션)에서 자사 전용 태스크를 위해 &amp;lsquo;가장 빠르고 고효율&amp;rsquo;로 파인튜닝(미세 조정)하기 위한 완전한 가이드를 제공합니다. 수학적 배경부터 최신 최적화 기술, 그리고 구체적인 PyTorch 구현 코드까지 망라하여 해설해 나갈 것입니다.&lt;/p>
&lt;hr>
&lt;h2 id="2-tinyllama의-아키텍처와-특징">2. TinyLLaMA의 아키텍처와 특징
&lt;/h2>&lt;p>TinyLLaMA는 Meta사가 개발한 LLaMA(Large Language Model Meta AI) 아키텍처를 따르고 있습니다. 파라미터 수를 1.1B로 억제하면서도 LLaMA 2와 같은 기술 스택을 이용하고 있어 생태계 호환성이 매우 높은 것이 특징입니다.&lt;/p>
&lt;h3 id="주요-아키텍처-컴포넌트">주요 아키텍처 컴포넌트
&lt;/h3>&lt;ol>
&lt;li>&lt;strong>RMSNorm (Root Mean Square Normalization):&lt;/strong>
기존의 LayerNorm 계산에서 평균 뺄셈을 생략하여 계산 효율을 향상시킨 정규화 기법입니다. 학습의 안정성을 유지하면서 처리량(throughput)을 향상시킵니다.&lt;/li>
&lt;li>&lt;strong>SwiGLU 활성화 함수:&lt;/strong>
피드 포워드 네트워크(FFN)에서 기존의 ReLU나 GELU 대신 SwiGLU를 채택했습니다. 이는 수학적으로 다음과 같이 표현됩니다.
$$ \text{SwiGLU}(x, W, V) = \text{Swish}(xW) \otimes (xV) $$
여기서 $\otimes$는 요소별 곱(아다마르 곱)을 나타내며, Swish 함수는 $\text{Swish}(z) = z \cdot \sigma(\beta z)$ 입니다. 이로 인해 표현력이 크게 향상됩니다.&lt;/li>
&lt;li>&lt;strong>RoPE (Rotary Position Embedding):&lt;/strong>
절대적 위치 인코딩과 상대적 위치 인코딩의 장점을 결합한 기법입니다. 시퀀스 길이가 확장되었을 때도 높은 일반화 성능을 가집니다.&lt;/li>
&lt;li>&lt;strong>Grouped Query Attention (GQA):&lt;/strong>
Multi-Head Attention (MHA)와 Multi-Query Attention (MQA)의 중간적인 접근 방식으로, 키와 밸류의 헤드를 그룹화함으로써 메모리 대역폭을 절약하고 추론 속도를 획기적으로 향상시킵니다.&lt;/li>
&lt;/ol>
&lt;p>다음 Mermaid 다이어그램은 TinyLLaMA의 전반적인 데이터 흐름과 Transformer 블록 구조를 보여줍니다.&lt;/p>
&lt;div class="mermaid">graph TD
A["입력 텍스트"] --> B["토크나이저 (BPE)"]
B --> C["임베딩 레이어"]
C --> D["트랜스포머 블록 (TinyLLaMA용 22개 레이어)"]
D --> E["RMSNorm (최종)"]
E --> F["선형 투영 (어휘 크기)"]
F --> G["출력 확률 (Softmax)"]
subgraph "트랜스포머 블록 구조"
D1["입력 은닉 상태"] --> D2["RMSNorm"]
D2 --> D3["그룹화된 쿼리 어텐션 (GQA)"]
D3 --> D4["잔차 연결 (Residual Add)"]
D4 --> D5["RMSNorm"]
D5 --> D6["SwiGLU FFN"]
D6 --> D7["잔차 연결 (Residual Add)"]
D7 --> D8["다음 레이어로 출력"]
D1 -.-> D4
D4 -.-> D7
end&lt;/div>
&lt;hr>
&lt;h2 id="3-파인튜닝의-돌파구-lora와-qlora">3. 파인튜닝의 돌파구: LoRA와 QLoRA
&lt;/h2>&lt;p>온프레미스 환경에서 전체 파라미터의 파인튜닝을 수행하려면 1.1B 모델이라 하더라도 옵티마이저의 상태나 그래디언트를 유지하기 위해 수십 GB의 VRAM(비디오 메모리)을 소비합니다. 제한된 리소스로 효율적인 학습을 수행하기 위해 필수적인 것이 &lt;strong>PEFT (Parameter-Efficient Fine-Tuning)&lt;/strong> 기법인 &amp;lsquo;&lt;strong>LoRA&lt;/strong>&amp;lsquo;와 그 양자화 확장인 &amp;lsquo;&lt;strong>QLoRA&lt;/strong>&amp;lsquo;입니다.&lt;/p>
&lt;h3 id="31-lora-low-rank-adaptation의-수학적-배경">3.1 LoRA (Low-Rank Adaptation)의 수학적 배경
&lt;/h3>&lt;p>LoRA는 사전 학습된 가중치 행렬을 고정(프리즈)하고, 그 가중치의 업데이트 양($\Delta W$)을 낮은 랭크를 가진 두 개의 작은 행렬의 곱으로 근사하는 기법입니다.&lt;/p>
&lt;p>사전 학습된 가중치를 $W_0 \in \mathbb{R}^{d \times k}$ 라고 합시다. 풀 파인튜닝에서는 $W_0$ 자체를 업데이트하여 $W_0 + \Delta W$ 로 만들지만, LoRA에서는 업데이트 행렬 $\Delta W$ 를 다음과 같이 분해합니다.&lt;/p>
$$ \Delta W = B \times A $$
&lt;p>여기서 $B \in \mathbb{R}^{d \times r}$, $A \in \mathbb{R}^{r \times k}$ 이며, $r$은 랭크(Rank)라고 불리는 하이퍼파라미터로, $r \ll \min(d, k)$ 를 만족하는 매우 작은 값(보통 8, 16, 32 등)입니다.&lt;/p>
&lt;p>포워드 패스의 계산은 다음과 같습니다.&lt;/p>
$$ h = W_0 x + \Delta W x = W_0 x + B A x $$
&lt;p>초기 상태에서 행렬 $A$는 정규 분포(가우스 분포)로 무작위 초기화되고, 행렬 $B$는 영행렬로 초기화됩니다. 이에 따라 학습 시작 시의 $\Delta W$는 0이 되어, 베이스 모델의 출력을 완전히 유지한 상태에서 학습을 시작할 수 있습니다.&lt;/p>
&lt;div class="mermaid">graph LR
X["입력 벡터 x"] --> W0["고정된 사전 학습 가중치 (W_0)"]
X --> A["학습 가능한 LoRA 행렬 A (r x k)"]
A --> B["학습 가능한 LoRA 행렬 B (d x r)"]
W0 --> Add["벡터 덧셈"]
B --> Add
Add --> Y["출력 벡터 h"]&lt;/div>
&lt;h3 id="32-qlora-quantized-lora의-혁신성">3.2 QLoRA (Quantized LoRA)의 혁신성
&lt;/h3>&lt;p>QLoRA는 LoRA의 접근 방식을 더욱 발전시켜 베이스 모델 $W_0$ 를 4-bit 정밀도(NormalFloat 4, NF4)로 양자화하여 메모리에 로드하는 기법입니다. 이를 통해 VRAM 소비량을 획기적으로 줄입니다.&lt;/p>
&lt;p>QLoRA에는 3가지 중요한 기술이 포함되어 있습니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>4-bit NormalFloat (NF4) 양자화:&lt;/strong> 정규 분포를 따르는 가중치에 최적화된 이론적으로 가장 이상적인 데이터 타입.&lt;/li>
&lt;li>&lt;strong>Double Quantization (이중 양자화):&lt;/strong> 양자화 상수(스케일 팩터) 자체도 양자화하여 메모리를 추가로 절약.&lt;/li>
&lt;li>&lt;strong>Paged Optimizers:&lt;/strong> NVIDIA의 통합 메모리 기능을 활용하여 VRAM이 부족할 때 옵티마이저의 상태를 CPU의 RAM으로 일시적으로 대피시키는 메커니즘.&lt;/li>
&lt;/ol>
&lt;p>이에 따라 보통 16GB~24GB의 VRAM이 필요한 튜닝이 소비자용 GPU(RTX 3060 12GB나 RTX 4070 등)에서도 여유롭게 실행 가능해집니다.&lt;/p>
&lt;hr>
&lt;h2 id="4-온프레미스-환경에서의-하드웨어-요구사항과-셋업">4. 온프레미스 환경에서의 하드웨어 요구사항과 셋업
&lt;/h2>&lt;p>TinyLLaMA (1.1B)를 QLoRA로 튜닝할 경우 하드웨어 요구사항을 매우 낮출 수 있습니다.&lt;/p>
&lt;h3 id="권장-하드웨어-사양">권장 하드웨어 사양
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>GPU:&lt;/strong> NVIDIA RTX 3060 (12GB), RTX 3090/4090 (24GB), 또는 NVIDIA A10G/A100 등. VRAM은 최소 8GB만 있어도 동작하지만, 배치 크기를 확보하기 위해서는 12GB 이상을 권장합니다.&lt;/li>
&lt;li>&lt;strong>CPU:&lt;/strong> 8코어 이상의 모던 CPU (Intel Core i7/i9, AMD Ryzen 7/9)&lt;/li>
&lt;li>&lt;strong>RAM:&lt;/strong> 32GB 이상 (Paged Optimizers를 이용할 경우 VRAM에서의 대피소로서 중요)&lt;/li>
&lt;li>&lt;strong>스토리지:&lt;/strong> NVMe SSD (데이터 세트 로딩이나 모델 저장을 가속하기 위함)&lt;/li>
&lt;/ul>
&lt;h3 id="소프트웨어-환경-구축">소프트웨어 환경 구축
&lt;/h3>&lt;p>Ubuntu 22.04 LTS 환경을 가정한 셋업 절차입니다. Python 3.10 이상을 사용합니다.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 가상 환경 생성 및 활성화&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">python3 -m venv tinyllama_env
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">source&lt;/span> tinyllama_env/bin/activate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># PyTorch 설치 (CUDA 12.1용)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 트랜스포머 관련 라이브러리 설치&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install transformers datasets peft trl accelerate bitsandbytes
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr>
&lt;h2 id="5-가장-빠른-튜닝을-위한-최적화-기술">5. 가장 빠른 튜닝을 위한 최적화 기술
&lt;/h2>&lt;p>단순히 스크립트를 돌리는 것뿐만 아니라, &amp;lsquo;가장 빠르게&amp;rsquo; 튜닝을 완료하기 위해서는 다음과 같은 최적화 기법을 조합해야 합니다.&lt;/p>
&lt;h3 id="51-flash-attention-2">5.1 Flash Attention 2
&lt;/h3>&lt;p>표준적인 어텐션(Attention) 메커니즘은 시퀀스 길이 $N$ 에 대해 시간·공간 계산량이 $O(N^2)$ 가 됩니다. Flash Attention 2는 GPU의 SRAM과 HBM(High Bandwidth Memory) 사이의 메모리 접근을 최적화함으로써 계산량을 줄이지 않고도 I/O 병목을 해소하여 학습 속도를 수 배 끌어올리고 메모리 소비를 급감시킵니다.&lt;/p>
&lt;h3 id="52-gradient-checkpointing-그래디언트-체크포인트">5.2 Gradient Checkpointing (그래디언트 체크포인트)
&lt;/h3>&lt;p>포워드 패스에서 계산된 중간 활성화를 모두 VRAM에 저장하는 것이 아니라 일부만 저장하고, 백워드 패스에서 필요해졌을 때 다시 계산하는 기법입니다. 계산 시간은 약 20% 증가하지만 메모리 소비량을 획기적으로 줄일 수 있기 때문에, 결과적으로 더 큰 배치 크기를 설정할 수 있어 전체적인 처리량이 향상됩니다.&lt;/p>
&lt;h3 id="53-mixed-precision-training-혼합-정밀도-학습과-bfloat16">5.3 Mixed Precision Training (혼합 정밀도 학습)과 Bfloat16
&lt;/h3>&lt;p>GPU의 텐서 코어(Tensor Core)를 최대한 활용하기 위해 학습 시의 계산을 &lt;code>bfloat16&lt;/code> (Brain Floating Point)으로 수행합니다. &lt;code>float16&lt;/code> 과 비교하여 지수부의 비트 길이가 &lt;code>float32&lt;/code> 와 동일하기 때문에 오버플로·언더플로의 위험이 극히 낮아 학습이 안정됩니다.&lt;/p>
&lt;hr>
&lt;h2 id="6-실전-tinyllama의-qlora-파인튜닝-코드">6. 실전: TinyLLaMA의 QLoRA 파인튜닝 코드
&lt;/h2>&lt;p>그러면 위에서 언급한 모든 최적화를 포함한 가장 빠른 튜닝용 PyTorch 스크립트를 해설하겠습니다. 여기서는 Hugging Face의 &lt;code>trl&lt;/code> (Transformer Reinforcement Learning) 라이브러리의 &lt;code>SFTTrainer&lt;/code> 를 이용합니다.&lt;/p>
&lt;h3 id="61-데이터-세트-준비-및-모델-로드">6.1 데이터 세트 준비 및 모델 로드
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">torch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">datasets&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">load_dataset&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">transformers&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">AutoModelForCausalLM&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">AutoTokenizer&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">BitsAndBytesConfig&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">TrainingArguments&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">peft&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">LoraConfig&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">get_peft_model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">prepare_model_for_kbit_training&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">trl&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">SFTTrainer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. 모델과 토크나이저 지정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;TinyLlama/TinyLlama-1.1B-Chat-v1.0&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. QLoRA용 4-bit 양자화 설정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">bnb_config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">BitsAndBytesConfig&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">load_in_4bit&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bnb_4bit_use_double_quant&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bnb_4bit_quant_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;nf4&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bnb_4bit_compute_dtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">bfloat16&lt;/span> &lt;span class="c1"># 계산은 bfloat16으로 수행&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. 모델 로드 (Flash Attention 2 활성화)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Loading model...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">AutoModelForCausalLM&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_pretrained&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model_id&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">quantization_config&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">bnb_config&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">device_map&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;auto&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">use_flash_attention_2&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span> &lt;span class="c1"># 속도 극대화의 핵심&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 4. 토크나이저 로드&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">tokenizer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">AutoTokenizer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_pretrained&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model_id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">trust_remote_code&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">tokenizer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pad_token&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">tokenizer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">eos_token&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">tokenizer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">padding_side&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;right&amp;#34;&lt;/span> &lt;span class="c1"># fp16/bf16 트레이닝 시의 버그 회피를 위해 right로 설정&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="62-lora-어댑터-적용-및-데이터-세트-성형">6.2 LoRA 어댑터 적용 및 데이터 세트 성형
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 5. k-bit 학습 준비 및 그래디언트 체크포인트 활성화&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gradient_checkpointing_enable&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">prepare_model_for_kbit_training&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 6. LoRA 설정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">peft_config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">LoraConfig&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">r&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">16&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 랭크&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">lora_alpha&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">32&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 스케일링 팩터&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">lora_dropout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.05&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bias&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;none&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">task_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;CAUSAL_LM&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">target_modules&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;q_proj&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;k_proj&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;v_proj&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;o_proj&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;gate_proj&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;up_proj&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;down_proj&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="c1"># 모든 Linear 레이어를 타겟으로 하면 성능이 향상됨&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_peft_model&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">peft_config&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">print_trainable_parameters&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 출력 예시: trainable params: 14,286,848 || all params: 1,114,335,232 || trainable%: 1.282%&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 7. 데이터 세트 로드 (여기서는 예시로 일본어 Instruction 데이터 세트를 사용)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 실제로는 온프레미스의 프라이빗 JSONL 파일 등을 로드합니다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">dataset&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_dataset&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;kunishou/databricks-dolly-15k-ja&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">split&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;train&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">format_instruction&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sample&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> ChatML 포맷이나 프롬프트 템플릿에 맞추어 문자열을 성형합니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;lt;|im_start|&amp;gt;user&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sample&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;instruction&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">sample&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;input&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">prompt&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sample&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;input&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">prompt&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;lt;|im_end|&amp;gt;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;lt;|im_start|&amp;gt;assistant&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sample&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;output&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;lt;|im_end|&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">prompt&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">dataset&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">dataset&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">format_instruction&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="63-트레이닝-실행">6.3 트레이닝 실행
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 8. 트레이닝 인자 설정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">training_args&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TrainingArguments&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">output_dir&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;./tinyllama-lora-output&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">per_device_train_batch_size&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">8&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># VRAM에 여유가 있다면 올림&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">gradient_accumulation_steps&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 실질적인 배치 크기 = 8 * 2 = 16&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">optim&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;paged_adamw_32bit&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># Paged Optimizer에 의한 VRAM 절약&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">save_steps&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">logging_steps&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">learning_rate&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">2e-4&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">fp16&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bf16&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 혼합 정밀도 학습 (bfloat16)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">max_grad_norm&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.3&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">max_steps&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">500&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 테스트용으로 500 스텝. 실전에서는 에포크 수로 지정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">warmup_ratio&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.03&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">group_by_length&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">lr_scheduler_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;cosine&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 9. SFTTrainer를 이용한 학습 시작&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">trainer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">SFTTrainer&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">train_dataset&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">dataset&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">peft_config&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">peft_config&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dataset_text_field&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">max_seq_length&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1024&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 예상되는 입력 길이에 맞춰 조정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tokenizer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">tokenizer&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">args&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">training_args&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Starting training...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">trainer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">train&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 10. LoRA 어댑터 저장&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">trainer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">save_pretrained&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./tinyllama-lora-final&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">tokenizer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">save_pretrained&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./tinyllama-lora-final&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Training complete and model saved.&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr>
&lt;h2 id="7-성능-평가-및-트러블슈팅">7. 성능 평가 및 트러블슈팅
&lt;/h2>&lt;p>온프레미스 환경에서 학습을 돌릴 때 자주 직면하는 문제와 그 해결책입니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>OOM(Out Of Memory)이 발생한다:&lt;/strong>
&lt;ul>
&lt;li>&lt;code>per_device_train_batch_size&lt;/code> 를 &lt;code>1&lt;/code> 로 낮춥니다.&lt;/li>
&lt;li>&lt;code>gradient_accumulation_steps&lt;/code> 를 늘려 실질적인 배치 크기를 유지합니다.&lt;/li>
&lt;li>&lt;code>max_seq_length&lt;/code> 를 &lt;code>2048&lt;/code> 에서 &lt;code>1024&lt;/code> 나 &lt;code>512&lt;/code> 로 줄입니다.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Loss가 떨어지지 않는다·발산한다:&lt;/strong>
&lt;ul>
&lt;li>학습률 (&lt;code>learning_rate&lt;/code>)이 너무 클 가능성이 있습니다. &lt;code>2e-4&lt;/code> 에서 &lt;code>5e-5&lt;/code> 정도로 낮춰보세요.&lt;/li>
&lt;li>Bfloat16이 아닌 Float16을 사용하고 있을 경우 그래디언트의 언더플로가 일어나고 있을 가능성이 있습니다. &lt;code>bf16=True&lt;/code> 를 확인하세요.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>추론 시 알 수 없는 문자열이 생성된다:&lt;/strong>
&lt;ul>
&lt;li>&lt;code>padding_side=&amp;quot;right&amp;quot;&lt;/code> 가 올바르게 설정되어 있는지 확인하세요. 또한 데이터 세트의 포맷(&lt;code>&amp;lt;|im_start|&amp;gt;&lt;/code> 등의 특수 토큰)이 베이스 모델의 사전 학습 때와 일치하는지 확인이 필요합니다.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="8-튜닝-후-모델-배포-deployment">8. 튜닝 후 모델 배포 (Deployment)
&lt;/h2>&lt;p>튜닝이 완료되면 저장되는 것은 &amp;lsquo;베이스 모델 전체&amp;rsquo;가 아니라 수 MB~수십 MB의 &amp;lsquo;&lt;strong>LoRA 어댑터 (차이점 가중치)&lt;/strong>&amp;lsquo;뿐입니다. 추론을 고속으로 수행하기 위해서는 이 LoRA 가중치를 원래 베이스 모델에 병합(통합)하여 단일 모델로 저장해야 합니다.&lt;/p>
&lt;h3 id="모델-병합-스크립트">모델 병합 스크립트
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">torch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">peft&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">AutoPeftModelForCausalLM&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">transformers&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">AutoTokenizer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">output_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;./tinyllama-lora-final&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># FP16/BF16으로 모델과 어댑터 로드&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">AutoPeftModelForCausalLM&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_pretrained&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">output_dir&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">device_map&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;auto&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">torch_dtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">bfloat16&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">tokenizer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">AutoTokenizer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_pretrained&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">output_dir&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 가중치를 병합하여 저장&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">merged_model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">merge_and_unload&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">merged_model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">save_pretrained&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./tinyllama-merged&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">safe_serialization&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">tokenizer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">save_pretrained&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./tinyllama-merged&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Model merged and saved successfully!&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="vllm을-활용한-초고속-추론-서버-구축">vLLM을 활용한 초고속 추론 서버 구축
&lt;/h3>&lt;p>온프레미스 환경에서의 배포에 있어 추론 속도(Tokens per second)를 극대화하기 위해서는 Hugging Face의 표준 &lt;code>pipeline&lt;/code> 이 아니라 &lt;strong>vLLM&lt;/strong>이나 &lt;strong>TGI (Text Generation Inference)&lt;/strong> 의 사용을 강력히 권장합니다. vLLM은 PagedAttention 기술을 사용하여 GPU 메모리의 단편화를 방지하고 병렬 요청 처리 능력을 극적으로 향상시킵니다.&lt;/p>
&lt;p>다음 Mermaid 다이어그램은 학습부터 추론 서버 배포까지의 파이프라인을 보여줍니다.&lt;/p>
&lt;div class="mermaid">graph TD
A["원시 프라이빗 데이터"] --> B["전처리 및 포맷팅 (JSONL)"]
B --> C["QLoRA 파인튜닝 (SFTTrainer)"]
C --> D["LoRA 어댑터 가중치 (.safetensors)"]
D --> E["베이스 TinyLLaMA 1.1B와 병합"]
E --> F["병합된 모델"]
F --> G["vLLM 서버를 통해 배포"]
G --> H["API 엔드포인트 / UI (예: 챗봇)"]&lt;/div>
&lt;p>vLLM을 사용한 API 서버 실행은 다음 명령어 하나로 완료됩니다.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">python -m vllm.entrypoints.openai.api_server &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --model ./tinyllama-merged &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --host 0.0.0.0 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --port &lt;span class="m">8000&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --max-model-len &lt;span class="m">2048&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --dtype bfloat16
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>이것으로 OpenAI API 호환 엔드포인트가 온프레미스 환경에 구축되어, 안전하고 빠르게 로컬 AI를 활용할 수 있게 됩니다.&lt;/p>
&lt;hr>
&lt;h2 id="9-마무리">9. 마무리
&lt;/h2>&lt;p>본 기사에서는 1.1B라는 가벼운 파라미터 수임에도 불구하고 고성능인 &amp;lsquo;TinyLLaMA&amp;rsquo;를 대상으로 온프레미스 환경에서 가장 빠르고 메모리 효율적으로 파인튜닝을 수행하는 기법을 해설했습니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>LoRA / QLoRA&lt;/strong> 를 통해 소비자용 GPU에서도 본격적인 LLM 튜닝이 가능해졌습니다.&lt;/li>
&lt;li>&lt;strong>Flash Attention 2&lt;/strong> 와 &lt;strong>Gradient Checkpointing&lt;/strong> 을 구사하여 학습 시간과 VRAM 소비를 극한까지 최적화했습니다.&lt;/li>
&lt;li>&lt;strong>vLLM&lt;/strong> 을 활용한 배포로 프로덕션 환경에서도 높은 처리량을 실현했습니다.&lt;/li>
&lt;/ul>
&lt;p>온프레미스에서의 로컬 LLM 운영은 데이터의 기밀성을 보호할 뿐만 아니라, 특정 도메인(법무, 의료, 사내 규정 등)에 특화된 전문 AI를 저비용으로 구축하기 위한 최강의 무기가 됩니다. 꼭 본 가이드를 참고하여 자사 전용의 TinyLLaMA를 육성해 보시기 바랍니다.&lt;/p></description></item><item><title>C++로 시작하는 소규모 AI 모델(TinyLLaMA 등) 개발 절차</title><link>http://kenji.blog/ko/p/cpp-small-ai-model-tinyllama-dev-guide/</link><pubDate>Fri, 11 Sep 2026 14:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/cpp-small-ai-model-tinyllama-dev-guide/</guid><description>&lt;img src="http://kenji.blog/p/cpp-small-ai-model-tinyllama-dev-guide/img/eyecatch.jpg" alt="Featured image of post C++로 시작하는 소규모 AI 모델(TinyLLaMA 등) 개발 절차" />&lt;h1 id="c로-시작하는-소규모-ai-모델tinyllama-등-개발-절차">C++로 시작하는 소규모 AI 모델(TinyLLaMA 등) 개발 절차
&lt;/h1>&lt;p>최근 대규모 언어 모델(LLM)을 로컬 환경에서 실행하는 것에 대한 관심이 급속히 높아지고 있습니다. 특히 TinyLLaMA(1.1B 파라미터)와 같은 소규모 모델은 제한된 리소스의 엣지 디바이스나 일반적인 노트북(Windows 환경 포함)에서도 실용적인 속도로 추론이 가능합니다. Python과 PyTorch를 이용한 개발이 주류인 반면, 궁극의 퍼포먼스와 메모리 절약을 추구할 경우 C++와 C언어 기반의 텐서 라이브러리인 &amp;lsquo;ggml&amp;rsquo;의 조합이 사실상의 표준이 되고 있습니다.&lt;/p>
&lt;p>본 기사에서는 C++를 사용하여 TinyLLaMA를 로드하고, 텍스트 생성을 수행하기 위한 추론 엔진을 제로부터 구축(혹은 기존 llama.cpp의 내부 구조를 깊이 이해)하기 위한 매우 상세한 개발 절차를 해설합니다.&lt;/p>
&lt;hr>
&lt;h2 id="1-왜-c와-ggml인가">1. 왜 C++와 ggml인가?
&lt;/h2>&lt;p>AI 학습 단계에서는 유연성과 풍부한 생태계를 가진 Python이 압도적으로 유리합니다. 하지만 배포나 &amp;lsquo;추론(Inference)&amp;rsquo; 단계에서는 다음과 같은 이유로 C++가 강력한 선택지가 됩니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>오버헤드 감소&lt;/strong>: Python의 글로벌 인터프리터 락(GIL)이나 런타임 오버헤드를 완전히 배제할 수 있습니다.&lt;/li>
&lt;li>&lt;strong>메모리 효율과 아레나 할당&lt;/strong>: 메모리 확보 및 해제를 수동으로 제어할 수 있으므로 가비지 컬렉션으로 인한 예측 불가능한 스파이크를 방지할 수 있습니다.&lt;/li>
&lt;li>&lt;strong>하드웨어 직접 접근&lt;/strong>: AVX-512, AVX2, ARM NEON 등 SIMD 내장 함수(Intrinsics)를 직접 호출하여 CPU의 연산 능력을 극한까지 끌어올릴 수 있습니다.&lt;/li>
&lt;li>&lt;strong>의존성 배제&lt;/strong>: ggml은 의존성이 전혀 없는(Zero dependencies) C/C++ 라이브러리이며 컴파일러만 있다면 Windows의 MSVC 환경에서도 쉽게 빌드할 수 있습니다.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="2-아키텍처의-전체-구조">2. 아키텍처의 전체 구조
&lt;/h2>&lt;p>추론 파이프라인 전체의 흐름을 아래 Mermaid 다이어그램에 나타냅니다. 사용자의 입력 텍스트부터 시작하여 최종적으로 다음 토큰이 생성될 때까지의 일련의 과정입니다.&lt;/p>
&lt;div class="mermaid">graph TD
A["사용자 입력 텍스트"] --> B["BPE 토크나이저"]
B --> C["토큰 ID 배열"]
C --> D["임베딩 레이어 조회"]
D --> E["트랜스포머 블록"]
E --> F["RMSNorm"]
F --> G["LM 헤드 레이어"]
G --> H["로짓 배열"]
H --> I["샘플러 모듈"]
I --> J["다음 토큰 ID"]
J --> K["디토크나이저"]
K --> L["출력 텍스트 청크"]
J -.-> |"컨텍스트에 추가"| C&lt;/div>
&lt;p>자기 회귀 모델이므로 출력된 토큰은 다시 컨텍스트에 추가되어 다음 토큰 예측을 위한 입력으로 순환합니다(그림의 점선 부분).&lt;/p>
&lt;hr>
&lt;h2 id="3-모델-포맷과-메모리-매핑-mmap">3. 모델 포맷과 메모리 매핑 (mmap)
&lt;/h2>&lt;p>거대한 신경망의 가중치를 다루는 데 있어 가장 큰 장벽은 디스크 I/O와 메모리 소비입니다. C++ 구현에서는 이를 **메모리 매핑(mmap)**으로 해결합니다.&lt;/p>
&lt;h3 id="31-메모리-매핑의-원리와-windows에서의-구현">3.1 메모리 매핑의 원리와 Windows에서의 구현
&lt;/h3>&lt;p>mmap을 사용하면 파일 내용을 프로세스의 가상 메모리 공간에 직접 매핑할 수 있습니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>제로 카피(Zero-copy)&lt;/strong>: 데이터는 디스크에서 커널의 페이지 캐시로 직접 로드되며 사용자 공간으로의 불필요한 복사가 발생하지 않습니다.&lt;/li>
&lt;li>&lt;strong>온디맨드 로드(Page Fault)&lt;/strong>: 실제로 CPU가 해당 메모리 주소에 접근하는 순간 페이지 폴트가 발생하며, 필요한 청크(일반적으로 4KB)만 물리 메모리에 로드됩니다.&lt;/li>
&lt;/ul>
&lt;p>Windows 환경에서는 POSIX의 &lt;code>mmap&lt;/code> 대신 Win32 API의 &lt;code>CreateFileMapping&lt;/code>과 &lt;code>MapViewOfFile&lt;/code>을 사용합니다.&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant OS["Windows OS"]
participant RAM["물리 메모리"]
participant App["C++ 애플리케이션"]
App->>OS: "CreateFileMapping / MapViewOfFile"
OS-->>App: "가상 메모리 주소 포인터"
App->>App: "포인터에서 텐서 데이터 읽기"
OS->>RAM: "페이지 폴트 / 디스크에서 페이지 로드"
RAM-->>App: "SIMD 연산용 데이터 준비 완료"&lt;/div>
&lt;h3 id="32-gguf-포맷의-바이너리-구조">3.2 GGUF 포맷의 바이너리 구조
&lt;/h3>&lt;p>Hugging Face 등의 &lt;code>.safetensors&lt;/code> 포맷에서 변환된 **GGUF (GPT-Generated Unified Format)**는 추론을 위한 궁극적인 포맷입니다. 다음과 같은 엄격한 바이너리 레이아웃을 갖습니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Magic Bytes&lt;/strong>: &lt;code>0x46554747&lt;/code> (GGUF).&lt;/li>
&lt;li>&lt;strong>Version&lt;/strong>: 포맷의 버전 번호.&lt;/li>
&lt;li>&lt;strong>Tensor Count &amp;amp; Metadata Count&lt;/strong>: 텐서 개수와 메타데이터의 키-값 쌍 개수.&lt;/li>
&lt;li>&lt;strong>Metadata (Key-Value Pairs)&lt;/strong>: 문자열 길이 접두사가 붙은 키와 타입이 지정된 값.&lt;/li>
&lt;li>&lt;strong>Tensor Info&lt;/strong>: 각 텐서의 이름, 차원 수, 데이터 타입(FP16, Q4_K 등), 파일 내의 오프셋 위치.&lt;/li>
&lt;li>&lt;strong>Padding&lt;/strong>: 텐서 데이터가 특정 경계(일반적으로 32바이트 또는 64바이트)에 정렬되도록 삽입되는 패딩. SIMD 명령(특히 AVX)에서의 빠른 메모리 접근에 필수적입니다.&lt;/li>
&lt;li>&lt;strong>Tensor Data&lt;/strong>: 정렬된 실제 가중치 데이터 배열.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="4-tinyllama의-수학적-기반과-c-알고리즘">4. TinyLLaMA의 수학적 기반과 C++ 알고리즘
&lt;/h2>&lt;p>TinyLLaMA는 효율화를 위해 몇 가지 고도화된 아키텍처적 개선을 도입했습니다. 이를 C++로 올바르게 구현하기 위한 수식 표현을 해설합니다.&lt;/p>
&lt;h3 id="41-rmsnorm-root-mean-square-normalization">4.1 RMSNorm (Root Mean Square Normalization)
&lt;/h3>&lt;p>LayerNorm에서 평균 중심화(centering)를 생략하고 분산 스케일링만 수행함으로써 계산 비용을 절감합니다.&lt;/p>
$$ \text{RMSNorm}(x) = \frac{x}{\sqrt{\frac{1}{d}\sum_{i=1}^{d} x_i^2 + \epsilon}} \odot \gamma $$
&lt;p>$d$는 차원 수, $\gamma$는 학습된 스케일링 텐서입니다.
C++로 구현할 경우 먼저 배열의 제곱합을 AVX2의 &lt;code>_mm256_fmadd_ps&lt;/code> 등으로 빠르게 계산하고 역제곱근(&lt;code>_mm256_rsqrt_ps&lt;/code> 명령 등)을 곱하여 최적화합니다.&lt;/p>
&lt;h3 id="42-rope-rotary-position-embedding">4.2 RoPE (Rotary Position Embedding)
&lt;/h3>&lt;p>토큰의 위치 정보를 텐서 공간에서의 회전(Rotate)으로 적용하는 기술입니다. 복소 평면 위에서의 회전으로 간주할 수 있으며, 벡터 $x$의 인접한 차원 쌍 $(x_1, x_2)$에 대해 다음과 같은 회전을 적용합니다.&lt;/p>
$$ \text{RoPE}(x, m) = \begin{pmatrix} x_{1} \cos(m\theta) - x_{2} \sin(m\theta) \\ x_{1} \sin(m\theta) + x_{2} \cos(m\theta) \end{pmatrix} $$
&lt;p>여기서 $m$은 토큰의 절대적인 위치 인덱스, $\theta$는 사전 계산된 기본 주파수입니다. ggml에서는 추론 그래프 구축 중에 &lt;code>ggml_rope&lt;/code> 연산자를 추가하기만 하면 병렬로 실행됩니다.&lt;/p>
&lt;h3 id="43-grouped-query-attention-gqa">4.3 Grouped-Query Attention (GQA)
&lt;/h3>&lt;p>일반적인 Multi-Head Attention(MHA)에서는 Query, Key, Value 각각에 대해 동일한 수의 헤드를 갖습니다. 그러나 TinyLLaMA는 메모리 대역폭과 KV 캐시 소비량을 극적으로 줄이기 위해 **Grouped-Query Attention(GQA)**을 채택했습니다.&lt;/p>
$$ \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{Q K^T}{\sqrt{d_k}}\right) V $$
&lt;p>GQA에서는 여러 Query 헤드가 하나의 Key/Value 헤드를 공유합니다. C++ 구현에서는 행렬 곱 &lt;code>ggml_mul_mat&lt;/code>를 실행하기 전에 KV 텐서를 Query의 수에 맞게 브로드캐스트하는 작업이 필요합니다.&lt;/p>
&lt;h3 id="44-swiglu-활성화-함수">4.4 SwiGLU 활성화 함수
&lt;/h3>&lt;p>Feed-Forward Network (FFN) 계층에서는 GELU 대신 SwiGLU가 사용됩니다.&lt;/p>
$$ \text{SwiGLU}(x) = \text{Swish}(x W_{\text{gate}}) \otimes (x W_{\text{up}}) $$
$$ \text{Swish}(z) = z \cdot \sigma(z) = z \cdot \frac{1}{1 + e^{-z}} $$
&lt;p>계산 그래프에서는 &lt;code>ggml_silu&lt;/code> 연산자와 &lt;code>ggml_mul&lt;/code>을 조합하여 표현합니다.&lt;/p>
&lt;hr>
&lt;h2 id="5-ggml을-통한-계산-그래프-구축-및-메모리-관리">5. ggml을 통한 계산 그래프 구축 및 메모리 관리
&lt;/h2>&lt;p>ggml은 추론을 위한 정적인 계산 그래프를 구축하고 이를 나중에 평가(evaluate)하는 &amp;lsquo;Define-and-Run&amp;rsquo; 방식을 취합니다.&lt;/p>
&lt;h3 id="51-ggml_context와-아레나-할당자">5.1 ggml_context와 아레나 할당자
&lt;/h3>&lt;p>ggml의 가장 독특한 점은 추론 루프 내에서 동적인 메모리 할당(&lt;code>malloc&lt;/code>이나 &lt;code>new&lt;/code>)을 일절 수행하지 않는 &amp;lsquo;아레나 할당&amp;rsquo;입니다.
초기화 시 거대한 연속된 메모리 영역(아레나)을 확보하고, &lt;code>ggml_new_tensor&lt;/code> 등을 호출할 때마다 이 영역의 포인터가 증가합니다. 추론의 1단계가 완료되면 할당 포인터를 초기 위치로 재설정하기만 하면 다음 추론 단계를 위한 메모리 확보가 즉시 완료됩니다.&lt;/p>
&lt;h3 id="52-그래프-구축의-구체적인-예">5.2 그래프 구축의 구체적인 예
&lt;/h3>&lt;p>추론 단계마다 다음과 같은 계산 그래프를 메모리상에 조립합니다.&lt;/p>
&lt;div class="mermaid">graph TD
A["토큰 입력 ID"] --> B["임베딩 조회"]
B --> C["ggml_rms_norm"]
C --> D["Q / K / V 프로젝션"]
D --> E["ggml_rope 위치 인코딩"]
E --> F["KV 캐시 저장"]
E --> G["KV 캐시 로드"]
G --> H["셀프 어텐션"]
H --> I["스케일 &amp; 소프트맥스"]
I --> J["어텐션 출력"]
J --> K["출력 프로젝션"]
K --> L["잔차 연결 추가"]&lt;/div>
&lt;hr>
&lt;h2 id="6-양자화-quantization와-windows--simd-최적화">6. 양자화 (Quantization)와 Windows / SIMD 최적화
&lt;/h2>&lt;p>TinyLLaMA (1.1B)를 FP16으로 다루면 약 2.2GB의 메모리가 필요하지만, 4비트 양자화(Q4_K 등)를 통해 약 600MB 정도까지 극적으로 압축할 수 있습니다.&lt;/p>
&lt;h3 id="61-블록-양자화-아키텍처">6.1 블록 양자화 아키텍처
&lt;/h3>&lt;p>ggml은 텐서 전체를 일률적으로 양자화하는 것이 아니라 &amp;lsquo;블록&amp;rsquo; 단위로 수행합니다.
&lt;code>Q4_0&lt;/code> 포맷에서는 32개의 FP16 값을 1개의 블록으로 묶습니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>스케일 팩터&lt;/strong>: 1개의 FP16 값 (2바이트)&lt;/li>
&lt;li>&lt;strong>양자화 데이터&lt;/strong>: 32개의 4비트 값 (16바이트)
이를 통해 국소적인 이상치의 영향을 최소화합니다.&lt;/li>
&lt;/ul>
&lt;h3 id="62-avx2를-통한-내적-연산-가속">6.2 AVX2를 통한 내적 연산 가속
&lt;/h3>&lt;p>Windows 환경의 최신 x86 CPU를 대상으로 빌드할 경우 &lt;code>/arch:AVX2&lt;/code> 등의 컴파일러 플래그를 활용하여 다음과 같은 흐름으로 SIMD 처리가 이루어집니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>로드&lt;/strong>: 256비트 AVX 레지스터에 메모리로부터 4비트 양자화 데이터를 로드합니다.&lt;/li>
&lt;li>&lt;strong>전개 및 언팩&lt;/strong>: 비트 마스크와 시프트 연산으로 4비트 값을 Int8 또는 Int16으로 전개합니다.&lt;/li>
&lt;li>&lt;strong>역양자화&lt;/strong>: 스케일 팩터를 곱하여 부동소수점으로 변환합니다.&lt;/li>
&lt;li>&lt;strong>FMA 연산&lt;/strong>: 활성화 값과 &lt;code>_mm256_fmadd_ps&lt;/code>(Fused Multiply-Add)를 사용하여 곱셈-덧셈(積和) 연산을 병렬로 실행합니다.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="7-kv-캐시의-구현-세부-사항">7. KV 캐시의 구현 세부 사항
&lt;/h2>&lt;p>자기 회귀적 생성에 있어 과거 토큰의 Key와 Value 계산을 생략하기 위한 &amp;lsquo;KV 캐시&amp;rsquo;는 필수 기능입니다.&lt;/p>
&lt;p>C++로 구현할 때의 핵심은 다음과 같습니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>텐서 사전 확보&lt;/strong>: 최대 컨텍스트 길이(예: 2048 토큰)만큼의 거대한 텐서를 KV 캐시용으로 초기화합니다(FP16 권장).&lt;/li>
&lt;li>&lt;strong>오프셋 복사&lt;/strong>: 토큰 위치 $N$에 대한 계산이 수행되면 해당 단계에서 얻은 K와 V 벡터를 KV 캐시 텐서의 $N$번째 행에 &lt;code>ggml_cpy&lt;/code> 등을 사용하여 저장(store)합니다.&lt;/li>
&lt;li>&lt;strong>어텐션 시의 뷰 생성&lt;/strong>: 어텐션을 계산할 때는 0부터 $N$번째 토큰 부분까지만 가리키는 &amp;lsquo;뷰(view)&amp;lsquo;를 생성하여 행렬 곱에 전달합니다.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="8-bpe-토크나이저와-디코딩">8. BPE 토크나이저와 디코딩
&lt;/h2>&lt;p>입력 문자열을 UTF-8 바이트 열로 취급하여 사전에 정의된 어휘 사전(Vocabulary)과 대조합니다. C++에서는 어휘 사전 검색을 가속화하기 위해 **트라이 트리(Trie tree)**나 우선순위 큐를 사용한 알고리즘을 구현합니다.&lt;/p>
&lt;p>LM Head에서 출력되는 로짓(logit)에서는 Temperature 파라미터를 사용하여 확률을 스케일링하고 Top-K 추출이나 Top-P(Nucleus Sampling) 기법으로 후보를 좁힌 뒤, 난수를 사용하여 최종적인 다음 토큰을 결정합니다.&lt;/p>
&lt;hr>
&lt;h2 id="9-c-프로젝트-시작-windows--powershell-환경">9. C++ 프로젝트 시작 (Windows / PowerShell 환경)
&lt;/h2>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cmake_minimum_required&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">VERSION&lt;/span> &lt;span class="s">3.14&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">project&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">TinyLLaMACpp&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">CMAKE_CXX_STANDARD&lt;/span> &lt;span class="s">17&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Windows (MSVC)를 위한 최적화 및 AVX2 플래그 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nb">if&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">MSVC&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">add_compile_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">/O2&lt;/span> &lt;span class="s">/arch:AVX2&lt;/span> &lt;span class="s">/fp:fast&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">add_link_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">/STACK:8388608&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">else&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span> &lt;span class="nb">add_compile_options&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">-O3&lt;/span> &lt;span class="s">-march=native&lt;/span> &lt;span class="s">-ffast-math&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">endif&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">add_library&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ggml&lt;/span> &lt;span class="s">OBJECT&lt;/span> &lt;span class="s">ggml/ggml.c&lt;/span> &lt;span class="s">ggml/ggml-alloc.c&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">target_compile_definitions&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ggml&lt;/span> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="s">GGML_USE_AVX2&lt;/span> &lt;span class="s">GGML_USE_F16C&lt;/span> &lt;span class="s">GGML_USE_FMA&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">add_executable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">main&lt;/span> &lt;span class="s">main.cpp&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">target_link_libraries&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">main&lt;/span> &lt;span class="s">ggml&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>PowerShell에서의 빌드 명령어 예시:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">mkdir&lt;/span> &lt;span class="n">build&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd &lt;/span>&lt;span class="n">build&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cmake&lt;/span> &lt;span class="p">..&lt;/span> &lt;span class="n">-G&lt;/span> &lt;span class="s2">&amp;#34;Visual Studio 17 2022&amp;#34;&lt;/span> &lt;span class="n">-A&lt;/span> &lt;span class="n">x64&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cmake&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-build&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-config&lt;/span> &lt;span class="n">Release&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr>
&lt;h2 id="10-요약">10. 요약
&lt;/h2>&lt;p>C++와 ggml을 사용하여 TinyLLaMA와 같은 소규모 AI 모델의 추론 엔진을 제로부터 구현하는 것은 딥러닝의 블랙박스를 파헤치고 저수준 하드웨어 제어의 아름다움을 배울 수 있는 절호의 기회입니다. 메모리 매핑을 이용한 제로 카피 로드, SIMD 최적화, KV 캐시 구축 등 시스템 프로그래밍의 정수를 마음껏 맛보며 엣지 AI의 미래를 개척해 봅시다.&lt;/p></description></item><item><title>【RAG 구현 입문】 로컬 AI에 내 문서를 읽게 하는 방법</title><link>http://kenji.blog/ko/p/rag-local-ai-implementation-guide/</link><pubDate>Fri, 11 Sep 2026 13:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/rag-local-ai-implementation-guide/</guid><description>&lt;img src="http://kenji.blog/p/rag-local-ai-implementation-guide/img/eyecatch.jpg" alt="Featured image of post 【RAG 구현 입문】 로컬 AI에 내 문서를 읽게 하는 방법" />&lt;h1 id="들어가며">들어가며
&lt;/h1>&lt;p>최근 대규모 언어 모델(LLM)의 진화는 눈부시며, ChatGPT와 Claude 등을 필두로 많은 AI가 우리의 생활과 업무에 스며들고 있습니다. 하지만 일반적인 LLM에는 명확한 약점이 존재합니다. 바로 &amp;lsquo;학습 시점의 공개 정보&amp;rsquo;밖에 모른다는 점입니다. 사내 규정, 개인적인 메모, 미공개 프로젝트 자료와 같은 &amp;lsquo;비공개 문서&amp;rsquo;와 관련된 질문에는 당연히 대답할 수 없습니다. 무리하게 대답을 유도하면 사실과 다른 그럴듯한 거짓말(환각, Hallucination)을 생성할 위험이 커집니다.&lt;/p>
&lt;p>그래서 현재 전 세계적으로 폭발적으로 보급되고 있는 것이 &lt;strong>RAG(Retrieval-Augmented Generation: 검색 증강 생성)&lt;/strong> 라는 기술 아키텍처입니다. RAG를 사용하면 LLM에 독자적인 지식을 외부 데이터베이스로부터 동적으로 제공하고, 이를 바탕으로 정확하고 근거 있는 답변을 생성하게 할 수 있습니다.&lt;/p>
&lt;p>또한, 엔터프라이즈 영역이나 개인의 기밀 정보를 다룰 경우, OpenAI와 같은 클라우드 기반 API에 데이터를 전송하는 것은 보안 정책상 허용되지 않는 경우가 많습니다. 여기서 요구되는 것이 &lt;strong>로컬 AI&lt;/strong>(자신의 PC나 온프레미스 서버에서 독립적으로 동작하는 LLM)와 결합한 &amp;lsquo;로컬 RAG&amp;rsquo;의 구축입니다.&lt;/p>
&lt;p>이 글에서는 RAG의 기초 이론부터 Python을 사용한 로컬 RAG의 구체적인 구현 방법, 수학적 배경(벡터 검색의 원리), 그리고 시스템을 실제 운영 환경에서 가동하기 위한 고급 기술까지 철저하게 해설합니다.&lt;/p>
&lt;hr>
&lt;h1 id="1-rag의-전체-아키텍처">1. RAG의 전체 아키텍처
&lt;/h1>&lt;p>RAG는 단일 AI 모델이 아니라 여러 컴포넌트가 연계되는 시스템 아키텍처입니다. 크게 &amp;lsquo;수집(데이터 로드) 페이즈&amp;rsquo;와 &amp;lsquo;검색 및 생성(Retrieval &amp;amp; Generation) 페이즈&amp;rsquo;의 두 가지로 구성됩니다.&lt;/p>
&lt;p>아래의 Mermaid 다이어그램은 RAG 시스템의 전체적인 모습을 보여줍니다.&lt;/p>
&lt;div class="mermaid">graph TD
subgraph "수집 페이즈 (사전 준비)"
Doc["독자적인 문서 (PDF, TXT, etc.)"] --> Loader["문서 로더"]
Loader --> Splitter["텍스트 분할 (청킹)"]
Splitter --> EmbedModel1["임베딩 모델 (Embedding)"]
EmbedModel1 --> VectorDB["벡터 데이터베이스"]
end
subgraph "추론 페이즈 (사용자 질의 시)"
User["사용자의 질문 (쿼리)"] --> EmbedModel2["임베딩 모델 (Embedding)"]
EmbedModel2 --> QueryVector["쿼리 벡터"]
QueryVector --> Search["유사도 검색 (벡터 검색)"]
VectorDB --> Search
Search --> Context["관련 청크 추출 (컨텍스트)"]
User --> PromptBuilder["프롬프트 구축"]
Context --> PromptBuilder
PromptBuilder --> LocalLLM["로컬 LLM"]
LocalLLM --> Answer["최종적인 답변 생성"]
end&lt;/div>
&lt;h2 id="수집-페이즈-사전-준비">수집 페이즈 (사전 준비)
&lt;/h2>&lt;ol>
&lt;li>&lt;strong>문서 로드&lt;/strong>: PDF, Word, 텍스트 파일 등의 비정형 데이터를 읽어 들입니다.&lt;/li>
&lt;li>&lt;strong>청킹 (텍스트 분할)&lt;/strong>: LLM의 입력 제한(컨텍스트 윈도우) 내에 맞추고 검색 정확도를 높이기 위해, 긴 문장을 의미 있는 덩어리(청크)로 분할합니다.&lt;/li>
&lt;li>&lt;strong>임베딩 (벡터화)&lt;/strong>: 분할된 청크를 임베딩 모델(Embedding Model)에 입력하여 수백~수천 차원의 수치 배열(벡터)로 변환합니다.&lt;/li>
&lt;li>&lt;strong>데이터베이스에 저장&lt;/strong>: 변환된 벡터와 원본 텍스트 데이터를 연결하여 벡터 데이터베이스(Vector DB)에 저장합니다.&lt;/li>
&lt;/ol>
&lt;h2 id="추론-페이즈-실행-시">추론 페이즈 (실행 시)
&lt;/h2>&lt;ol>
&lt;li>&lt;strong>쿼리 벡터화&lt;/strong>: 사용자의 질문 문장을 사전 준비와 동일한 임베딩 모델을 사용하여 벡터화합니다.&lt;/li>
&lt;li>&lt;strong>유사도 검색&lt;/strong>: 쿼리 벡터와 데이터베이스 내의 문서 벡터 간에 유사도 계산을 수행하여, 의미적으로 가까운(관련성이 높은) 텍스트 청크를 상위 몇 개 가져옵니다.&lt;/li>
&lt;li>&lt;strong>프롬프트 구축&lt;/strong>: 가져온 관련 텍스트를 &amp;lsquo;컨텍스트(배경지식)&amp;lsquo;로서 사용자의 질문 문장과 결합하여 LLM에 입력할 프롬프트를 만듭니다.&lt;/li>
&lt;li>&lt;strong>답변 생성&lt;/strong>: 확장된 프롬프트를 받은 LLM이 제공된 컨텍스트 정보를 바탕으로 답변을 생성합니다.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h1 id="2-벡터-검색과-임베딩embeddings의-깊은-이해">2. 벡터 검색과 임베딩(Embeddings)의 깊은 이해
&lt;/h1>&lt;p>RAG의 핵심을 이루는 것이 &amp;lsquo;벡터 검색(시맨틱 검색)&amp;lsquo;입니다. 기존의 키워드 검색(BM25 등)이 단어의 완전 일치나 빈도에 기반하는 반면, 벡터 검색은 &amp;lsquo;의미의 유사성&amp;rsquo;에 기반합니다. 예를 들어 &amp;lsquo;개&amp;rsquo;와 &amp;lsquo;강아지&amp;rsquo;, &amp;lsquo;PC&amp;rsquo;와 &amp;lsquo;컴퓨터&amp;rsquo;처럼 다른 단어라도 의미가 비슷하면 검색 결과에 나타납니다.&lt;/p>
&lt;h2 id="임베딩-모델embedding-model이란-무엇인가">임베딩 모델(Embedding Model)이란 무엇인가?
&lt;/h2>&lt;p>임베딩 모델은 자연어 텍스트를 입력으로 받아, 고정 길이의 밀집 벡터(Dense Vector)를 출력하는 신경망입니다. 일반적인 모델(예를 들어 &lt;code>text-embedding-3-small&lt;/code>이나 오픈소스인 &lt;code>multilingual-e5-large&lt;/code>)은 텍스트를 384차원이나 1024차원의 실수 벡터로 매핑합니다.&lt;/p>
&lt;p>이 다차원 공간(잠재 공간)에서는 의미가 비슷한 문장일수록 좌표 공간상의 거리가 가까워지도록 학습되어 있습니다.&lt;/p>
&lt;h2 id="유사도-계산의-수학적-배경-코사인-유사도">유사도 계산의 수학적 배경: 코사인 유사도
&lt;/h2>&lt;p>벡터 데이터베이스가 관련 문서를 검색할 때 가장 일반적으로 사용되는 거리 지표가 &lt;strong>코사인 유사도(Cosine Similarity)&lt;/strong> 입니다. 유클리드 거리(공간적인 절대 거리)와는 달리, 코사인 유사도는 &amp;lsquo;두 벡터가 이루는 각&amp;rsquo;에 주목합니다. 문장의 길이(벡터의 노름)에 영향을 덜 받기 때문에 텍스트의 유사도 계산에 매우 적합합니다.&lt;/p>
&lt;p>수식으로 나타내면, 벡터 $\mathbf{A}$와 $\mathbf{B}$의 코사인 유사도는 다음과 같습니다.&lt;/p>
$$ \text{Cosine Similarity}(\mathbf{A}, \mathbf{B}) = \cos(\theta) = \frac{\mathbf{A} \cdot \mathbf{B}}{\|\mathbf{A}\| \|\mathbf{B}\|} = \frac{\sum_{i=1}^{n} A_i B_i}{\sqrt{\sum_{i=1}^{n} A_i^2} \sqrt{\sum_{i=1}^{n} B_i^2}} $$
&lt;ul>
&lt;li>$\mathbf{A} \cdot \mathbf{B}$ 는 내적(Dot Product)을 나타냅니다.&lt;/li>
&lt;li>$\|\mathbf{A}\|$ 는 벡터 $\mathbf{A}$ 의 L2 노름(길이)을 나타냅니다.&lt;/li>
&lt;li>$n$ 은 벡터의 차원 수입니다.&lt;/li>
&lt;/ul>
&lt;p>코사인 유사도는 -1에서 1 사이의 값을 가집니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>1 에 가까움&lt;/strong>: 두 벡터의 방향이 거의 같음 (의미가 매우 비슷함)&lt;/li>
&lt;li>&lt;strong>0 에 가까움&lt;/strong>: 두 벡터가 직교함 (무관함)&lt;/li>
&lt;li>&lt;strong>-1 에 가까움&lt;/strong>: 두 벡터의 방향이 정반대임 (의미가 반대임)&lt;/li>
&lt;/ul>
&lt;p>최근의 벡터 DB(Chroma, FAISS, Qdrant 등)는 HNSW(Hierarchical Navigable Small World)라고 불리는 근사 최근접 이웃 탐색(ANN) 알고리즘을 채택하고 있어, 수백만 건의 벡터 데이터에서도 밀리초 단위로 코사인 유사도가 높은 문서를 검색할 수 있도록 최적화되어 있습니다.&lt;/p>
&lt;hr>
&lt;h1 id="3-로컬-rag를-구축하기-위한-기술-스택">3. 로컬 RAG를 구축하기 위한 기술 스택
&lt;/h1>&lt;p>클라우드에 의존하지 않는 완전한 로컬 RAG를 구축하려면 오픈소스 생태계를 활용합니다. 아래에 권장되는 기술 스택을 소개합니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>언어 모델 (LLM)&lt;/strong>
&lt;ul>
&lt;li>도구: &lt;code>Ollama&lt;/code> 또는 &lt;code>Llama.cpp&lt;/code>&lt;/li>
&lt;li>모델: &lt;code>Llama-3-8B-Instruct&lt;/code>, &lt;code>Gemma-2-9B-It&lt;/code>, &lt;code>Qwen2-7B-Instruct&lt;/code> 등의 경량·고성능 오픈 모델. 일본어 태스크에는 일본어 튜닝이 된 &lt;code>Llama-3-ELYZA-JP-8B&lt;/code> 등이 적합합니다.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>임베딩 모델 (Embedding)&lt;/strong>
&lt;ul>
&lt;li>모델: &lt;code>intfloat/multilingual-e5-large&lt;/code> 또는 &lt;code>BAAI/bge-m3&lt;/code>. 로컬에서 구동할 경우, Hugging Face에서 다운로드하여 Sentence-Transformers로 실행하는 것이 일반적입니다.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>벡터 데이터베이스 (Vector DB)&lt;/strong>
&lt;ul>
&lt;li>&lt;code>ChromaDB&lt;/code>: Python 기반으로 설정이 매우 간단함. 로컬 개발에 최적.&lt;/li>
&lt;li>&lt;code>FAISS&lt;/code>: Meta가 개발한 고속 벡터 검색 라이브러리.&lt;/li>
&lt;li>&lt;code>Qdrant&lt;/code> / &lt;code>Milvus&lt;/code>: 보다 대규모이며 프로덕션 환경용.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>오케스트레이션 프레임워크&lt;/strong>
&lt;ul>
&lt;li>&lt;code>LangChain&lt;/code>: 컴포넌트들을 연결(Chain)하기 위한 사실상의 표준.&lt;/li>
&lt;li>&lt;code>LlamaIndex&lt;/code>: 특히 RAG에 특화된 데이터 연결 프레임워크.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>이번에는 도입이 가장 간단한 &lt;strong>LangChain + ChromaDB + Ollama + HuggingFaceEmbeddings&lt;/strong> 조합으로 구현합니다.&lt;/p>
&lt;hr>
&lt;h1 id="4-구현-튜토리얼-python을-활용한-완전한-로컬-rag-구축">4. 구현 튜토리얼: Python을 활용한 완전한 로컬 RAG 구축
&lt;/h1>&lt;p>지금부터는 실제로 Python 코드를 작성하면서 로컬 RAG를 구축해 보겠습니다. 사전에 PC에 Ollama를 설치하고 백그라운드에서 실행해 두세요. 또한, Ollama에서 모델을 풀(pull)해 둡니다(예: &lt;code>ollama run llama3&lt;/code>).&lt;/p>
&lt;h2 id="step-1-필요한-라이브러리-설치">Step 1: 필요한 라이브러리 설치
&lt;/h2>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install langchain langchain-community langchain-huggingface
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install chromadb sentence-transformers pypdf
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="step-2-구현-코드-전체-모습">Step 2: 구현 코드 전체 모습
&lt;/h2>&lt;p>다음은 PDF 파일을 읽어 벡터화하고 로컬 LLM이 질문에 답하게 하는 완전한 Python 스크립트입니다.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;span class="lnt">64
&lt;/span>&lt;span class="lnt">65
&lt;/span>&lt;span class="lnt">66
&lt;/span>&lt;span class="lnt">67
&lt;/span>&lt;span class="lnt">68
&lt;/span>&lt;span class="lnt">69
&lt;/span>&lt;span class="lnt">70
&lt;/span>&lt;span class="lnt">71
&lt;/span>&lt;span class="lnt">72
&lt;/span>&lt;span class="lnt">73
&lt;/span>&lt;span class="lnt">74
&lt;/span>&lt;span class="lnt">75
&lt;/span>&lt;span class="lnt">76
&lt;/span>&lt;span class="lnt">77
&lt;/span>&lt;span class="lnt">78
&lt;/span>&lt;span class="lnt">79
&lt;/span>&lt;span class="lnt">80
&lt;/span>&lt;span class="lnt">81
&lt;/span>&lt;span class="lnt">82
&lt;/span>&lt;span class="lnt">83
&lt;/span>&lt;span class="lnt">84
&lt;/span>&lt;span class="lnt">85
&lt;/span>&lt;span class="lnt">86
&lt;/span>&lt;span class="lnt">87
&lt;/span>&lt;span class="lnt">88
&lt;/span>&lt;span class="lnt">89
&lt;/span>&lt;span class="lnt">90
&lt;/span>&lt;span class="lnt">91
&lt;/span>&lt;span class="lnt">92
&lt;/span>&lt;span class="lnt">93
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_community.document_loaders&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">PyPDFLoader&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_text_splitters&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">RecursiveCharacterTextSplitter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_huggingface&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">HuggingFaceEmbeddings&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_community.vectorstores&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Chroma&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_community.llms&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Ollama&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.prompts&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain.chains&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">RetrievalQA&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 1. 문서 로드&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;문서를 읽고 있습니다...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 읽어 들일 PDF의 경로를 지정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">file_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;sample_company_policy.pdf&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">loader&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PyPDFLoader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">file_path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">documents&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">loader&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 2. 청크 분할 (Text Splitting)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 문장의 의미를 훼손하지 않도록 적당한 크기로 분할한다&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">text_splitter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">RecursiveCharacterTextSplitter&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chunk_size&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">500&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 1청크당 최대 글자 수&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chunk_overlap&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 앞뒤 청크의 중복 글자 수 (문맥 단절 방지)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">separators&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;。&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;、&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34; &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chunks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">text_splitter&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split_documents&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">documents&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunks&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">개의 청크로 분할되었습니다.&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 3. 임베딩 모델 초기화 (Local HuggingFace Model)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 일본어에 강한 다국어 모델 사용&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;임베딩 모델을 로드하고 있습니다...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">embeddings&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">HuggingFaceEmbeddings&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model_name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;intfloat/multilingual-e5-large&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model_kwargs&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;device&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;cpu&amp;#39;&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="c1"># GPU가 있는 경우 &amp;#39;cuda&amp;#39; 또는 &amp;#39;mps&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 4. 벡터 데이터베이스 구축 (Chroma)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;벡터 데이터베이스를 구축하고 있습니다...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">persist_directory&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;./chroma_db&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">vectorstore&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Chroma&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_documents&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">documents&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">chunks&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">embedding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">embeddings&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">persist_directory&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">persist_directory&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 검색기(Retriever) 생성. 상위 3건의 관련 문서를 가져오도록 설정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">retriever&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">vectorstore&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">as_retriever&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">search_kwargs&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;k&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 5. 로컬 LLM 초기화 (Ollama)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;로컬 LLM에 연결하고 있습니다...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 사전에 &amp;#39;ollama pull llama3&amp;#39; 등으로 모델을 다운로드해 둘 것&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Ollama&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;llama3&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 6. 프롬프트 템플릿 정의&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">prompt_template&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;당신은 회사의 규정이나 사내 정보에 해박한 우수한 어시스턴트입니다.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">아래의 컨텍스트(배경 정보)만을 사용하여 사용자의 질문에 한국어로 상세히 답해 주세요.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">컨텍스트에서 답을 찾을 수 없는 경우, 함부로 추측하지 말고 &amp;#39;제공된 정보로는 알 수 없습니다&amp;#39;라고 솔직하게 답해 주세요.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">【컨텍스트】
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&lt;/span>&lt;span class="si">{context}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">【질문】
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&lt;/span>&lt;span class="si">{question}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">【답변】:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">PROMPT&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">template&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">prompt_template&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">input_variables&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;context&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;question&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 7. RAG 체인 구축&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qa_chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">RetrievalQA&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_chain_type&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llm&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">llm&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chain_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;stuff&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">retriever&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">retriever&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">return_source_documents&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 정보 출처를 반환할지 설정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chain_type_kwargs&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;prompt&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">PROMPT&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 8. 질문 실행&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">query&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;원격 근무 시 교통비 지급 조건에 대해 알려주세요.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">질문: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">qa_chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;query&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">query&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;【답변】&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;result&amp;#39;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">---&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;【참조한 정보 출처】&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">doc&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;source_documents&amp;#39;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;- 페이지 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">doc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">metadata&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;page&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;알 수 없음&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">doc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">page_content&lt;/span>&lt;span class="p">[:&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">main&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="코드의-포인트-해설">코드의 포인트 해설
&lt;/h2>&lt;ol>
&lt;li>&lt;strong>RecursiveCharacterTextSplitter&lt;/strong>:
자연어 분할에서 가장 권장되는 스플리터입니다. 단락(&lt;code>\n\n&lt;/code>), 줄(&lt;code>\n&lt;/code>), 마침표(&lt;code>。&lt;/code>) 순으로 분할을 시도하며, 의미 덩어리를 가능한 한 유지한 채 지정된 &lt;code>chunk_size&lt;/code>에 맞도록 분할합니다. &lt;code>chunk_overlap&lt;/code>을 설정함으로써 문맥의 경계가 끊어져 정보가 손실되는 것을 방지합니다.&lt;/li>
&lt;li>&lt;strong>HuggingFaceEmbeddings&lt;/strong>:
&lt;code>intfloat/multilingual-e5-large&lt;/code>는 다국어를 지원하는 매우 강력한 오픈소스 임베딩 모델입니다. 클라우드 API(OpenAI의 &lt;code>text-embedding-ada-002&lt;/code> 등)를 사용하지 않고도, 오프라인으로 로컬 메모리상에서 텍스트를 벡터화할 수 있습니다.&lt;/li>
&lt;li>&lt;strong>ChromaDB&lt;/strong>:
인메모리 또는 로컬 스토리지(SQLite 기반)에서 동작하기 때문에 복잡한 데이터베이스 서버를 구동할 필요가 없습니다. &lt;code>persist_directory&lt;/code>를 지정하면, 재실행 시 벡터화 프로세스를 건너뛰고 디스크에서 DB를 읽어올 수 있습니다.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h1 id="5-발전된-rag-기법-advanced-rag-techniques">5. 발전된 RAG 기법 (Advanced RAG Techniques)
&lt;/h1>&lt;p>위의 튜토리얼에서 구축한 기본 RAG 시스템(Naive RAG)으로도 동작하지만, 프로덕션 환경에서 높은 답변 정확도를 요구받을 경우 다음과 같은 고급 기술의 도입이 필요해집니다.&lt;/p>
&lt;h2 id="51-하이브리드-검색-hybrid-search">5.1 하이브리드 검색 (Hybrid Search)
&lt;/h2>&lt;p>벡터 검색은 &amp;lsquo;의미&amp;rsquo;를 파악하는 데에는 능하지만, &amp;lsquo;특정 고유명사&amp;rsquo;, &amp;lsquo;제품 모델명&amp;rsquo;, &amp;lsquo;직원 ID&amp;rsquo; 등과 같은 엄밀한 키워드 검색에는 약할 수 있습니다.
그래서 벡터 검색에 의한 &lt;strong>시맨틱 검색&lt;/strong>과 BM25 알고리즘 등을 사용한 &lt;strong>키워드 검색&lt;/strong>을 병행하고, 양쪽의 결과를 스코어링하여 통합하는(Reciprocal Rank Fusion; RRF 등의 기법을 사용) 방식으로 검색 누락을 극적으로 줄일 수 있습니다.&lt;/p>
&lt;h2 id="52-리랭킹-re-ranking">5.2 리랭킹 (Re-ranking)
&lt;/h2>&lt;p>벡터 검색은 고속이지만, 반드시 컨텍스트의 정확한 문맥 적합성을 평가하고 있는 것은 아닙니다. 검색 정확도를 향상시키기 위한 일반적인 파이프라인은 다음과 같습니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>초기 검색 (First-stage Retrieval)&lt;/strong>: 벡터 DB에서 넓고 얕게 관련 청크를 20~30건 정도 가져옵니다.&lt;/li>
&lt;li>&lt;strong>재평가 (Re-ranking)&lt;/strong>: Cross-Encoder라고 불리는 다른 더 무거운 머신러닝 모델(예: &lt;code>bge-reranker&lt;/code> 등)을 사용하여, 사용자의 쿼리와 가져온 청크의 쌍을 입력해 의미적 적합도 점수를 다시 계산합니다.&lt;/li>
&lt;li>&lt;strong>선별&lt;/strong>: 점수가 높은 상위 3~5건만을 최종적인 컨텍스트로서 LLM의 프롬프트에 전달합니다.&lt;/li>
&lt;/ol>
&lt;p>이 기법을 통해 무관한 노이즈 정보가 LLM에 전달되는 것을 방지하고 답변의 정확도(Precision)를 대폭 높일 수 있습니다.&lt;/p>
&lt;div class="mermaid">graph LR
Query["쿼리"] --> VSearch["벡터 검색 (상위 20건)"]
VSearch --> Reranker["리랭커 모델 (Cross-Encoder)"]
Query --> Reranker
Reranker --> TopK["고정밀 상위 3건"]
TopK --> LLM["LLM 생성"]&lt;/div>
&lt;h2 id="53-시맨틱-청킹과-부모-문서-검색">5.3 시맨틱 청킹과 부모 문서 검색
&lt;/h2>&lt;p>고정된 글자 수로 기계적으로 텍스트를 분할하는 것이 아니라, 문장의 의미 변화를 AI로 감지하여 분할하는 &amp;lsquo;Semantic Chunking(시맨틱 청킹)&amp;lsquo;이라는 기법이 있습니다.
또한, &amp;lsquo;Parent 단락 검색(Parent Document Retriever)&amp;lsquo;이라는 기법에서는 검색용으로 매우 작은 단위(문장 등)로 벡터화를 수행하여 정확도 높은 검색을 실현하면서도, LLM에 전달할 때는 그 문장이 포함된 &amp;lsquo;원래의 큰 단락(부모 문서)&amp;lsquo;을 전달함으로써 LLM에 충분한 문맥(컨텍스트)을 제공합니다.&lt;/p>
&lt;hr>
&lt;h1 id="6-로컬-rag-운영-시의-과제와-대책">6. 로컬 RAG 운영 시의 과제와 대책
&lt;/h1>&lt;p>로컬 환경에서 RAG를 구축하고 운영할 때는 특유의 장벽이 존재합니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>VRAM(비디오 메모리) 고갈&lt;/strong>:
로컬 LLM을 실용적인 속도(1초에 수십 토큰)로 구동하려면 GPU의 VRAM에 모델을 올려야 합니다. 8B 클래스의 모델을 fp16(16비트 부동소수점)으로 구동하려면 약 16GB의 VRAM이 필요하지만, &lt;strong>양자화(Quantization)&lt;/strong> 기술(GGUF나 AWQ 형식 등, 4bit나 8bit로 압축하는 기술)을 사용하면 8GB의 VRAM(일반적인 게이밍 PC 등)에서도 충분히 고속으로 동작시킬 수 있습니다. Llama.cpp나 Ollama는 기본적으로 이러한 양자화 포맷을 지원합니다.&lt;/li>
&lt;li>&lt;strong>컨텍스트 윈도우의 제한&lt;/strong>:
검색하여 가져온 컨텍스트의 양이 너무 많으면 LLM의 입력 상한(토큰 제한)을 초과해 버리거나, 모델이 정보의 중간 부분을 잊어버리는(Lost in the middle 현상) 경우가 있습니다. 추출할 청크 수의 조정이나 앞서 언급한 리랭킹 기술을 통한 엄선이 필수적입니다.&lt;/li>
&lt;li>&lt;strong>데이터 최신성 관리&lt;/strong>:
소스 문서가 업데이트된 경우, 벡터 데이터베이스 내의 해당 문서 벡터도 업데이트 및 삭제(CRUD 작업)할 필요가 있습니다. ChromaDB에서는 문서 ID 기반의 업데이트를 지원하므로, 파일의 해시값을 관리하여 변경 사항만을 동기화하는 배치 처리를 구성하는 것이 실용적입니다.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h1 id="마무리">마무리
&lt;/h1>&lt;p>RAG(검색 증강 생성)는 AI를 일반적인 범용 어시스턴트에서 &amp;lsquo;당신 전속의 전문가&amp;rsquo;나 &amp;lsquo;사내 업무에 특화된 전문가&amp;rsquo;로 진화시키는 강력한 패러다임입니다.&lt;/p>
&lt;p>클라우드 서비스를 이용할 수 없는 기밀성이 높은 요구사항이라도, Ollama, LangChain, ChromaDB 등의 오픈소스 생태계를 결합함으로써 완전한 &amp;lsquo;로컬 RAG&amp;rsquo; 환경을 비교적 쉽게 구축할 수 있음을 알 수 있었습니다.&lt;/p>
&lt;p>이 글에서 해설한 벡터 공간의 수학적 이해나 텍스트 분할, 리랭킹과 같은 발전된 접근 방식을 바탕으로, 꼭 자신이 보유한 데이터를 사용하여 독자적인 AI 시스템을 개발해 보시기 바랍니다. 로컬 AI의 진화 속도는 경이로우며, 오늘 구축한 시스템은 내일 등장할 더욱 똑똑한 경량 모델로 교체하는 것만으로 순식간에 성능을 업데이트할 수 있습니다.&lt;/p>
&lt;hr>
&lt;p>&lt;em>본 블로그에서는 앞으로도 AI 기술이나 RAG에 관한 심층 기사를 게재할 예정입니다. 질문이나 피드백이 있으시다면 꼭 댓글란에 남겨주세요.&lt;/em>&lt;/p></description></item><item><title>【2026년 최신】 로컬 LLM을 Windows 환경에서 구동하는 완전 가이드</title><link>http://kenji.blog/ko/p/local-llm-windows-2026/</link><pubDate>Fri, 11 Sep 2026 10:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/local-llm-windows-2026/</guid><description>&lt;img src="http://kenji.blog/p/local-llm-windows-2026/img/eyecatch.jpg" alt="Featured image of post 【2026년 최신】 로컬 LLM을 Windows 환경에서 구동하는 완전 가이드" />&lt;h1 id="1-소개-왜-지금-windows에서-로컬-llm인가">1. 소개: 왜 지금 Windows에서 로컬 LLM인가?
&lt;/h1>&lt;p>2026년 현재, 생성형 AI와 대규모 언어 모델(LLM)의 진화는 클라우드 상의 거대한 API 서비스에서 개인 PC나 온프레미스 환경에서 동작하는 &amp;lsquo;로컬 LLM&amp;rsquo;으로 큰 패러다임 전환을 보여주고 있습니다. OpenAI의 GPT-5나 Anthropic의 Claude 3.5와 같은 클라우드 AI는 매우 강력하지만, 기업이나 개인이 모든 데이터를 클라우드로 전송할 수 있는 것은 아닙니다. 개인정보 보호, 보안, 대기 시간(레이턴시), 그리고 장기적이고 지속 가능한 비용 관점에서 로컬 LLM에 대한 수요는 그 어느 때보다 폭발적으로 증가하고 있습니다.&lt;/p>
&lt;p>특히 Windows 환경에서 로컬 LLM 생태계의 발전은 눈부십니다. 몇 년 전까지만 해도 &amp;ldquo;AI 개발 및 실행은 Linux&amp;quot;라는 것이 상식이었지만, 2026년 현재 Windows는 매우 강력하고 편리한 AI 플랫폼으로 변모했습니다.&lt;/p>
&lt;p>본 기사에서는 2026년 최신 기술 동향을 바탕으로 Windows 환경에서 로컬 LLM을 구축, 운영 및 최적화하기 위한 완전한 가이드를 제공합니다. 초보자를 위한 Ollama를 사용한 간단한 구축부터, 고급 사용자를 위한 llama.cpp를 활용한 극한의 최적화, 나아가 VRAM 계산의 수학적 접근 및 아키텍처에 대한 깊은 이해, 그리고 로컬에서의 파인튜닝(미세조정)까지 압도적인 분량으로 철저하게 해설합니다.&lt;/p>
&lt;h2 id="11-2026년-로컬-llm을-둘러싼-기술-트렌드">1.1 2026년 로컬 LLM을 둘러싼 기술 트렌드
&lt;/h2>&lt;p>현재의 로컬 LLM 생태계를 형성하는 주요 트렌드는 다음과 같습니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>GGUF 포맷의 완전한 보급&lt;/strong>: 메타데이터와 텐서를 단일 파일로 통합한 GGUF(GPT-Generated Unified Format)가 완전히 사실상 표준(De facto standard)이 되었습니다. 이를 통해 Hugging Face에서 파일 하나를 다운로드하는 것만으로 어떤 환경에서든 실행할 수 있게 되었습니다.&lt;/li>
&lt;li>&lt;strong>MoE(Mixture of Experts) 아키텍처의 대중화&lt;/strong>: 소규모이면서도 고성능인 MoE 모델이 다수 출시되었으며, 추론 시 일부 전문가(Expert)만 활성화함으로써 일반 소비자용 PC의 계산 부하를 억제하면서 거대 모델에 필적하는 성능을 내고 있습니다.&lt;/li>
&lt;li>&lt;strong>추론 엔진의 고도화된 추상화 및 최적화&lt;/strong>: Ollama, LM Studio, AnythingLLM 등의 도구가 세련되어져서 사용자가 CUDA 드라이버 설치 등 복잡한 의존성을 신경 쓸 필요가 없어졌습니다. 또한 FlashAttention 3의 Windows 네이티브 지원으로 추론 속도가 극적으로 향상되었습니다.&lt;/li>
&lt;li>&lt;strong>NPU 활용과 Windows Copilot+ PC의 대두&lt;/strong>: GPU가 없는 노트북에서도 탑재된 NPU(Neural Processing Unit)를 활용하여 소규모 LLM(SLM: Small Language Models)을 저전력으로 구동하는 기술이 실용화 단계에 접어들었습니다.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h1 id="2-하드웨어-요구-사항-및-os-준비">2. 하드웨어 요구 사항 및 OS 준비
&lt;/h1>&lt;p>로컬 LLM을 실용적인 속도(초당 15~30 토큰 이상)로 구동하기 위해서는 하드웨어 선택이 가장 중요합니다.&lt;/p>
&lt;h2 id="21-권장-하드웨어-구성">2.1 권장 하드웨어 구성
&lt;/h2>&lt;p>AI PC의 진화에 따라 요구 스펙도 변화하고 있습니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>OS&lt;/strong>: Windows 11 Pro (24H2 이후). WSL2의 완전한 기능과 고급 메모리 관리, 나아가 DirectML의 최신 API를 활용하기 위해 필수적입니다.&lt;/li>
&lt;li>&lt;strong>CPU&lt;/strong>: Intel Core Ultra 200 시리즈 이상 또는 AMD Ryzen 9000 시리즈 이상. CPU 추론을 병행할 경우, 광대역 메모리 통신이 필수적입니다.&lt;/li>
&lt;li>&lt;strong>RAM&lt;/strong>: 최소 32GB, 권장 64GB 이상. 메인 메모리의 대역폭(MB/s)이 CPU 추론 시나 오프로드(Offload) 시 결정적인 병목 지점이 됩니다. DDR5-6000 이상의 고속 메모리가 이상적입니다.&lt;/li>
&lt;li>&lt;strong>GPU&lt;/strong>: NVIDIA RTX 4000/5000 시리즈. 로컬 LLM에서 가장 중요한 것은 연산 성능이 아니라 &amp;lsquo;VRAM 용량&amp;rsquo;입니다.
&lt;ul>
&lt;li>&lt;strong>엔트리&lt;/strong>: RTX 4060 Ti (16GB 버전) - 가성비 최강. 8B~14B 클래스의 모델에 최적입니다.&lt;/li>
&lt;li>&lt;strong>미들레인지&lt;/strong>: RTX 4070 Ti SUPER (16GB) / RTX 4080 SUPER (16GB)&lt;/li>
&lt;li>&lt;strong>하이엔드&lt;/strong>: RTX 4090 (24GB) / RTX 5090 (32GB) - 30B~70B 클래스의 양자화 모델을 구동하기 위해 필요합니다.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>스토리지&lt;/strong>: PCIe Gen4 또는 Gen5 NVMe SSD. 수십 GB에 달하는 모델 로드 시간을 극적으로 단축시킵니다.&lt;/li>
&lt;/ul>
&lt;h2 id="22-wsl2-windows-subsystem-for-linux-2-설정">2.2 WSL2 (Windows Subsystem for Linux 2) 설정
&lt;/h2>&lt;p>많은 GUI 도구는 Windows 네이티브로 동작하지만, Python을 사용한 개발, 최신 도구 컴파일, 후술할 LoRA 파인튜닝에는 WSL2가 매우 편리합니다. Windows 11의 최신 환경에서는 호스트 쪽에 NVIDIA 드라이버를 설치하는 것만으로 WSL2에서 투명하게 GPU(CUDA)를 사용할 수 있습니다.&lt;/p>
&lt;p>관리자 권한으로 PowerShell을 열고 다음을 실행합니다.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># WSL2와 최신 Ubuntu 설치&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">wsl&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-install&lt;/span> &lt;span class="n">-d&lt;/span> &lt;span class="n">Ubuntu&lt;/span>&lt;span class="p">-&lt;/span>&lt;span class="mf">24.04&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 커널 업데이트&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">wsl&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-update&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>설치 후 WSL2 터미널 내에서 &lt;code>nvidia-smi&lt;/code>를 실행하여 GPU가 정상적으로 인식되면 성공입니다.&lt;/p>
&lt;hr>
&lt;h1 id="3-로컬-llm-아키텍처-및-추론-메커니즘">3. 로컬 LLM 아키텍처 및 추론 메커니즘
&lt;/h1>&lt;p>로컬 환경에서 모델이 어떻게 텍스트를 생성하는지, 그 내부 구조를 이해하는 것은 문제 해결(트러블슈팅)이나 최적화에 매우 유용합니다.&lt;/p>
&lt;p>다음 Mermaid 다이어그램은 전형적인 로컬 LLM의 추론 파이프라인을 보여줍니다.&lt;/p>
&lt;div class="mermaid">graph TD
User["사용자 입력 (프롬프트)"] --> Tokenizer["토크나이저 (Tokenizer)"]
Tokenizer --> Embedding["임베딩 계층 (Embedding)"]
subgraph "Transformer Block (x Layers)"
Embedding --> Attn["자기 주의 (Self-Attention)"]
Attn --> KVCache["KV 캐시 (Key/Value 유지)"]
Attn --> FFN["피드 포워드 네트워크 (FFN)"]
end
FFN --> Logits["로짓 계산 (Logits)"]
Logits --> Sampler["샘플러 (Temperature, Top-K, Top-P)"]
Sampler --> OutputToken["출력 토큰"]
OutputToken --> |"자기 회귀 생성 (Autoregressive)"| Tokenizer
OutputToken --> Decoder["디토크나이저 (Detokenizer)"]
Decoder --> FinalOutput["최종 출력 텍스트"]&lt;/div>
&lt;h2 id="31-2가지-단계-prefill과-decode">3.1 2가지 단계: Prefill과 Decode
&lt;/h2>&lt;p>LLM의 텍스트 생성은 계산 특성이 다른 두 가지 단계로 나뉩니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Prefill (프롬프트 처리) 단계&lt;/strong>: 입력된 프롬프트 전체를 한 번에 처리하고 이해하는 단계입니다. 병렬 계산이 가능하므로 GPU의 계산 능력(FLOPS)이 속도에 직결됩니다. 프롬프트가 길 경우 이 단계에 몇 초가 걸릴 수 있습니다.&lt;/li>
&lt;li>&lt;strong>Decode (토큰 생성) 단계&lt;/strong>: 1 토큰씩 예측하고 다음 입력으로 넘기는(자기 회귀, Autoregressive) 단계입니다. 이 단계에서는 병렬 계산이 제한되므로 GPU의 VRAM 대역폭(Memory Bandwidth)이 결정적인 병목 지점이 됩니다.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h1 id="4-vram-소비량-계산-및-모델-크기의-수학적-이해">4. VRAM 소비량 계산 및 모델 크기의 수학적 이해
&lt;/h1>&lt;p>&amp;ldquo;내 PC에서 어떤 모델이 돌아갈까?&amp;ldquo;를 정확히 판단하려면 VRAM 계산 공식을 이해해야 합니다. VRAM 부족으로 인해 시스템 메모리(RAM)로의 폴백(Fallback)이 발생하면 추론 속도는 10배~100배 느려집니다.&lt;/p>
&lt;h2 id="41-파라미터-크기-기반-기본-vram">4.1 파라미터 크기 기반 기본 VRAM
&lt;/h2>&lt;p>모델의 가중치(웨이트)를 VRAM에 로드하기 위한 메모리 양입니다.
모델 크기 $P$ (파라미터 수, 단위: 10억 = 1B)와 1 파라미터당 바이트 수 $B$를 사용하여 계산합니다.&lt;/p>
$$
V_{base} = P \times B \quad \text{(GB)}
$$
&lt;p>예를 들어, 8B(80억) 파라미터 모델을 FP16(반정밀도 부동소수점, 16비트=2바이트)으로 로드할 경우:&lt;/p>
$$
V_{base} = 8 \times 2 = 16 \text{ GB}
$$
&lt;p>즉, 16GB VRAM을 가진 GPU라도 모델을 로드하는 것만으로 거의 한계에 도달하게 됩니다.&lt;/p>
&lt;h2 id="42-양자화quantization의-마법">4.2 양자화(Quantization)의 마법
&lt;/h2>&lt;p>여기서 &amp;lsquo;양자화&amp;rsquo;가 등장합니다. 파라미터의 정밀도를 낮춰 모델 크기를 극적으로 축소합니다. 가장 일반적인 4bit 양자화(예: Q4_K_M)의 경우, 1 파라미터당 평균 약 0.55바이트가 됩니다.&lt;/p>
$$
V_{base\_4bit} = 8 \times 0.55 = 4.4 \text{ GB}
$$
&lt;p>이를 통해 16GB의 VRAM이 있다면 충분한 여유를 가지고 8B 모델을 구동할 수 있습니다.&lt;/p>
&lt;h2 id="43-kv-캐시-계산-gqa-지원-버전">4.3 KV 캐시 계산 (GQA 지원 버전)
&lt;/h2>&lt;p>추론 시에는 과거의 문맥을 유지하기 위한 &amp;lsquo;KV 캐시&amp;rsquo;가 VRAM을 소비합니다. Llama 3 등 최신 모델에서는 메모리 절약을 위해 GQA(Grouped Query Attention)가 채택되었습니다.&lt;/p>
&lt;p>KV 캐시 소비량 $V_{kv}$ (기가바이트)는 다음 수식으로 표현됩니다.&lt;/p>
$$
V_{kv} = 2 \times b \times s \times l \times \left( \frac{h_{kv}}{h_q} \right) \times h_q \times d \times B_{kv} \div 10^9
$$
&lt;p>정리하면, 키와 값의 헤드 수 $h_{kv}$를 사용하여 단순하게 계산할 수 있습니다.&lt;/p>
$$
V_{kv} = 2 \times b \times s \times l \times h_{kv} \times d \times B_{kv} \div 10^9
$$
&lt;p>여기서:&lt;/p>
&lt;ul>
&lt;li>$b$: 배치 크기 (개인의 로컬 사용이라면 보통 1)&lt;/li>
&lt;li>$s$: 시퀀스 길이 (컨텍스트 길이, 예: 8192)&lt;/li>
&lt;li>$l$: 레이어 수 (예: 32)&lt;/li>
&lt;li>$h_{kv}$: KV 헤드 수 (예: 8)&lt;/li>
&lt;li>$d$: 헤드당 차원 수 (예: 128)&lt;/li>
&lt;li>$B_{kv}$: KV 캐시의 바이트 수 (FP16이면 2)&lt;/li>
&lt;/ul>
&lt;p>계산 예 (Llama 3 8B, 컨텍스트 8192, FP16 캐시):
$V_{kv} = 2 \times 1 \times 8192 \times 32 \times 8 \times 128 \times 2 \div 10^9 \approx 1.07 \text{ GB}$&lt;/p>
&lt;p>컨텍스트 길이 $s$를 길게 할수록 필요한 VRAM은 선형적으로 증가한다는 점에 주의하십시오.&lt;/p>
&lt;hr>
&lt;h1 id="5-실전-1-ollama를-이용한-가장-빠르고-짧은-설정">5. 실전 1: Ollama를 이용한 가장 빠르고 짧은 설정
&lt;/h1>&lt;p>이론을 이해했으니, 실제로 Windows 환경에서 LLM을 구동해 봅시다.
2026년 현재 가장 사용자 친화적인 도구가 &amp;lsquo;Ollama&amp;rsquo;입니다. Docker와 유사한 직관적인 CLI를 제공합니다.&lt;/p>
&lt;h2 id="51-설치-및-실행">5.1 설치 및 실행
&lt;/h2>&lt;ol>
&lt;li>&lt;a class="link" href="https://ollama.com/" target="_blank" rel="noopener"
>Ollama 공식 웹사이트&lt;/a>에서 Windows 버전 설치 프로그램을 다운로드하여 실행합니다.&lt;/li>
&lt;li>PowerShell을 열고 다음 명령어를 입력합니다. 여기서는 일본어/한국어 등을 지원하는 &lt;code>llama3:8b&lt;/code>를 사용합니다.&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">ollama&lt;/span> &lt;span class="n">run&lt;/span> &lt;span class="n">llama3&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="n">8b&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>최초 실행 시 모델 다운로드가 진행됩니다. 완료되면 터미널에서 바로 대화가 가능합니다.&lt;/p>
&lt;h2 id="52-modelfile을-통한-사용자-정의-ai-생성">5.2 Modelfile을 통한 사용자 정의 AI 생성
&lt;/h2>&lt;p>특정 페르소나를 가진 AI를 쉽게 생성할 수 있습니다. 임의의 위치에 &lt;code>Modelfile&lt;/code>을 생성합니다.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">FROM llama3:8b
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SYSTEM &amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">당신은 매우 뛰어난 시니어 소프트웨어 엔지니어입니다.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">사용자의 질문에 대해 반드시 코드 예시를 곁들여 논리적이고 간결하게 답변해 주세요.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PARAMETER temperature 0.3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PARAMETER num_ctx 8192
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>다음 명령어로 자체 모델을 빌드하고 실행합니다.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">ollama&lt;/span> &lt;span class="n">create&lt;/span> &lt;span class="n">SeniorDev&lt;/span> &lt;span class="o">-f&lt;/span> &lt;span class="p">./&lt;/span>&lt;span class="n">Modelfile&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">ollama&lt;/span> &lt;span class="n">run&lt;/span> &lt;span class="n">SeniorDev&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="53-외부-앱-ai-에디터에서-활용">5.3 외부 앱 (AI 에디터)에서 활용
&lt;/h2>&lt;p>Ollama는 &lt;code>http://localhost:11434&lt;/code>에 OpenAI 호환 API 엔드포인트를 공개합니다.
Cursor나 Continue.dev와 같은 VS Code 확장 프로그램의 백엔드 설정에서 URL을 위와 같이 지정하고, 모델 이름에 &lt;code>SeniorDev&lt;/code> 등을 지정하기만 하면 무료로 강력한 로컬 코딩 어시스턴트가 실현됩니다.&lt;/p>
&lt;hr>
&lt;h1 id="6-실전-2-llamacpp에-의한-극한의-성능-튜닝">6. 실전 2: llama.cpp에 의한 극한의 성능 튜닝
&lt;/h1>&lt;p>세밀한 메모리 관리나 최신 포맷(EXL2나 IQ 양자화 등)을 가장 먼저 시도해보고 싶다면, 핵심 엔진인 &lt;code>llama.cpp&lt;/code>를 직접 조작합니다.&lt;/p>
&lt;h2 id="61-llamacpp-빌드-절차">6.1 llama.cpp 빌드 절차
&lt;/h2>&lt;p>Windows 환경에서는 CUDA Toolkit과 CMake를 사용하여 소스에서 빌드하는 것이 가장 좋습니다.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="n">git&lt;/span> &lt;span class="n">clone&lt;/span> &lt;span class="n">https&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="p">//&lt;/span>&lt;span class="n">github&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">com&lt;/span>&lt;span class="p">/&lt;/span>&lt;span class="n">ggerganov&lt;/span>&lt;span class="p">/&lt;/span>&lt;span class="n">llama&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">cpp
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd &lt;/span>&lt;span class="n">llama&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nb">cpp
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">&lt;/span>&lt;span class="n">mkdir&lt;/span> &lt;span class="n">build&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd &lt;/span>&lt;span class="n">build&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># CUDA 지원으로 구성하고 컴파일&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cmake&lt;/span> &lt;span class="p">..&lt;/span> &lt;span class="n">-DLLAMA_CUBLAS&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="n">ON&lt;/span> &lt;span class="n">-DBUILD_SHARED_LIBS&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="n">OFF&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cmake&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-build&lt;/span> &lt;span class="p">.&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-config&lt;/span> &lt;span class="n">Release&lt;/span> &lt;span class="n">-j&lt;/span> &lt;span class="mf">16&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="62-서버-모드에서의-고급-실행">6.2 서버 모드에서의 고급 실행
&lt;/h2>&lt;p>빌드된 &lt;code>llama-server.exe&lt;/code>를 사용하여 모델을 호스팅합니다.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="p">.\&lt;/span>&lt;span class="n">bin&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="n">Release&lt;/span>&lt;span class="p">\&lt;/span>&lt;span class="nb">llama-server&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">exe&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">-&lt;/span>&lt;span class="n">-model&lt;/span> &lt;span class="s2">&amp;#34;C:\models\Llama-3-8B-Instruct.Q4_K_M.gguf&amp;#34;&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">-&lt;/span>&lt;span class="n">-ctx-size&lt;/span> &lt;span class="mf">8192&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">-&lt;/span>&lt;span class="n">-n-gpu-layers&lt;/span> &lt;span class="mf">99&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">-&lt;/span>&lt;span class="n">-threads&lt;/span> &lt;span class="mf">8&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">-&lt;/span>&lt;span class="n">-flash-attn&lt;/span> &lt;span class="p">`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">-&lt;/span>&lt;span class="n">-port&lt;/span> &lt;span class="mf">8080&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;ul>
&lt;li>&lt;code>--n-gpu-layers 99&lt;/code>: 가능한 모든 레이어를 GPU VRAM으로 오프로드합니다.&lt;/li>
&lt;li>&lt;code>--flash-attn&lt;/code>: FlashAttention 3을 활성화하여 추론 속도를 향상시키고 KV 캐시의 VRAM 소비를 줄입니다.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h1 id="7-gui-프론트엔드-lm-studio-및-로컬-rag-구축">7. GUI 프론트엔드: LM Studio 및 로컬 RAG 구축
&lt;/h1>&lt;p>명령줄(CLI)에 거부감이 있거나 RAG(검색 증강 생성)를 직관적으로 수행하고 싶은 경우 GUI를 이용합니다.&lt;/p>
&lt;h2 id="71-lm-studio">7.1 LM Studio
&lt;/h2>&lt;p>LM Studio는 모델 검색, 다운로드, 시스템 요구 사항 사전 확인, 그리고 채팅 UI까지 하나로 통합한 훌륭한 애플리케이션입니다. 앱 내의 &amp;ldquo;Local Server&amp;rdquo; 버튼을 누르기만 하면 OpenAI 호환 API가 시작됩니다.&lt;/p>
&lt;h2 id="72-anythingllm을-이용한-rag-아키텍처">7.2 AnythingLLM을 이용한 RAG 아키텍처
&lt;/h2>&lt;p>사내 문서나 개인 메모를 읽게 하는 RAG 환경의 아키텍처 다이어그램입니다.&lt;/p>
&lt;div class="mermaid">graph LR
Document["문서 (PDF, MD)"] --> Chunking["청크 분할 (Chunking)"]
Chunking --> EmbedModel["임베딩 모델"]
EmbedModel --> VectorDB["벡터 데이터베이스"]
UserQuery["사용자 질문"] --> EmbedQuery["질문 임베딩"]
EmbedQuery --> VectorDB
VectorDB --> |"유사도 검색"| RetrievedDocs["관련 문서 추출"]
UserQuery --> PromptBuilder["프롬프트 생성"]
RetrievedDocs --> PromptBuilder
PromptBuilder --> LocalLLM["로컬 LLM"]
LocalLLM --> Answer["최종 답변"]&lt;/div>
&lt;p>AnythingLLM 데스크톱 버전(Windows)을 사용하면 설정 화면에서 Ollama(LLM 및 Embedding)를 지정하고, 로컬 VectorDB(LanceDB)를 사용하도록 설정하는 것만으로 몇 분 만에 이 아키텍처가 완성됩니다. 데이터를 외부에 전혀 전송하지 않는 프라이빗 AI의 탄생입니다.&lt;/p>
&lt;hr>
&lt;h1 id="8-windows-wsl2-상에서의-파인튜닝-lora">8. Windows WSL2 상에서의 파인튜닝 (LoRA)
&lt;/h1>&lt;p>로컬에서 구동할 뿐만 아니라 내 데이터로 모델을 똑똑하게 만들고 싶다면, LoRA(Low-Rank Adaptation)를 이용한 파인튜닝이 가능합니다. 2026년 현재 &amp;lsquo;Unsloth&amp;rsquo;라는 라이브러리를 사용하면 Windows의 WSL2 환경에서 16GB VRAM으로도 8B 모델의 학습을 몇 시간 만에 완료할 수 있습니다.&lt;/p>
&lt;p>WSL2의 Ubuntu 내에서 다음을 실행하여 환경을 구축합니다.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">conda create --name unsloth_env &lt;span class="nv">python&lt;/span>&lt;span class="o">=&lt;/span>3.11
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">conda activate unsloth_env
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install &lt;span class="s2">&amp;#34;unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install --no-deps trl peft accelerate bitsandbytes
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Unsloth는 CUDA 커널을 극한까지 최적화하여, 표준 Hugging Face 라이브러리와 비교해 학습 속도가 약 2배, VRAM 소비량이 약 절반이 됩니다. Jupyter Notebook을 실행하고 데이터 세트(JSONL 형식)를 읽어 들이는 것만으로, VRAM 12GB~16GB의 RTX 4060 Ti 등에서도 몇 에포크(epoch)의 학습이 가능합니다.&lt;/p>
&lt;hr>
&lt;h1 id="9-성능-및-문제-해결-트러블슈팅">9. 성능 및 문제 해결 (트러블슈팅)
&lt;/h1>&lt;p>자주 직면하는 문제와 그 해결책입니다.&lt;/p>
&lt;h3 id="1-추론-속도가-극단적으로-느림-12-tokenss">1. 추론 속도가 극단적으로 느림 (1~2 tokens/s)
&lt;/h3>&lt;p>&lt;strong>원인&lt;/strong>: 모델이 VRAM에 다 들어가지 못하고 시스템 메모리(RAM)로 오프로드되었습니다.
&lt;strong>대책&lt;/strong>: 작업 관리자에서 &amp;ldquo;전용 GPU 메모리&amp;quot;를 확인해 주세요. 한계에 도달한 경우 컨텍스트 크기(&lt;code>-c&lt;/code>)를 줄이거나 더 낮은 비트 수의 양자화 모델(Q4_K_M 등)을 사용하십시오.&lt;/p>
&lt;h3 id="2-cuda-out-of-memory-에러">2. &amp;ldquo;CUDA out of memory&amp;rdquo; 에러
&lt;/h3>&lt;p>&lt;strong>원인&lt;/strong>: VRAM이 완전히 고갈되었습니다. 특히 대화가 길어져 KV 캐시가 비대해졌을 때 발생합니다.
&lt;strong>대책&lt;/strong>: Ollama의 경우 &lt;code>num_ctx&lt;/code>, llama.cpp의 경우 &lt;code>-c&lt;/code> 값을 의도적으로 작게 제한합니다.&lt;/p>
&lt;h3 id="3-언어일본어한국어-등-생성이-이상함">3. 언어(일본어/한국어 등) 생성이 이상함
&lt;/h3>&lt;p>&lt;strong>원인&lt;/strong>: 프롬프트 템플릿의 불일치 또는 지원하지 않는 모델입니다.
&lt;strong>대책&lt;/strong>: 모델 이름에 &lt;code>Instruct&lt;/code>가 포함된 것을 사용하고, ChatML이나 Llama3 포맷 등 모델 작성자가 지정한 올바른 템플릿이 도구 측에서 선택되어 있는지 확인하십시오.&lt;/p>
&lt;hr>
&lt;h1 id="10-요약-및-향후-전망">10. 요약 및 향후 전망
&lt;/h1>&lt;p>2026년, Windows 환경에서의 로컬 LLM 구축은 더 이상 일부 엔지니어들만의 특권이 아닙니다. GGUF 포맷의 사실상 표준화, Ollama나 LM Studio와 같이 세련된 생태계의 등장, 그리고 FlashAttention을 비롯한 하드웨어 최적화 덕분에 누구나 쉽게 엔터프라이즈급 AI 환경을 구축할 수 있게 되었습니다.&lt;/p>
&lt;p>이 기사에서 설명한 다음 요점들을 꼭 활용해 보시기 바랍니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>VRAM의 수학적 계산&lt;/strong>을 활용하여 내 PC 스펙에 최적화된 모델 크기와 양자화 수준을 논리적으로 선택한다.&lt;/li>
&lt;li>&lt;strong>Ollama&lt;/strong>를 사용하여 가장 빠르게 환경을 구축하고, AI 에디터와 연동하여 생산성을 극적으로 향상시킨다.&lt;/li>
&lt;li>&lt;strong>llama.cpp&lt;/strong>의 고급 파라미터 제어로 하드웨어의 한계 성능을 끌어낸다.&lt;/li>
&lt;li>&lt;strong>AnythingLLM&lt;/strong>으로 기밀 데이터를 다루는 안전한 로컬 RAG 시스템을 구축한다.&lt;/li>
&lt;li>**Unsloth (WSL2)**를 활용하여 나만의 전문 지식을 가진 커스텀 AI를 육성한다.&lt;/li>
&lt;/ol>
&lt;p>AI의 &amp;lsquo;대중화&amp;rsquo;는 더 이상 버즈워드가 아니라 여러분의 Windows 데스크톱에서 작동하는 현실의 시스템입니다. 클라우드 API 사용 비용이나 정보 유출 위험에서 벗어나, 자유롭고 강력한 프라이빗 AI의 세계로 지금 당장 발을 들여놓아 보시기 바랍니다.&lt;/p></description></item></channel></rss>