<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>AI on kenji.blog</title><link>http://kenji.blog/ko/categories/ai/</link><description>Recent content in AI on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ko</language><copyright>kenjinote</copyright><lastBuildDate>Fri, 11 Sep 2026 18:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/ko/categories/ai/index.xml" rel="self" type="application/rss+xml"/><item><title>Microsoft.Windows.AI의 최신 API 활용 사례 및 샘플 코드</title><link>http://kenji.blog/ko/p/microsoft-windows-ai-api-guide/</link><pubDate>Fri, 11 Sep 2026 18:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/microsoft-windows-ai-api-guide/</guid><description>&lt;img src="http://kenji.blog/p/microsoft-windows-ai-api-guide/img/eyecatch.jpg" alt="Featured image of post Microsoft.Windows.AI의 최신 API 활용 사례 및 샘플 코드" />&lt;h1 id="microsoftwindowsai의-최신-api-활용-사례-및-샘플-코드-windows-copilot-runtime의-심연을-탐구하다">Microsoft.Windows.AI의 최신 API 활용 사례 및 샘플 코드: Windows Copilot Runtime의 심연을 탐구하다
&lt;/h1>&lt;h2 id="1-시작하며-ai가-네이티브로-내장되는-windows의-새로운-시대">1. 시작하며: AI가 네이티브로 내장되는 Windows의 새로운 시대
&lt;/h2>&lt;p>최근 AI 기술의 진화는 눈부시며, 클라우드 상에서의 대규모 언어 모델(LLM) 활용에서 엣지 디바이스(로컬 PC)에서의 AI 추론으로 급속히 패러다임 전환이 일어나고 있습니다. 그 핵심을 담당하는 것이 Microsoft가 Windows 11용으로 제공하는 &amp;ldquo;Windows Copilot Runtime&amp;quot;과 이를 제어하기 위한 &amp;ldquo;Microsoft.Windows.AI&amp;rdquo; API입니다.&lt;/p>
&lt;p>클라우드 API(OpenAI나 Azure OpenAI 등)를 이용한 애플리케이션 개발은 쉽지만, 지연 시간(레이턴시), 프라이버시, 그리고 지속적인 비용이라는 과제가 따라다닙니다. 반면, 로컬에서 AI 모델을 구동함으로써 기밀 데이터를 디바이스 밖으로 내보내지 않고 오프라인에서도 작동하는 초저지연 애플리케이션을 구현할 수 있습니다.&lt;/p>
&lt;p>본 기사에서는 앞으로의 Windows 애플리케이션 개발에서 필수가 될 로컬 AI 기능의 구현 방법에 대해 C# 및 C++의 실용적인 샘플 코드를 곁들여 아키텍처부터 성능 튜닝까지 아주 상세하게 철저히 해설합니다. 단순히 API를 호출하는 것에 그치지 않고, 그 이면에 있는 하드웨어(NPU 및 GPU)의 활용, DirectML과의 연동 등 고도의 기술적 세부 사항까지 깊이 파고듭니다.&lt;/p>
&lt;h2 id="2-windows-copilot-runtime과-아키텍처의-전체상">2. Windows Copilot Runtime과 아키텍처의 전체상
&lt;/h2>&lt;p>Windows Copilot Runtime은 개발자가 Windows 상에서 AI 모델을 쉽게 통합하고 최고의 성능을 끌어낼 수 있도록 설계된 일련의 AI 스택입니다. 이 런타임은 OS 레벨에서 하드웨어 가속을 추상화하여 개발자에게 통일된 인터페이스를 제공합니다.&lt;/p>
&lt;div class="mermaid">graph TD
App["Windows 애플리케이션 (C# / C++)"] --> API["Microsoft.Windows.AI APIs"]
App --> ORT["ONNX Runtime"]
API --> WCR["Windows Copilot Runtime (OS 레이어)"]
WCR --> SLM["로컬 모델 (Phi-Silica 등)"]
ORT --> DML["DirectML Execution Provider"]
SLM --> DML
DML --> DXCore["DXCore / DirectX 12"]
DXCore --> NPU["NPU (Neural Processing Unit)"]
DXCore --> GPU["GPU (Graphics Processing Unit)"]
DXCore --> CPU["CPU"]&lt;/div>
&lt;p>위의 아키텍처 다이어그램이 보여주듯이, 애플리케이션은 고수준의 &lt;code>Microsoft.Windows.AI&lt;/code> API를 이용함으로써 OS에 내장된 소규모 언어 모델(SLM: Phi-Silica 등)에 직접 접근할 수 있습니다. 또한, 커스텀 모델을 사용할 경우에는 ONNX Runtime과 DirectML을 통해 하드웨어 가속을 명시적으로 이용하는 것도 가능합니다. OS 레이어가 CPU, GPU, NPU로의 워크로드 분산을 최적화하므로, 개발자는 하드웨어의 차이를 깊이 의식하지 않고도 고성능 AI 앱을 구축할 수 있습니다.&lt;/p>
&lt;h2 id="3-하드웨어-가속과-npu의-수리적-평가">3. 하드웨어 가속과 NPU의 수리적 평가
&lt;/h2>&lt;p>최신 Copilot+ PC에는 AI 처리에 특화된 프로세서인 NPU(Neural Processing Unit)가 탑재되어 있습니다. NPU의 성능은 일반적으로 TOPS(Tera Operations Per Second)로 평가됩니다.&lt;/p>
&lt;p>AI 모델의 추론에 있어서, 특히 행렬 곱셈(GEMM: General Matrix Multiply)의 계산 능력이 처리량(Throughput)을 결정짓습니다. 하드웨어의 이론상 최대 성능 $P_{\text{peak}}$ 은 다음 수식으로 개략적으로 계산됩니다.&lt;/p>
$$
P_{\text{peak}} = f \times N_{\text{cores}} \times N_{\text{MACs/core}} \times 2
$$
&lt;p>여기서,&lt;/p>
&lt;ul>
&lt;li>$f$ 는 NPU의 클럭 주파수(Hz)&lt;/li>
&lt;li>$N_{\text{cores}}$ 는 NPU 내의 코어 수&lt;/li>
&lt;li>$N_{\text{MACs/core}}$ 는 1코어당 MAC(Multiply-Accumulate) 유닛 수&lt;/li>
&lt;li>마지막의 $2$ 는, 1회의 MAC 연산이 곱셈과 덧셈의 2개 오퍼레이션(FLOPs/OPs)으로 계산되기 때문입니다.&lt;/li>
&lt;/ul>
&lt;p>예를 들어, 주파수 1.5GHz, 4코어, 각 코어가 4096MACs를 가진 NPU의 경우,
&lt;/p>
$$
P_{\text{peak}} = 1.5 \times 10^9 \times 4 \times 4096 \times 2 \approx 49.15 \text{ TOPS}
$$
&lt;p>
가 됩니다. Windows 11의 Copilot+ PC 요건인 40 TOPS를 만족하는 성능임이 수학적으로 증명됩니다.&lt;/p>
&lt;p>또한, AI 모델, 특히 LLM의 추론(디코드 페이즈)은 **메모리 바운드(Memory-Bound)**가 되기 쉽습니다. 시스템 메모리의 이론적 대역폭 $BW$ 는 다음과 같이 계산됩니다.&lt;/p>
$$
BW = f_{\text{mem}} \times W_{\text{bus}} \times \frac{2}{8}
$$
&lt;p>LPDDR5x-8533 메모리($f_{\text{mem}} = 8533 \text{ MT/s}$), 128비트 버스($W_{\text{bus}} = 128$)의 경우, 대역폭은 약 $136 \text{ GB/s}$ 가 됩니다. AI 애플리케이션의 최적화에서는 이 대역폭을 어떻게 절약할지가 중요하며, 후술할 모델의 양자화(Quantization)가 필수적입니다.&lt;/p>
&lt;h2 id="4-개발-환경-설정">4. 개발 환경 설정
&lt;/h2>&lt;p>최신 Windows AI API를 이용하려면, 다음의 환경과 툴체인을 갖추어야 합니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>OS&lt;/strong>: Windows 11 버전 24H2 이상 (Copilot+ PC 요건을 만족하는 NPU 탑재 디바이스를 강력히 권장)&lt;/li>
&lt;li>&lt;strong>SDK&lt;/strong>: Windows App SDK (v1.5 이후의 AI 확장 대응 버전)&lt;/li>
&lt;li>&lt;strong>개발 환경&lt;/strong>: Visual Studio 2022 (v17.10 이상), C++를 사용한 네이티브 개발 워크로드 및 .NET 데스크톱 개발 워크로드&lt;/li>
&lt;li>&lt;strong>패키지&lt;/strong>: NuGet을 통해 &lt;code>Microsoft.Windows.AI&lt;/code> 및 &lt;code>Microsoft.ML.OnnxRuntime.DirectML&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;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-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="c">&amp;lt;!-- .csproj 설정 예시 --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ItemGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;PackageReference&lt;/span> &lt;span class="na">Include=&lt;/span>&lt;span class="s">&amp;#34;Microsoft.Windows.AI&amp;#34;&lt;/span> &lt;span class="na">Version=&lt;/span>&lt;span class="s">&amp;#34;1.0.0-preview1&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;PackageReference&lt;/span> &lt;span class="na">Include=&lt;/span>&lt;span class="s">&amp;#34;Microsoft.WindowsAppSDK&amp;#34;&lt;/span> &lt;span class="na">Version=&lt;/span>&lt;span class="s">&amp;#34;1.5.240311000&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;PackageReference&lt;/span> &lt;span class="na">Include=&lt;/span>&lt;span class="s">&amp;#34;Microsoft.ML.OnnxRuntime.DirectML&amp;#34;&lt;/span> &lt;span class="na">Version=&lt;/span>&lt;span class="s">&amp;#34;1.17.1&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/ItemGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="5-deep-dive-1-c을-이용한-로컬-언어-모델phi-silica-활용">5. 【Deep Dive 1】 C#을 이용한 로컬 언어 모델(Phi-Silica) 활용
&lt;/h2>&lt;p>Windows Copilot Runtime에는 Microsoft가 개발한 고효율 소규모 언어 모델인 &amp;ldquo;Phi-Silica&amp;quot;가 OS 표준 컴포넌트로 내장되어 있습니다. 이를 통해 GB 단위의 모델을 네트워크에서 다운로드할 필요 없이, 오프라인 환경에서 고도의 자연어 처리(문서 요약, 코드 생성, 챗봇)가 가능해집니다.&lt;/p>
&lt;p>다음은 &lt;code>Microsoft.Windows.AI.Generative&lt;/code> 네임스페이스를 사용하여 C#으로 채팅 AI를 구축하는 고급 샘플 코드입니다. 스트리밍 응답을 지원하며, UI 스레드를 차단하지 않고 실시간으로 텍스트를 생성합니다.&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;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Text&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Threading&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Threading.Tasks&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">Microsoft.Windows.AI.Generative&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">namespace&lt;/span> &lt;span class="nn">WindowsAI.Sample&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="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">LocalLanguageModelService&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="kd">private&lt;/span> &lt;span class="n">LanguageModel&lt;/span> &lt;span class="n">_languageModel&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">_isInitialized&lt;/span> &lt;span class="p">=&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cs">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cs">/// 언어 모델의 초기화를 수행합니다. NPU 사용 가능 여부를 확인하고 최적의 디바이스에 모델을 로드합니다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cs">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">async&lt;/span> &lt;span class="n">Task&lt;/span> &lt;span class="n">InitializeAsync&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="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_isInitialized&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&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">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;로컬 AI 모델(Phi-Silica)의 시스템 요구 사항 및 가용성을 확인하고 있습니다...&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">// 모델이 시스템에서 사용 가능한지 확인 (미지원일 경우 다운로드가 촉구될 수 있음)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">availability&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">LanguageModel&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CheckAvailabilityAsync&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">availability&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="n">LanguageModelAvailability&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Available&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="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">InvalidOperationException&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;로컬 AI 모델을 현재 사용할 수 없습니다. 상태: {availability}&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">// 모델 인스턴스 생성 (이 타이밍에 메모리 공간으로의 매핑과 NPU 초기화가 수행됨)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_languageModel&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">LanguageModel&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CreateAsync&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_isInitialized&lt;/span> &lt;span class="p">=&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;언어 모델의 초기화가 완료되었습니다. DirectML을 통한 하드웨어 가속이 활성화되었습니다.&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="cs">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cs">/// 사용자의 프롬프트를 받아 스트리밍으로 응답을 생성합니다.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="cs">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">async&lt;/span> &lt;span class="n">Task&lt;/span> &lt;span class="n">GenerateResponseStreamAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">prompt&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CancellationToken&lt;/span> &lt;span class="n">cancellationToken&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="k">if&lt;/span> &lt;span class="p">(!&lt;/span>&lt;span class="n">_isInitialized&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">InitializeAsync&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">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;\n[사용자 입력]: {prompt}\n[AI 어시스턴트]: &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">// 생성 시 하이퍼파라미터 설정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">options&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">LanguageModelOptions&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">Temperature&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0.7f&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">TopP&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0.9f&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">MaxTokens&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">2048&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">RepetitionPenalty&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">1.1f&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">// 대화 컨텍스트 구축&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">context&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">LanguageModelContext&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddSystemMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;당신은 Windows의 로컬 NPU 상에서 직접 동작하는 고도의 AI 어시스턴트입니다. 단계별로 논리적으로 생각하고 간결하게 답변해 주세요.&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">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddUserMessage&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="k">try&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">// 스트리밍 추론 API 호출&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">responseStream&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">_languageModel&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GenerateResponseStreamAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">options&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">// IAsyncEnumerable로 반환되는 청크를 비동기로 반복 처리&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">chunk&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">responseStream&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WithCancellation&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cancellationToken&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="c1">// 생성된 토큰(청크)을 실시간으로 콘솔에 출력&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// UI 애플리케이션의 경우, 여기서 DispatcherQueue를 사용하여 TextBox 등에 반영합니다&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunk&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Text&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">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&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="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">OperationCanceledException&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">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;\n[사용자 또는 시스템에 의해 생성이 취소되었습니다]&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="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">Exception&lt;/span> &lt;span class="n">ex&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">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;\n[치명적인 오류가 발생했습니다: {ex.Message}]&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="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="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="51-c-구현에서의-아키텍처-해설">5.1 C# 구현에서의 아키텍처 해설
&lt;/h3>&lt;p>이 코드의 핵심은 &lt;code>LanguageModel.CheckAvailabilityAsync()&lt;/code> 에 의한 실행 전 검증과 &lt;code>GenerateResponseStreamAsync&lt;/code> 에 의한 비동기 스트리밍입니다. OS 백그라운드에서 동작하는 Copilot Runtime은 이 API 호출을 받으면 내부적으로 ONNX Runtime을 기동하고, 시스템의 구성에 따라 최적의 Execution Provider(대부분의 최신 PC에서는 DirectML + NPU)를 선택합니다.&lt;/p>
&lt;p>개발자는 모델의 텐서 형태, 토크나이저 구현, KV 캐시의 메모리 관리 등을 전혀 의식하지 않고, 단 몇 줄의 C# 코드로 최첨단 AI 추론 파이프라인을 애플리케이션에 통합할 수 있습니다.&lt;/p>
&lt;h2 id="6-deep-dive-2-c와-directml을-이용한-커스텀-모델의-고속-추론">6. 【Deep Dive 2】 C++와 DirectML을 이용한 커스텀 모델의 고속 추론
&lt;/h2>&lt;p>OS 표준 언어 모델만으로는 커버할 수 없는 특정 도메인(독자적인 이미지 세그멘테이션, 음성 인식, 커스텀 객체 탐지 모델 등)을 다룰 경우, 개발자는 &lt;code>Microsoft.Windows.AI&lt;/code> 의 하위 레이어에 위치하는 ONNX Runtime과 DirectML을 직접 제어해야 합니다.&lt;/p>
&lt;p>C++를 사용함으로써 메모리 할당을 극한까지 최적화하고 NPU/GPU의 최대 성능을 끌어낼 수 있습니다. 다음은 ONNX 형식의 커스텀 모델(예: YOLOv8)을 DirectML을 사용하여 C++로 실행하기 위한, 고급 초기화 및 추론 파이프라인의 핵심 구현입니다.&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;span class="lnt">94
&lt;/span>&lt;span class="lnt">95
&lt;/span>&lt;span class="lnt">96
&lt;/span>&lt;span class="lnt">97
&lt;/span>&lt;span class="lnt">98
&lt;/span>&lt;span class="lnt">99
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;vector&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;string&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;stdexcept&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;onnxruntime_cxx_api.h&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;dml_provider_factory.h&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CustomVisionAIProcessor&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">private&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Ort&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Env&lt;/span> &lt;span class="n">env&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Ort&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Session&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="k">nullptr&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Ort&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">AllocatorWithDefaultOptions&lt;/span> &lt;span class="n">allocator&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">public&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">CustomVisionAIProcessor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">wstring&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">modelPath&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">:&lt;/span> &lt;span class="n">env&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ORT_LOGGING_LEVEL_WARNING&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;VisionAIProcessor&amp;#34;&lt;/span>&lt;span class="p">)&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">Ort&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">SessionOptions&lt;/span> &lt;span class="n">sessionOptions&lt;/span>&lt;span class="p">;&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="c1">&lt;/span> &lt;span class="n">sessionOptions&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SetIntraOpNumThreads&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&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="c1">&lt;/span> &lt;span class="n">sessionOptions&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SetGraphOptimizationLevel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">GraphOptimizationLevel&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">ORT_ENABLE_ALL&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">// 1. DirectML Execution Provider (DML EP) 추가
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// device_id = 0 은 시스템의 기본 권장 어댑터(NPU 또는 고성능 GPU)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="n">OrtApi&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">ortApi&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Ort&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">GetApi&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">OrtDmlApi&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">dmlApi&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">nullptr&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">OrtStatus&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">status&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ortApi&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetExecutionProviderApi&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;DML&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ORT_API_VERSION&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">reinterpret_cast&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="o">**&amp;gt;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">dmlApi&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="p">(&lt;/span>&lt;span class="n">status&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="k">nullptr&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">dmlApi&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="k">nullptr&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Ort&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">ThrowOnError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dmlApi&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">SessionOptionsAppendExecutionProvider_DML&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sessionOptions&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;[Info] DirectML Execution Provider를 정상적으로 연결했습니다.&amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&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="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cerr&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;[Warning] DirectML API 가져오기에 실패했습니다. CPU 폴백 모드로 실행합니다.&amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">status&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="k">nullptr&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">ortApi&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ReleaseStatus&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">status&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">// 2. 모델 로드 및 세션 생성
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">session&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Ort&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Session&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">env&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">modelPath&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">c_str&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">sessionOptions&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;[Info] ONNX 모델 로드에 성공하고 연산 그래프가 컴파일되었습니다.&amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&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="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">Ort&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Exception&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cerr&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;[Error] 모델 로드 실패: &amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">what&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&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="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="kt">void&lt;/span> &lt;span class="nf">RunInference&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">float&lt;/span>&lt;span class="o">&amp;gt;&amp;amp;&lt;/span> &lt;span class="n">imageTensor&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">int64_t&lt;/span>&lt;span class="o">&amp;gt;&amp;amp;&lt;/span> &lt;span class="n">inputShape&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 3. 입력 텐서 버퍼 생성
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">Ort&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">MemoryInfo&lt;/span> &lt;span class="n">memoryInfo&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Ort&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">MemoryInfo&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">CreateCpu&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">OrtArenaAllocator&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">OrtMemTypeDefault&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Ort&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Value&lt;/span> &lt;span class="n">inputTensor&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Ort&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Value&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">CreateTensor&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">float&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">memoryInfo&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">const_cast&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">float&lt;/span>&lt;span class="o">*&amp;gt;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">imageTensor&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">()),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">imageTensor&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">inputShape&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">inputShape&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">size&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">// 4. 입출력 노드 이름 동적 획득
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">auto&lt;/span> &lt;span class="n">inputNamePtr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetInputNameAllocated&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">allocator&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">auto&lt;/span> &lt;span class="n">outputNamePtr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetOutputNameAllocated&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">allocator&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">const&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">inputNames&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">inputNamePtr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">const&lt;/span> &lt;span class="kt">char&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">outputNames&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">outputNamePtr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">()&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. 추론 실행 (DirectML을 통해 NPU/GPU로 오프로드됨)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;[Info] 추론 엔진 실행을 시작합니다...&amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">auto&lt;/span> &lt;span class="n">startTime&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">chrono&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">high_resolution_clock&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">now&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">auto&lt;/span> &lt;span class="n">outputTensors&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">session&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Ort&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">RunOptions&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="k">nullptr&lt;/span>&lt;span class="p">},&lt;/span> &lt;span class="n">inputNames&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">inputTensor&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">outputNames&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&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">auto&lt;/span> &lt;span class="n">endTime&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">chrono&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">high_resolution_clock&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">now&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">auto&lt;/span> &lt;span class="n">duration&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">chrono&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">duration_cast&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">chrono&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">milliseconds&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">endTime&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">startTime&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="c1">&lt;/span> &lt;span class="kt">float&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">outputData&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">outputTensors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">front&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">GetTensorMutableData&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">float&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">size_t&lt;/span> &lt;span class="n">outputSize&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">outputTensors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">front&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">GetTensorTypeAndShapeInfo&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">GetElementCount&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">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;[Result] 추론 완료: &amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">duration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34; ms&amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;[Result] 출력 텐서의 요소 수: &amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">outputSize&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ※ 이 후, 출력 텐서에 대해 NMS(Non-Maximum Suppression)나 바운딩 박스 그리기 처리를 구현합니다
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&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="kt">int&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 실행할 ONNX 모델의 경로
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">CustomVisionAIProcessor&lt;/span> &lt;span class="n">processor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">L&lt;/span>&lt;span class="s">&amp;#34;models/yolov8n_quantized.onnx&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">// 추론용 더미 이미지 데이터 (배치 크기 1 x 3 채널 x 640 x 640)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">int64_t&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">inputShape&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">640&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">640&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">float&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">dummyImage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">640&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">640&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">0.5f&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">processor&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">RunInference&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dummyImage&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">inputShape&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="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">exception&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cerr&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;프로그램이 비정상 종료되었습니다: &amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">what&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&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="k">return&lt;/span> &lt;span class="mi">0&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;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="61-c에서의-메모리-관리와-제로-카피-추론의-중요성">6.1 C++에서의 메모리 관리와 제로 카피 추론의 중요성
&lt;/h3>&lt;p>C++에서 DirectML을 사용하는 최대의 장점은 DirectX 12(DX12)와의 긴밀한 통합이 가능하다는 점입니다. 위의 코드는 교육적 관점에서 표준적인 CPU 메모리로부터의 데이터 복사를 포함하고 있지만, 실제 게임 엔진이나 영상 처리 애플리케이션에서는 DX12를 사용하여 이미지(텍스처)를 이미 GPU나 NPU의 메모리 공간에 유지하고 있는 경우가 많습니다.&lt;/p>
&lt;p>이 경우, &lt;code>OrtDmlApi&lt;/code> 의 고급 바인딩 기능을 이용하여 DX12의 리소스를 직접 ONNX Runtime의 텐서로 매핑하는 &amp;ldquo;&lt;strong>제로 카피 추론(Zero-Copy Inference)&lt;/strong>&amp;ldquo;을 구현할 수 있습니다. 이를 통해 PCIe 버스 간의 데이터 전송 오버헤드(앞서 언급한 대역폭 $BW$ 의 소비)가 완전히 사라져, 실시간 동영상 처리에서의 프레임 속도가 극적으로 향상됩니다.&lt;/p>
&lt;h2 id="7-성능-최적화와-모범-사례">7. 성능 최적화와 모범 사례
&lt;/h2>&lt;p>Windows AI API나 DirectML을 활용하여 최상급의 AI 애플리케이션을 개발할 때의 필수적인 최적화 전략을 아래에 정리합니다.&lt;/p>
&lt;h3 id="71-모델-양자화quantization와-olive-toolkit">7.1 모델 양자화(Quantization)와 Olive Toolkit
&lt;/h3>&lt;p>NPU의 진정한 힘을 발휘시키려면, AI 모델의 가중치와 활성화 값을 FP32(단정밀도 부동소수점)에서 INT8 또는 INT4로 **양자화(Quantization)**하는 것이 절대적인 조건입니다. NPU의 아키텍처는 정수 연산에 특화되어 있어, FP32와 비교해 INT8에서는 이론상 4배의 처리량과 대폭적인 전력 절감을 실현합니다.&lt;/p>
&lt;p>Microsoft가 제공하는 &lt;code>Olive (ONNX Live)&lt;/code> 툴체인을 사용하면 PyTorch 등의 모델을 Windows 환경에 맞게 자동 최적화할 수 있습니다. Olive는 Transformer 모델에 대한 특수한 어텐션 최적화나 하드웨어별 그래프 컴파일을 강력하게 지원합니다.&lt;/p>
&lt;h3 id="72-배치-처리-vs-대화형-스트리밍의-트레이드오프">7.2 배치 처리 vs 대화형 스트리밍의 트레이드오프
&lt;/h3>&lt;p>API 호출에 있어 여러 추론 요청을 묶어서 배치(Batch) 처리함으로써 NPU의 사용 효율(Compute Utilization)을 높일 수 있습니다. 그러나 챗봇과 같은 대화형 UI의 경우, 처리량보다도 첫 번째 토큰이 표시될 때까지의 시간(TTFT: Time To First Token)이 사용자 경험(UX)을 결정짓습니다.
따라서 대화형 UI에서는 배치 크기를 1로 설정하고, 스트리밍 생성을 우선하는 설계가 모범 사례가 됩니다.&lt;/p>
&lt;h3 id="73-백그라운드-작업과-os의-연동">7.3 백그라운드 작업과 OS의 연동
&lt;/h3>&lt;p>AI 추론은 로컬 전력과 시스템 리소스를 대량으로 소비합니다. Windows의 &lt;code>App Lifecycle API&lt;/code> 와 연동하여, 애플리케이션이 백그라운드로 전환될 때는 우선순위가 낮은 추론 작업을 일시 중단(Suspend)하거나 리소스 소비를 줄이는 구현이 요구됩니다.&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant User as "사용자"
participant App as "Windows 앱 (C#)"
participant API as "Microsoft.Windows.AI"
participant OS as "Windows Copilot Runtime"
participant ORT as "ONNX Runtime (DML)"
participant NPU as "NPU 하드웨어"
User->>App: "프롬프트 입력"
App->>API: "GenerateResponseStreamAsync()"
API->>OS: "추론 작업 디스패치"
OS->>ORT: "그래프 실행 요청"
ORT->>NPU: "DirectML을 통한 명령 목록 실행"
NPU-->>ORT: "계산 완료 (1 토큰 생성)"
ORT-->>OS: "텐서 결과"
OS-->>API: "디코딩된 텍스트"
API-->>App: "IAsyncEnumerable&lt;string> 청크"
App-->>User: "UI로의 실시간 문자 렌더링"
Note over ORT,NPU: "완료될 때까지 이 루프를 고속으로 반복"&lt;/div>
&lt;p>이 시퀀스 다이어그램은 UI 스레드를 전혀 차단하지 않고, 최하층의 NPU 하드웨어에서 애플리케이션의 프레젠테이션 계층까지 데이터가 흐르듯이 스트리밍되는 비동기 처리의 아름다움을 보여줍니다.&lt;/p>
&lt;h2 id="8-향후-전망과-windows-ai의-진화">8. 향후 전망과 Windows AI의 진화
&lt;/h2>&lt;p>&lt;code>Microsoft.Windows.AI&lt;/code> API와 Copilot Runtime은 현재 진행형으로 급속한 진화를 거듭하고 있습니다. 향후 개발자 대상 업데이트에서는 다음과 같은 패러다임 전환이 기대됩니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>멀티모달 API의 OS 네이티브 통합&lt;/strong>: 텍스트뿐만 아니라 음성, 이미지, 나아가 라이브 비디오 피드를 매끄럽게 동시 처리하여 크로스모달 AI 추론을 OS 수준에서 기본 제공.&lt;/li>
&lt;li>&lt;strong>RAG(Retrieval-Augmented Generation) 시스템 수준 지원&lt;/strong>: 로컬 PC 내의 개인 문서들이나 Windows Search의 인덱스와 AI 모델을 OS의 안전한 샌드박스 내에서 연동시켜, 사용자의 프라이버시를 완벽히 보호한 상태에서의 초고도 개인 AI 어시스턴트 구축.&lt;/li>
&lt;li>&lt;strong>NPU의 동적 리소스 스케일링&lt;/strong>: 여러 AI 애플리케이션(예를 들어, 백그라운드에서의 노이즈 캔슬링과 포그라운드에서의 코드 생성)이 동시에 가동될 때, Windows 커널 스케줄러가 NPU의 실행 컨텍스트를 동적으로 전환하여 QoS(Quality of Service)를 보장하는 메커니즘.&lt;/li>
&lt;/ul>
&lt;h2 id="9-결론-로컬-ai가-바꾸는-애플리케이션의-미래">9. 결론: 로컬 AI가 바꾸는 애플리케이션의 미래
&lt;/h2>&lt;p>Windows 11의 Copilot Runtime과 &lt;code>Microsoft.Windows.AI&lt;/code> API는 모든 Windows 개발자에게 &amp;lsquo;로컬 AI&amp;rsquo;라는 극히 강력한 무기를 가져다주었습니다. 이제 클라우드 API에 전적으로 의존할 필요가 없습니다. 지연 시간을 없애고 프라이버시를 굳건히 지키면서도, 오프라인에서 완벽하게 작동하는 차세대 AI 경험을 사용자에게 제공하는 것이 가능해졌습니다.&lt;/p>
&lt;p>본 기사에서 해설한 C#을 이용한 시스템 표준 언어 모델의 통합, 수리적인 성능 평가, 그리고 C++와 DirectML을 이용한 극한의 하드웨어 최적화 지식을 활용하여, 여러분의 손으로 차세대 &amp;ldquo;AI 네이티브&amp;rdquo; Windows 애플리케이션을 창조해 보세요. AI가 가져올 무한한 가능성은 여러분이 작성하는 코드의 바로 앞에 펼쳐져 있습니다.&lt;/p>
&lt;hr>
&lt;p>&lt;em>※ 주의사항: 이 기사는 2026년 9월 현재의 프리뷰 버전 API 및 최신 사양을 바탕으로 작성되었습니다. Windows 업데이트에 따라 API 사양이나 하드웨어 요구 사항이 변경될 수 있으므로, 구현 시에는 반드시 Microsoft Learn의 공식 문서를 함께 참조해 주시기 바랍니다.&lt;/em>&lt;/p></description></item><item><title>Python 필요 없음! C++만으로 AI 추론 엔진을 구축해 보았다</title><link>http://kenji.blog/ko/p/building-ai-inference-engine-cpp-only/</link><pubDate>Fri, 11 Sep 2026 17:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/building-ai-inference-engine-cpp-only/</guid><description>&lt;img src="http://kenji.blog/p/building-ai-inference-engine-cpp-only/img/eyecatch.jpg" alt="Featured image of post Python 필요 없음! C++만으로 AI 추론 엔진을 구축해 보았다" />&lt;h2 id="1-시작하며-왜-python을-버리고-c로-ai-추론-엔진을-만드는가">1. 시작하며: 왜 Python을 버리고 C++로 AI 추론 엔진을 만드는가?
&lt;/h2>&lt;p>현대 AI 개발에서 Python은 사실상의 표준(De facto standard)입니다. PyTorch나 TensorFlow 같은 강력한 프레임워크의 은혜 덕분에, 몇 줄의 코드만으로 복잡한 신경망을 구축하고 학습 및 추론시킬 수 있습니다. 하지만 이런 프레임워크의 이면에서는 C++이나 CUDA와 같은 로우 레벨(Low-level) 언어가 계산량이 많은 처리를 담당하고 있습니다. Python은 어디까지나 &amp;lsquo;접착제(Glue)&amp;rsquo; 역할을 할 뿐입니다.&lt;/p>
&lt;p>그렇다면 왜 굳이 Python을 배제하고 C++ 단독으로 AI 추론 엔진을 만들어야 할까요? 거기에는 몇 가지 강력한 이유가 있습니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>극한의 성능과 짧은 지연 시간&lt;/strong>: Python의 GIL(Global Interpreter Lock)이나 동적 타이핑에 의한 오버헤드를 완전히 제거할 수 있습니다. 특히 실시간성이 요구되는 시스템에서는 밀리초 단위의 지연이 치명적일 수 있습니다.&lt;/li>
&lt;li>&lt;strong>배포의 용이성&lt;/strong>: Python 환경(거대한 라이브러리군, 의존성 지옥)을 최종 사용자의 환경에 구축하는 것은 매우 어렵습니다. C++이라면 정적으로 링크된 단일 실행 바이너리(&lt;code>.exe&lt;/code>나 ELF 바이너리)를 배포하기만 하면 됩니다.&lt;/li>
&lt;li>&lt;strong>엣지 디바이스 대응&lt;/strong>: 스마트폰이나 임베디드 기기, 라즈베리 파이 같은 리소스 제약이 심한 환경에서 수 기가바이트의 메모리를 소비하는 Python 런타임을 구동할 여유는 없습니다.&lt;/li>
&lt;li>&lt;strong>하드웨어 직접 제어&lt;/strong>: 메모리 할당 타이밍, SIMD 명령어의 명시적 사용, GPU와의 메모리 전송 최적화 등 로우 레벨 제어는 C++에서만 가능합니다.&lt;/li>
&lt;/ol>
&lt;p>본 기사에서는 Georgi Gerganov 씨가 개발한 &amp;lsquo;GGML&amp;rsquo; 라이브러리의 아키텍처에서 많은 영감을 받아, C++만으로 대규모 언어 모델(LLM) 등을 구동하기 위한 추론 엔진을 처음부터 구축해 나가는 과정을 기술적인 깊은 곳까지 파고들어 해설합니다.&lt;/p>
&lt;hr>
&lt;h2 id="2-추론-엔진-아키텍처의-전체적인-모습">2. 추론 엔진 아키텍처의 전체적인 모습
&lt;/h2>&lt;p>AI의 추론 처리는 본질적으로 &amp;lsquo;거대한 행렬 계산의 연속&amp;rsquo;입니다. 이를 효율적으로 실행하기 위해, 추론 엔진은 다음과 같은 컴포넌트로 구성되어야 합니다.&lt;/p>
&lt;div class="mermaid">graph TD
A["입력 데이터 (토큰/이미지)"] --> B["텐서 관리"]
B --> C["계산 그래프 (DAG)"]
C --> D["메모리 아레나 &amp; 할당자"]
C --> E["스케줄러 &amp; 스레드 풀"]
E --> F["CPU 백엔드 (AVX2/ARM NEON)"]
E --> G["GPU 백엔드 (CUDA/Metal)"]
F --> H["출력 결과"]
G --> H&lt;/div>
&lt;ol>
&lt;li>&lt;strong>텐서(Tensor) 관리&lt;/strong>: 다차원 배열의 데이터 구조와 차원별 스트라이드(Stride)를 관리합니다.&lt;/li>
&lt;li>&lt;strong>계산 그래프(Computation Graph)&lt;/strong>: 신경망 각 층의 연산을 방향성 비순환 그래프(DAG)로 표현합니다.&lt;/li>
&lt;li>&lt;strong>메모리 아레나(Memory Arena)&lt;/strong>: 동적 메모리 확보(&lt;code>malloc&lt;/code>이나 &lt;code>new&lt;/code>)의 오버헤드를 피하기 위한, 사전 확보형 메모리 관리 메커니즘입니다.&lt;/li>
&lt;li>&lt;strong>백엔드(Backend)&lt;/strong>: CPU나 GPU 등, 특정 하드웨어에 최적화된 연산 구현(커널)입니다.&lt;/li>
&lt;/ol>
&lt;p>이들을 C++의 강력한 기능(템플릿, 포인터 연산, RAII 등)을 사용하여 조립해 나갑니다.&lt;/p>
&lt;hr>
&lt;h2 id="3-메모리-관리의-극의-메모리-아레나와-simd-정렬">3. 메모리 관리의 극의: 메모리 아레나와 SIMD 정렬
&lt;/h2>&lt;p>추론 엔진에서 메모리 관리는 성능과 직결되는 가장 중요한 요소 중 하나입니다. 추론 중, 특히 트랜스포머(Transformer) 모델의 각 층을 통과할 때 방대한 수의 중간 텐서가 생성됩니다. 이를 매번 표준 &lt;code>malloc&lt;/code>으로 할당하고 해제한다면, 힙의 단편화와 OS의 컨텍스트 스위칭으로 인해 치명적인 속도 저하를 초래합니다.&lt;/p>
&lt;p>그래서 &amp;lsquo;&lt;strong>메모리 아레나(Memory Arena)&lt;/strong>&amp;lsquo;라는 접근 방식을 채택합니다. 이는 추론 시작 시 필요한 최대 메모리 양을 계산(또는 미리 결정)하여 일괄적으로 할당하고, 포인터의 증가만으로 메모리를 잘라내어 사용하는 방법입니다.&lt;/p>
&lt;h3 id="31-정렬alignment의-중요성">3.1 정렬(Alignment)의 중요성
&lt;/h3>&lt;p>현대 CPU는 SIMD(Single Instruction, Multiple Data) 명령어를 지원합니다. Intel/AMD의 AVX2/AVX-512나 ARM의 NEON 등이 있습니다. 이 명령어들은 256비트(32바이트)나 512비트(64바이트)의 데이터를 한 번에 처리하지만, 처리 대상 데이터의 메모리가 특정 바이트 경계(보통 32바이트나 64바이트)에 정렬(Alignment)되어 있어야 합니다.&lt;/p>
&lt;p>다음은 정렬을 고려한 메모리 아레나의 C++ 구현 예시입니다.&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;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;cstdint&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;cstddef&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;stdexcept&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">struct&lt;/span> &lt;span class="nc">MemoryArena&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">size_t&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">size_t&lt;/span> &lt;span class="n">offset&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">uint8_t&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">data&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">MemoryArena&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">size_t&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">offset&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// POSIX 계열이라면 posix_memalign, Windows라면 _aligned_malloc을 사용
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="cp">#ifdef _WIN32
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span> &lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">static_cast&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">uint8_t&lt;/span>&lt;span class="o">*&amp;gt;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">_aligned_malloc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">64&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#else
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">posix_memalign&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">reinterpret_cast&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">void&lt;/span>&lt;span class="o">**&amp;gt;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="mi">64&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">bad_alloc&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="cp">#endif
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&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="o">~&lt;/span>&lt;span class="n">MemoryArena&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#ifdef _WIN32
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span> &lt;span class="n">_aligned_free&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#else
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span> &lt;span class="n">free&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#endif
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&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="kt">void&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="nf">allocate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">size_t&lt;/span> &lt;span class="n">bytes&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">size_t&lt;/span> &lt;span class="n">alignment&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">64&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&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="c1">&lt;/span> &lt;span class="n">size_t&lt;/span> &lt;span class="n">pad&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">alignment&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">offset&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">alignment&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">alignment&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">offset&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">pad&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">bytes&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">runtime_error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;OOM: MemoryArena out of memory&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">offset&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">pad&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">void&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">ptr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">data&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">offset&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">offset&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">bytes&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ptr&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="kt">void&lt;/span> &lt;span class="nf">reset&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">offset&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 메모리 해제는 포인터를 되돌리기만 하면 됨 (O(1))
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&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;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>이처럼 텐서 생성 시에는 반드시 이 아레나를 통해 메모리를 얻습니다. 추론의 각 단계(토큰 생성마다 등)가 끝날 때마다 &lt;code>reset()&lt;/code>을 호출하는 것만으로, 순식간에 메모리를 재사용할 수 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="4-텐서-데이터-구조와-스트라이드의-마법">4. 텐서 데이터 구조와 스트라이드의 마법
&lt;/h2>&lt;p>텐서는 스칼라, 벡터, 행렬을 일반화한 개념입니다. 구현에 있어 중요한 것은 실제 데이터가 메모리 상에 &lt;strong>1차원의 연속된 배열&lt;/strong>로 배치되어 있는 반면, 이를 다차원으로 해석하기 위한 &amp;lsquo;스트라이드(Stride)&amp;lsquo;라는 개념을 갖는다는 점입니다.&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;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">enum&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">DataType&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">FP32&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="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">INT8&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="c1">&lt;/span> &lt;span class="n">INT4&lt;/span> &lt;span class="c1">// 양자화용
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&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">struct&lt;/span> &lt;span class="nc">Tensor&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">n_dims&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="c1">&lt;/span> &lt;span class="kt">int64_t&lt;/span> &lt;span class="n">ne&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// 각 차원의 요소 수 (Number of Elements)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">size_t&lt;/span> &lt;span class="n">nb&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// 각 차원의 스트라이드 (Number of Bytes)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">DataType&lt;/span> &lt;span class="n">type&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="c1">&lt;/span> &lt;span class="kt">void&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">data&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="c1">&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="c1">&lt;/span> &lt;span class="k">enum&lt;/span> &lt;span class="nc">OpType&lt;/span> &lt;span class="n">op&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">src0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">src1&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;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>스트라이드 &lt;code>nb[i]&lt;/code>는 차원 &lt;code>i&lt;/code>에서 인접한 요소 간의 메모리 상 바이트 거리를 나타냅니다.
예를 들어 요소 수 $M \times N$인 행렬(FP32, 1요소 4바이트)이 Row-Major(행 우선)로 저장된 경우, 스트라이드는 다음과 같습니다.&lt;/p>
&lt;ul>
&lt;li>&lt;code>nb[0]&lt;/code> = 4 (바이트) : 열 방향 이동&lt;/li>
&lt;li>&lt;code>nb[1]&lt;/code> = $N \times 4$ (바이트) : 행 방향 이동&lt;/li>
&lt;/ul>
&lt;p>이를 이용하면 메모리 복사 없이 &amp;lsquo;전치(Transpose)&amp;lsquo;나 &amp;lsquo;뷰(View)&amp;rsquo; 같은 연산을 스트라이드 값의 교환만으로 구현할 수 있습니다. 매우 우아하고 빠릅니다.&lt;/p>
&lt;hr>
&lt;h2 id="5-계산-그래프dag-구축과-지연-평가">5. 계산 그래프(DAG) 구축과 지연 평가
&lt;/h2>&lt;p>PyTorch 등과 마찬가지로, 우리의 추론 엔진도 &amp;lsquo;Define-by-Run&amp;rsquo;에 가까운 지연 평가(Lazy Evaluation)를 채택합니다. 즉, 연산 함수를 호출한 시점에서는 계산을 수행하지 않고, 그래프(노드 간의 의존 관계)만 구축합니다.&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;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="nf">tensor_add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MemoryArena&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">arena&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">out&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_tensor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">arena&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">type&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">n_dims&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">ne&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">out&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">op&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">OpType&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">ADD&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">out&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">src0&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">out&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">src1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">out&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="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="nf">tensor_mul_mat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MemoryArena&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">arena&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// b는 전치되어 있는 경우가 많음
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">int64_t&lt;/span> &lt;span class="n">ne&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">ne&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">ne&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">out&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_tensor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">arena&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">type&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ne&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">out&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">op&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">OpType&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">MUL_MAT&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">out&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">src0&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">out&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">src1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">out&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;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>추론 처리의 흐름은 다음과 같습니다.&lt;/p>
&lt;div class="mermaid">graph LR
A["텐서 정의"] --> B["연산을 통한 그래프 구축"]
B --> C["위상 정렬"]
C --> D["출력을 위한 메모리 할당"]
D --> E["순서대로 노드 실행"]&lt;/div>
&lt;p>그래프를 평가할 때(순전파 패스), 위상 정렬을 사용하여 의존 관계가 없는 노드부터 순서대로 처리를 실행합니다. 추론만 한다면 역전파용 기울기를 유지할 필요가 없으므로 메모리 관리가 매우 단순해집니다.&lt;/p>
&lt;hr>
&lt;h2 id="6-수학과-최적화의-핵심-행렬곱-gemm">6. 수학과 최적화의 핵심: 행렬곱 (GEMM)
&lt;/h2>&lt;p>AI 추론 연산량의 90% 이상은 행렬 곱셈(GEMM: General Matrix Multiply)에 소요됩니다. 트랜스포머 모델의 핵심인 어텐션(Attention) 메커니즘도 피드포워드 신경망(FFN)도 궁극적으로는 거대한 행렬곱입니다.&lt;/p>
&lt;p>두 행렬 $A$ (크기 $M \times K$)와 $B$ (크기 $K \times N$)의 곱 $C = A B$ (크기 $M \times N$)은 수식으로 표현하면 다음과 같습니다.&lt;/p>
$$
C_{i,j} = \sum_{k=0}^{K-1} A_{i,k} \cdot B_{k,j}
$$
&lt;p>이를 단순한 삼중 루프로 구현하면 캐시 미스가 빈발하여 성능이 전혀 나오지 않습니다.&lt;/p>
&lt;h3 id="61-cpu에서의-캐시-블로킹과-simd-최적화">6.1 CPU에서의 캐시 블로킹과 SIMD 최적화
&lt;/h3>&lt;p>CPU에서 GEMM을 가속하기 위한 기본 전략은 다음과 같습니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>루프 타일링(캐시 블로킹)&lt;/strong>: L1/L2 캐시에 들어갈 수 있는 작은 블록으로 행렬을 분할하여 계산합니다.&lt;/li>
&lt;li>&lt;strong>데이터 팩킹&lt;/strong>: 메모리 접근 패턴이 연속적이 되도록 내부적으로 데이터를 재배열합니다.&lt;/li>
&lt;li>&lt;strong>SIMD 활용&lt;/strong>: AVX-512의 &lt;code>_mm512_fmadd_ps&lt;/code>와 같은 FMA(Fused Multiply-Add) 명령어를 사용하여 한 클록 사이클에 다수의 곱셈 및 덧셈 연산을 처리합니다.&lt;/li>
&lt;/ol>
&lt;p>C++과 SIMD Intrinsics를 사용한 단순화된 벡터 내적(Dot Product)의 예를 보여드립니다.&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;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;immintrin.h&amp;gt;&lt;/span>&lt;span class="cp"> &lt;/span>&lt;span class="c1">// AVX 명령어용
&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="c1">// AVX2를 활용한 FP32 고속 내적
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">float&lt;/span> &lt;span class="nf">dot_product_avx2&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="kt">float&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="kt">float&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">__m256&lt;/span> &lt;span class="n">sum256&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_mm256_setzero_ps&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&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">// 8개 요소씩 한 번에 처리 (256비트 = 32바이트 = 8 * 4바이트)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="p">(;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">8&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="mi">8&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">__m256&lt;/span> &lt;span class="n">va&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_mm256_loadu_ps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">__m256&lt;/span> &lt;span class="n">vb&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_mm256_loadu_ps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// FMA 명령어: sum256 = va * vb + sum256
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">sum256&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_mm256_fmadd_ps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">va&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">vb&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">sum256&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">// SIMD 레지스터 내의 값을 수평 가산
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">float&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">8&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_mm256_storeu_ps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">sum256&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">float&lt;/span> &lt;span class="n">dot&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">+&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">6&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">7&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="c1">&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="p">(;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="o">++&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dot&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&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="k">return&lt;/span> &lt;span class="n">dot&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;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>이 작은工夫(고안)만으로도 단순 구현에 비해 몇 배에서 십여 배의 속도 향상을 얻을 수 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="7-하드웨어의-장벽을-넘다-cuda-및-metal-백엔드-통합">7. 하드웨어의 장벽을 넘다: CUDA 및 Metal 백엔드 통합
&lt;/h2>&lt;p>순수 C++ 구현만으로도 CPU 상에서는 어느 정도 동작하지만, LLM과 같은 거대 모델을 실용적인 속도(예: 1초당 20토큰 이상 생성)로 구동하기 위해서는 GPU의 병렬 계산 능력이 필수적입니다. 따라서 우리 엔진에 백엔드 추상화 레이어를 도입합니다.&lt;/p>
&lt;h3 id="71-백엔드-추상화">7.1 백엔드 추상화
&lt;/h3>&lt;p>C++의 다형성을 이용하여 연산 실행기(Executor)를 전환할 수 있도록 합니다.&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;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Backend&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">virtual&lt;/span> &lt;span class="o">~&lt;/span>&lt;span class="n">Backend&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">default&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">virtual&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">alloc_buffer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">t&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">virtual&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">free_buffer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">t&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">virtual&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">copy_to_device&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">t&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">virtual&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">copy_to_host&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">t&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&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="c1">&lt;/span> &lt;span class="k">virtual&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">compute_add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">src0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">src1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">dst&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">virtual&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">compute_mul_mat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">src0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">src1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">dst&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&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;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="72-nvidia-cuda-백엔드-구현">7.2 NVIDIA CUDA 백엔드 구현
&lt;/h3>&lt;p>NVIDIA의 GPU를 활용하기 위해, CUDA C++ 확장을 사용하여 백엔드를 구현합니다. 독자적인 커널을 작성하는 것도 가능하지만, 행렬곱에 관해서는 NVIDIA가 제공하는 최고 수준의 라이브러리인 &amp;lsquo;cuBLAS&amp;rsquo;를 활용하는 것이 최선의 선택입니다.&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;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;cublas_v2.h&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;cuda_runtime.h&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CUDABackend&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="k">public&lt;/span> &lt;span class="n">Backend&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">private&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cublasHandle_t&lt;/span> &lt;span class="n">handle&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">public&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">CUDABackend&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cublasCreate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">handle&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="o">~&lt;/span>&lt;span class="n">CUDABackend&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cublasDestroy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">handle&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="kt">void&lt;/span> &lt;span class="nf">compute_mul_mat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">src0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">src1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Tensor&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">dst&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">override&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// CUDA는 기본이 Column-Major이므로 파라미터에 주의가 필요함
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="kt">float&lt;/span> &lt;span class="n">alpha&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">1.0f&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">const&lt;/span> &lt;span class="kt">float&lt;/span> &lt;span class="n">beta&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">0.0f&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="kt">int&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">src0&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">ne&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">k&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">src0&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">ne&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">src1&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">ne&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// src1은 전치되어 있다는 전제
&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">cublasSgemm&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">handle&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CUBLAS_OP_T&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CUBLAS_OP_N&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">m&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">k&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">alpha&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="k">const&lt;/span> &lt;span class="kt">float&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">src0&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">k&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="k">const&lt;/span> &lt;span class="kt">float&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">src1&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">k&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">beta&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="kt">float&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">dst&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cudaDeviceSynchronize&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="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>CUDA 메모리와 호스트(CPU) 메모리 간의 데이터 전송(&lt;code>cudaMemcpy&lt;/code>)은 매우 무겁기 때문에, 추론 중에는 가급적 모든 가중치(웨이트 텐서)와 중간 텐서를 VRAM 상에 계속 유지하는 설계가 중요합니다.&lt;/p>
&lt;h3 id="73-apple-silicon-metal-백엔드">7.3 Apple Silicon (Metal) 백엔드
&lt;/h3>&lt;p>최근 Mac의 M1/M2/M3 칩(Apple Silicon)은 AI 추론기로서 매우 우수합니다. 그 이유는 &amp;lsquo;통합 메모리(Unified Memory)&amp;lsquo;에 있습니다. CPU와 GPU가 동일한 메모리 영역을 공유하고 있기 때문에, 앞서 언급한 CUDA와 같은 PCIe 버스를 통한 고비용의 호스트-디바이스 간 메모리 전송이 전혀 필요하지 않습니다.&lt;/p>
&lt;p>C++에서 Metal을 호출하려면 Objective-C++(&lt;code>.mm&lt;/code> 파일)을 브리지로 사용하거나, &lt;code>metal-cpp&lt;/code> 라이브러리를 이용합니다.
Metal의 Compute Shader(&lt;code>.metal&lt;/code> 파일에 C++와 비슷하게 작성)를 사용하여 커널을 작성합니다.&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;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Metal 셰이더 (kernel.metal)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;metal_stdlib&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>&lt;span class="k">using&lt;/span> &lt;span class="k">namespace&lt;/span> &lt;span class="n">metal&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">kernel&lt;/span> &lt;span class="kt">void&lt;/span> &lt;span class="nf">mul_mat_kernel&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&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="kt">float&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">A&lt;/span> &lt;span class="p">[[&lt;/span>&lt;span class="n">buffer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&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&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="kt">float&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">B&lt;/span> &lt;span class="na">[[buffer(1)]]&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&lt;/span> &lt;span class="kt">float&lt;/span>&lt;span class="o">*&lt;/span> &lt;span class="n">C&lt;/span> &lt;span class="na">[[buffer(2)]]&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">constant&lt;/span> &lt;span class="n">uint3&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">dims&lt;/span> &lt;span class="na">[[buffer(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">uint2&lt;/span> &lt;span class="n">gid&lt;/span> &lt;span class="na">[[thread_position_in_grid]]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">uint&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">dims&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">uint&lt;/span> &lt;span class="n">k&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">dims&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">uint&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">dims&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">z&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">uint&lt;/span> &lt;span class="n">row&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">gid&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">uint&lt;/span> &lt;span class="n">col&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">gid&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&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="p">(&lt;/span>&lt;span class="n">row&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">col&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">float&lt;/span> &lt;span class="n">sum&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">0.0&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="p">(&lt;/span>&lt;span class="n">uint&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">k&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="o">++&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sum&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">A&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">row&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">k&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">B&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">col&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="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">C&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">row&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">col&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">sum&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="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Apple Silicon 환경에서는 MPS(Metal Performance Shaders)라는 행렬곱 전용 최적화 라이브러리도 제공하고 있으므로, 실제 운용 시에는 이를 활용하여 경이로운 추론 속도를 달성할 수 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="8-트랜스포머-모델-특유의-처리-어텐션attention과-kv-캐시">8. 트랜스포머 모델 특유의 처리: 어텐션(Attention)과 KV 캐시
&lt;/h2>&lt;p>LLaMA 2/3이나 GPT와 같은 최첨단 LLM은 트랜스포머 아키텍처에 기반을 두고 있습니다. 이를 C++로 구현하기 위해서는 다음 수식으로 표현되는 &amp;lsquo;Scaled Dot-Product Attention&amp;rsquo;의 구축이 필수입니다.&lt;/p>
$$
\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V
$$
&lt;p>또한, 자기회귀형(Autoregressive) 토큰 생성에서는 과거 토큰의 계산 결과(Key와 Value)를 유지해 두어야 합니다. 이를 &amp;lsquo;&lt;strong>KV 캐시(Key-Value Cache)&lt;/strong>&amp;lsquo;라고 부릅니다.&lt;/p>
&lt;div class="mermaid">graph TD
T["현재 토큰"] --> Q["쿼리(Query)"]
T --> K["키(Key)"]
T --> V["값(Value)"]
K --> KCache["KV 캐시에 추가"]
V --> VCache["KV 캐시에 추가"]
Q --> Dot1["Q * K_Cache^T"]
KCache --> Dot1
Dot1 --> Scale["스케일링 (1/sqrt(d))"]
Scale --> Softmax["소프트맥스(Softmax)"]
Softmax --> Dot2["SoftmaxOut * V_Cache"]
VCache --> Dot2
Dot2 --> Out["컨텍스트 벡터"]&lt;/div>
&lt;p>KV 캐시의 메모리 할당도, 사전에 최대 컨텍스트 길이(예를 들어 4096이나 8192 토큰)만큼의 메모리 공간을 아레나에 확보해 두어 링 버퍼처럼 운용합니다. 이를 통해 생성 단계마다 재할당되는 것을 방지할 수 있습니다.&lt;/p>
&lt;p>또한 위치 인코딩(Positional Encoding)에는 최근 주류가 된 &amp;lsquo;RoPE(Rotary Position Embedding)&amp;lsquo;를 구현합니다. 이는 복소 공간에서의 회전 벡터로 위치 정보를 임베딩하는 기법으로, C++에서의 &lt;code>sin&lt;/code> 및 &lt;code>cos&lt;/code> 함수 호출 최적화(룩업 테이블 등)가 성능의 열쇠를 쥐고 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="9-모델-양자화quantization를-통한-극한의-최적화">9. 모델 양자화(Quantization)를 통한 극한의 최적화
&lt;/h2>&lt;p>대규모 모델(예: 70억 파라미터의 LLaMA 모델)을 FP32(32비트 부동 소수점) 그대로 읽어 들이면 가중치만으로 약 28GB의 메모리(VRAM)를 소비합니다. 여기에 KV 캐시와 추론용 버퍼를 포함하면 30GB를 훌쩍 넘어 일반적인 소비자용 GPU에서는 실행이 불가능합니다.&lt;/p>
&lt;p>따라서 필수가 되는 것이 &amp;lsquo;&lt;strong>양자화(Quantization)&lt;/strong>&amp;lsquo;입니다. 이는 GGML 포맷의 진면목이기도 합니다.&lt;/p>
&lt;p>양자화란 가중치의 정밀도를 의도적으로 낮추는 기술입니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>FP16 (16-bit)&lt;/strong>: 크기 절반 감소. 정밀도 저하 거의 없음.&lt;/li>
&lt;li>&lt;strong>INT8 (8-bit)&lt;/strong>: 크기 1/4 감소. 약간의 저하.&lt;/li>
&lt;li>&lt;strong>INT4 (4-bit)&lt;/strong>: 크기 1/8 감소. 독자적인 블로킹과 스케일링 팩터를 사용하면 실용적인 추론 가능.&lt;/li>
&lt;/ul>
&lt;p>추론 엔진 측에서는 메모리에서 INT4(또는 INT8)로 압축된 가중치를 읽어내어, &lt;strong>CPU나 GPU의 레지스터에 로드한 직후에 FP16 또는 FP32로 전개(Dequantize)하여 계산&lt;/strong>을 수행합니다.&lt;/p>
&lt;p>놀랍게도 계산량을 늘려서라도 메모리에서 읽어들이는 데이터 양을 줄이는 쪽이 더 빠릅니다. 이는 현대 하드웨어에서 추론 작업의 병목 현상이 &amp;lsquo;계산 능력(Compute Bound)&amp;lsquo;이 아니라 &amp;lsquo;&lt;strong>메모리 대역폭(Memory Bandwidth Bound)&lt;/strong>&amp;lsquo;에 있기 때문입니다. INT4 양자화를 적용한 C++ 구현 엔진이라면, 8GB VRAM을 가진 MacBook Air 등에서도 쾌적하게 로컬 LLM을 구동할 수 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="10-성능-튜닝-numa-아키텍처와-스레드-풀">10. 성능 튜닝: NUMA 아키텍처와 스레드 풀
&lt;/h2>&lt;p>CPU를 이용한 추론을 할 경우 멀티스레딩은 필수입니다. 하지만 단순히 &lt;code>std::thread&lt;/code>를 많이 띄우기만 해서는 최적이라 할 수 없습니다.&lt;/p>
&lt;p>현대의 멀티 소켓 서버나 Ryzen Threadripper와 같은 하이엔드 CPU에서는 &lt;strong>NUMA(Non-Uniform Memory Access)&lt;/strong> 아키텍처가 채택되어 있습니다. 어떤 CPU 코어에서 물리적으로 가까운 메모리(로컬 메모리)로의 접근은 빠르지만, 다른 프로세서에 연결된 메모리에 접근하는 것은 극단적으로 느려집니다.&lt;/p>
&lt;p>고도화된 C++ 추론 엔진에서는 다음과 같은 기술을 구사합니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>스레드 피닝(Thread Pinning)&lt;/strong>: 각 스레드를 특정 CPU 코어에 고정(Affinity 설정)하여, 컨텍스트 스위치에 의한 캐시 무효화를 방지합니다.&lt;/li>
&lt;li>&lt;strong>NUMA 인식 할당(NUMA-aware Allocation)&lt;/strong>: 데이터를 처리하는 스레드와 동일한 NUMA 노드 상에 메모리를 확보합니다.&lt;/li>
&lt;li>&lt;strong>워크 스틸링(Work-stealing) 기반 스레드 풀&lt;/strong>: 계산 그래프의 각 노드를 작은 태스크로 분할하고, 유휴 스레드가 자동으로 태스크를 훔쳐서(steal) 실행하는 효율적인 스케줄러를 구현합니다.&lt;/li>
&lt;/ol>
&lt;p>이들을 구사함으로써 CPU 사용률을 100% 부근에 딱 붙여 이론치에 가까운 처리량을 뽑아낼 수 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="11-정리-c의-근육으로-ai를-구동하는-즐거움">11. 정리: C++의 &amp;lsquo;근육&amp;rsquo;으로 AI를 구동하는 즐거움
&lt;/h2>&lt;p>Python은 확실히 편리합니다. 연구 개발이나 프로토타이핑에 있어 그 생산성을 능가할 언어는 없습니다. 하지만 완성된 모델을 &amp;lsquo;현실 세계에서, 효율적으로, 모든 디바이스에서 구동한다&amp;rsquo;는 단계로 넘어가는 순간 C++이 나설 차례입니다.&lt;/p>
&lt;p>메모리의 바이트 배열을 직접 조작하고, SIMD 명령어로 레지스터를 한계까지 몰아붙이며, GPU의 VRAM 대역폭과 씨름하며 만들어낸 추론 엔진이, 콘솔 상에 연이어 자연스러운 한국어(또는 일본어 등) 텍스트(토큰)를 생성해 나가는 모습을 보았을 때의 성취감은, Python 프레임워크에서 &lt;code>model.generate()&lt;/code>를 호출했을 때는 결코 얻을 수 없는 &amp;lsquo;엔지니어로서의 순수한 기쁨&amp;rsquo;이 있습니다.&lt;/p>
&lt;p>&amp;lsquo;블랙박스&amp;rsquo;가 되기 쉬운 AI 기술이지만, 텐서 연산부터 메모리 할당에 이르기까지 모든 것을 내 손으로 C++로 작성함으로써 LLM이 어떻게 &amp;lsquo;생각&amp;rsquo;하는지 그 진정한 메커니즘을 깊이 이해할 수 있습니다.&lt;/p>
&lt;p>만약 여러분이 C++에 대한 기초 지식이 있고 현재의 AI 기술에 강한 흥미가 있다면, 꼭 직접 추론 엔진 개발에 도전해 보시기 바랍니다. GGML이나 llama.cpp의 소스 코드는 최고의 살아있는 교과서가 될 것입니다.&lt;/p>
&lt;p>&lt;strong>자, Python의 무거운 런타임을 버리고 C++의 근육으로 최첨단 AI를 달리게 합시다!&lt;/strong>&lt;/p></description></item><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>AI 시대의 프로그래머는 어떻게 살아남아야 하는가?</title><link>http://kenji.blog/ko/p/how-programmers-survive-in-ai-era/</link><pubDate>Fri, 11 Sep 2026 15:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/how-programmers-survive-in-ai-era/</guid><description>&lt;img src="http://kenji.blog/p/how-programmers-survive-in-ai-era/img/eyecatch.jpg" alt="Featured image of post AI 시대의 프로그래머는 어떻게 살아남아야 하는가?" />&lt;h1 id="ai-시대의-프로그래머는-어떻게-살아남아야-하는가-코딩의-종말과-새로운-엔지니어링의-서막">AI 시대의 프로그래머는 어떻게 살아남아야 하는가? 코딩의 종말과 새로운 엔지니어링의 서막
&lt;/h1>&lt;p>2026년 현재, 소프트웨어 개발 현장은 그 어느 때보다 격변기를 맞이하고 있다. 불과 몇 년 전만 해도 &amp;lsquo;AI가 코드를 작성한다&amp;rsquo;는 개념은 기껏해야 보일러플레이트(상용구 코드) 생성이나 함수의 자동 완성 같은 프로그래머의 &amp;lsquo;보조 도구&amp;rsquo; 역할에 머물러 있었다. 하지만 대규모 언어 모델(LLM)의 경이적인 진화로 인해 상황은 근본적으로 뒤집혔다. 현대의 AI는 단순한 &amp;lsquo;똑똑한 타자기&amp;rsquo;가 아니라, 요구사항 정의서를 주면 프론트엔드부터 백엔드의 로직, 데이터베이스의 스키마 설계, 심지어 CI/CD 파이프라인 구축에 이르기까지 시스템 전체를 순식간에, 그리고 자율적으로 조립하는 능력을 갖춘 &amp;lsquo;자율형 주니어 엔지니어&amp;rsquo;로 변모했다.&lt;/p>
&lt;p>이러한 시대에 우리 &amp;lsquo;프로그래머&amp;rsquo;나 &amp;lsquo;소프트웨어 엔지니어&amp;rsquo;는 어떻게 살아남아야 할까? &amp;lsquo;코드를 작성한다&amp;rsquo;는 행위 자체의 경제적 가치가 급속히 디플레이션되는 가운데, 단지 특정 프로그래밍 언어의 문법(신택스)을 알고 특정 프레임워크의 API에 정통할 뿐인 &amp;lsquo;코더&amp;rsquo;는 급속히 시장에서 도태되고 있다.&lt;/p>
&lt;p>본고에서는 AI 시대에 프로그래머의 생존 전략에 대해 기술적, 수학적, 그리고 철학적 관점에서 극히 상세하게 고찰해 본다. 이는 단순한 커리어론이 아니라, 소프트웨어 엔지니어링이라는 학문 자체의 재정의이다.&lt;/p>
&lt;hr>
&lt;h2 id="1-추상화abstraction의-역사와-프로그래밍의-재정의">1. 추상화(Abstraction)의 역사와 &amp;lsquo;프로그래밍&amp;rsquo;의 재정의
&lt;/h2>&lt;p>소프트웨어 엔지니어링의 역사를 되돌아보면, 그것은 항상 &amp;lsquo;추상화(Abstraction)&amp;lsquo;의 역사였음을 알 수 있다. 우리는 항상 더 인간에 가까운 언어로 더 복잡한 시스템을 기술하기 위한 레이어를 구축해 왔다.&lt;/p>
&lt;p>초기 컴퓨터 과학자들은 천공 카드를 사용하여 물리적인 하드웨어의 스위치를 직접 조작하고 기계어(0과 1의 나열)로 컴퓨터에 지시를 내렸다. 그 후 어셈블리어(Assembly Language)가 등장하여 인간이 이해하기 쉬운 니모닉(Mnemonic)으로 하드웨어를 조작할 수 있게 되었다. 시대가 더 지나 C언어나 Fortran 같은 고급 언어가 등장하여 메모리 관리나 CPU의 레지스터와 같은 하드웨어의 복잡한 세부 사항을 캡슐화하는 데 성공했다. 이어서 등장한 Java, Python, Ruby, TypeScript 등의 모던한 언어로 인해 프로그래머는 &amp;lsquo;컴퓨터를 어떻게 움직일 것인가(How)&amp;lsquo;가 아니라 &amp;lsquo;컴퓨터에게 무엇을 시킬 것인가(What)&amp;lsquo;에 더욱 집중할 수 있게 되었다.&lt;/p>
&lt;p>AI(LLM)의 등장은 이 추상화의 역사에 있어서 가장 최신이자 최대의 패러다임 시프트이다. 프로그래밍 언어의 진화가 &amp;lsquo;하드웨어의 은닉&amp;rsquo;이었다고 한다면, LLM의 진화는 &amp;lsquo;신택스(문법)의 은닉&amp;rsquo;이다.&lt;/p>
&lt;div class="mermaid">graph TD
A["기계어 / 천공 카드 (1940년대)"] --> B["어셈블리어 (1950년대)"]
B --> C["컴파일형 고급 언어 (1970년대)"]
C --> D["매니지드 / 스크립트 언어 (1990년대)"]
D --> E["LLM을 통한 자연어 (2020년대)"]
E --> F["자율형 AI 에이전트 (2026-)"]
style E fill:#f9f,stroke:#333,stroke-width:2px
style F fill:#fbb,stroke:#333,stroke-width:2px&lt;/div>
&lt;p>더 이상 개발자가 메모리 누수를 걱정하며 포인터를 조작하거나, JSON 파싱 처리를 위한 정형 코드를 수백 줄씩 쓰는 시대는 끝났다. 자연어(한국어나 영어)라는, 인류에게 가장 추상도가 높은 언어를 사용하여 시스템을 정의하는 것이 2026년의 &amp;lsquo;프로그래밍&amp;rsquo; 표준이 되었다.&lt;/p>
&lt;hr>
&lt;h2 id="2-생산성의-수학적-모델-기하급수적-성장의-파도에-올라타라">2. 생산성의 수학적 모델: 기하급수적 성장의 파도에 올라타라
&lt;/h2>&lt;p>AI가 가져오는 생산성의 향상을 수학적 모델을 사용하여 정량적으로 평가해 보자.
기존 소프트웨어 개발에서 개인의 생산성 $P_{traditional}$은 개인의 스킬 수준 $S$, 도메인 경험 $E$, 그리고 도구의 효율성 $T$의 선형적인 조합으로 모델링할 수 있었다.&lt;/p>
$$ P_{traditional} = c_1 \cdot S + c_2 \cdot E + c_3 \cdot T $$
&lt;p>하지만 AI를 활용한 현대의 개발에 있어서는, AI의 능력 $A(t)$가 인간의 능력을 증폭시키는 &amp;lsquo;강력한 레버리지(Multiplier)&amp;lsquo;로서 작용한다. AI의 능력은 시간의 경과 $t$와 함께 기하급수적으로 성장(무어의 법칙의 AI 버전)하기 때문에, AI 시대의 생산성 $P_{AI}(t)$는 다음과 같은 방정식으로 나타낼 수 있다.&lt;/p>
$$ P_{AI}(t) = \alpha \cdot S_{core} \cdot e^{\beta \cdot A(t)} $$
&lt;p>여기서 각 변수는 다음을 의미한다.&lt;/p>
&lt;ul>
&lt;li>$\alpha$: 기초가 되는 인간의 생산성 계수&lt;/li>
&lt;li>$S_{core}$: AI로 대체되지 않는 &amp;lsquo;인간의 핵심 스킬&amp;rsquo;(아키텍처 설계, 비즈니스 요구사항 이해, 윤리적 판단 등)&lt;/li>
&lt;li>$A(t)$: 시간 $t$에서의 AI 모델의 절대적인 능력(파라미터 수, 컨텍스트 창, 추론 능력)&lt;/li>
&lt;li>$\beta$: AI 도구를 얼마나 효과적으로 이끌어낼 수 있는가(프롬프트 엔지니어링의 질이나 AI와의 협업 워크플로우의 세련도)를 나타내는 계수&lt;/li>
&lt;/ul>
&lt;p>이 수식에서 도출되는 중요한 통찰은, &lt;strong>$A(t)$가 기하급수적으로 증대하는 세계에서는 단순한 타이핑 속도나 특정 언어의 암기 같은 기존의 스킬이 전체 생산성에 미치는 영향이 극히 작아진다&lt;/strong>는 것이다. 대신, 기하급수적인 AI 성장에 편승하기 위한 계수 $\beta$와 AI가 커버할 수 없는 영역인 $S_{core}$가 엔지니어의 시장 가치를 결정짓는 지배적인 요인이 된다.&lt;/p>
&lt;hr>
&lt;h2 id="3-작업의-자동화-확률probability-of-automation">3. 작업의 자동화 확률(Probability of Automation)
&lt;/h2>&lt;p>그렇다면 어떤 작업이 자동화되고 어떤 작업이 인간의 손에 남게 될 것인가?
어떤 작업 $T$가 AI에 의해 완전히 자동화될 확률 $P_{auto}(T)$는 다음과 같이 정식화할 수 있다.&lt;/p>
$$ P_{auto}(T) = 1 - \exp\left(-\lambda \cdot \frac{\text{Predictability}(T)}{\text{Complexity}(T) \times \text{Context Dependency}(T)}\right) $$
&lt;ul>
&lt;li>$\text{Predictability}(T)$: 작업의 예측 가능성(과거의 데이터에 패턴이 얼마나 존재하는가)&lt;/li>
&lt;li>$\text{Complexity}(T)$: 작업의 복잡성&lt;/li>
&lt;li>$\text{Context Dependency}(T)$: 작업이 의존하는 &amp;lsquo;암묵적인 컨텍스트(도메인 고유의 지식이나 인간관계)&amp;lsquo;의 강도&lt;/li>
&lt;li>$\lambda$: AI의 기술 진보율&lt;/li>
&lt;/ul>
&lt;p>API 라우팅 처리를 작성하거나 단순한 CRUD 화면을 만드는 등의 예측 가능성이 높고 컨텍스트 의존성이 낮은 작업은 $P_{auto} \approx 1$이 되어 거의 완전히 자동화된다. 반면, &amp;lsquo;기존의 레거시 시스템과 새로운 마이크로서비스를 어떻게 안전하게 통합할 것인가&amp;rsquo;나 &amp;lsquo;법무 부서의 요구를 충족하면서도 사용자 경험을 해치지 않는 인증 플로우를 어떻게 설계할 것인가&amp;rsquo;와 같이 컨텍스트 의존성이 극히 높은 작업은 자동화하기 어렵다.&lt;/p>
&lt;hr>
&lt;h2 id="4-신택스문법에서-아키텍처구조로의-회귀">4. 신택스(문법)에서 아키텍처(구조)로의 회귀
&lt;/h2>&lt;p>AI가 잘하는 것과 인간이 잘하는 것을 명확하게 분리하는 것이 생존의 절대 조건이 된다.&lt;/p>
&lt;div class="mermaid">graph LR
Sub1["AI가 뛰어난 영역"]
Sub2["인간이 뛰어난 영역"]
A["사양서로부터의 코드 생성"] --> Sub1
B["문법 오류 및 버그 수정"] --> Sub1
C["보일러플레이트 / 테스트 생성"] --> Sub1
D["로그 분석 및 패턴 매칭"] --> Sub1
E["시스템 아키텍처 설계"] --> Sub2
F["모호한 요구사항 해결"] --> Sub2
G["팀 간의 협상"] --> Sub2
H["윤리적 판단 / 책임"] --> Sub2&lt;/div>
&lt;p>AI는 &amp;lsquo;국소적인 최적화&amp;rsquo;에 있어 인간을 능가한다. 하나의 함수, 하나의 클래스, 또는 단일 모듈을 기술하는 속도와 정확성에 있어서 인간은 승산이 없다. 하지만 AI는 &amp;lsquo;전역적인 최적화&amp;rsquo;나 &amp;lsquo;컨텍스트의 결여(Missing Context)&amp;lsquo;에 대해서는 매우 취약하다.&lt;/p>
&lt;p>앞으로의 프로그래머는 &amp;lsquo;코드를 작성하는 노동자&amp;rsquo;에서 &amp;lsquo;AI가 생성한 무수한 컴포넌트를 오케스트레이션하는 아키텍트&amp;rsquo;로 역할을 바꿔야 한다. 시스템 전체를 조감하고, 마이크로서비스의 경계를 어디에 그을지, CAP 정리에서의 가용성과 일관성의 트레이드오프를 비즈니스 문맥에 맞춰 어떻게 해결할지, 기술 부채를 어떻게 제어할지. 이들은 전체 그림과 비즈니스 목표를 이해하고 있는 인간만이 할 수 있는 고도의 지적 작업이다.&lt;/p>
&lt;hr>
&lt;h2 id="5-요구사항-정의야말로-진정한-프롬프트-엔지니어링이다">5. 요구사항 정의야말로 &amp;lsquo;진정한 프롬프트 엔지니어링&amp;rsquo;이다
&lt;/h2>&lt;p>최근 자주 듣는 &amp;lsquo;프롬프트 엔지니어링&amp;rsquo;이라는 말은 종종 &amp;lsquo;AI를 속여 원하는 출력을 얻어내기 위한 핵(hack)&amp;lsquo;처럼 오해받곤 한다. 하지만 소프트웨어 개발에서의 프롬프트 엔지니어링의 본질은 틀림없는 **&amp;lsquo;고도의 요구사항 정의(Requirements Engineering)&amp;rsquo;**이다.&lt;/p>
&lt;p>자연어로 AI에게 지시를 내리고 의도한 대로 소프트웨어를 출력하게 만들기 위해서는 다음과 같은 요소들을 엄밀하게 언어화해야 한다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>목적(Why)&lt;/strong>: 왜 이 기능이 필요한가. 비즈니스적 가치는 무엇인가.&lt;/li>
&lt;li>&lt;strong>제약 조건(Constraints)&lt;/strong>: 성능 요구사항(레이턴시, 처리량), 보안 요구사항, 비용 제약.&lt;/li>
&lt;li>&lt;strong>엣지 케이스(Edge Cases)&lt;/strong>: 사용자가 예기치 않은 입력을 했을 경우의 폴백(Fallback) 처리.&lt;/li>
&lt;li>&lt;strong>인터페이스(Interfaces)&lt;/strong>: 기존 시스템과의 연동 사양.&lt;/li>
&lt;/ol>
&lt;p>모호한 지시(프롬프트)에서는 모호하고 취약한 시스템밖에 나오지 않는다. &amp;lsquo;고객이 진정으로 원했던 것&amp;rsquo;을 깊이 청취하고, 모순되는 요구사항을 정리하며, 논리적인 파탄이 없는 사양서(프롬프트)를 작성하는 능력. 이것이야말로 AI 시대의 최강의 &amp;lsquo;코딩 스킬&amp;rsquo;이다. 프로그래머는 코드 에디터가 아닌 Notion이나 Markdown 파일을 마주하고 시스템의 이상적인 모습을 텍스트로 정교하게 기술하는 시간이 늘어날 것이다.&lt;/p>
&lt;hr>
&lt;h2 id="6-도메인-지식domain-knowledge의-압도적-우위성">6. 도메인 지식(Domain Knowledge)의 압도적 우위성
&lt;/h2>&lt;p>AI는 전 세계의 오픈 소스 코드나 공개 문서를 학습하고 있기 때문에 일반적인 웹 기술이나 알고리즘에는 정통하다. 하지만 AI가 접근할 수 없는 데이터가 있다. 그것은 &amp;lsquo;여러분의 회사 고유의 비즈니스 규칙&amp;rsquo;이며, &amp;lsquo;특정 업계(의료, 금융, 제조 등)에 깊이 뿌리내린 도메인 지식&amp;rsquo;이다.&lt;/p>
&lt;p>예를 들어, 어떤 의료계 스타트업에서 전자 의무 기록 시스템을 개발하고 있다고 하자. AI는 &amp;lsquo;React로 표 형식의 UI를 만드는 방법&amp;rsquo;이나 &amp;lsquo;HL7 FHIR의 일반적인 데이터 구조&amp;rsquo;는 알고 있다. 하지만 &amp;lsquo;A병원의 특정 진료과에서 의사가 어떤 순서로 환자의 데이터를 열람하고, 어떤 UI여야 의료 실수의 위험을 최소화할 수 있는가&amp;rsquo;라는 암묵지까지는 학습하지 못했다.&lt;/p>
&lt;p>기술 자체가 코모디티화(범용화)되는 세계에서 엔지니어의 진정한 가치는 &amp;lsquo;테크놀로지&amp;rsquo;와 &amp;lsquo;비즈니스 도메인&amp;rsquo;의 교차점에서 태어난다. 기술력만으로 승부하는 것이 아니라 의료, 금융, 물류, 엔터테인먼트 등 특정 도메인에 대한 깊은 전문 지식을 가지고, 그 도메인의 과제를 AI라는 강력한 도구를 사용하여 해결할 수 있는 인재가 앞으로의 시장을 견인해 나갈 것이다.&lt;/p>
&lt;hr>
&lt;h2 id="7-소프트웨어-개발의-트롤리-딜레마-누가-책임을-질-것인가">7. 소프트웨어 개발의 &amp;lsquo;트롤리 딜레마&amp;rsquo;: 누가 책임을 질 것인가
&lt;/h2>&lt;p>AI에 대한 의존도가 높아짐에 따라 우리는 중대한 철학적, 윤리적 문제에 직면하게 된다. 소프트웨어 엔지니어링에서의 &amp;lsquo;책임의 소재&amp;rsquo;라는 문제이다.&lt;/p>
&lt;p>만약 AI가 자율적으로 생성한 코드가 프로덕션 환경에서 심각한 버그를 일으켜 기업에 수억 원의 손실을 입히거나, 인명과 관련된 의료 시스템에서 오작동을 일으켰을 경우, 누가 그 책임을 질 것인가? AI 모델을 개발한 기업인가? 아니면 프롬프트를 입력한 엔지니어인가? AI를 &amp;lsquo;해고&amp;rsquo;하거나 &amp;lsquo;체포&amp;rsquo;할 수는 없다.&lt;/p>
&lt;p>시스템이 사회에 미치는 영향에 대한 &amp;lsquo;법적, 윤리적 책임(Accountability)&amp;lsquo;을 떠맡는 주체로서의 &amp;lsquo;인간&amp;rsquo;의 역할은 기술이 아무리 진화하더라도 소멸하지 않는다. 오히려 코드의 생성 프로세스가 블랙박스화될수록 인간은 시스템에 대한 &amp;lsquo;최종 승인자(Approver)&amp;rsquo; 및 &amp;lsquo;감독자(Supervisor)&amp;lsquo;로서 무거운 책임을 지게 된다.&lt;/p>
&lt;p>AI가 제시한 아키텍처나 코드가 보안 기준을 충족하는지, 윤리적으로 문제가 없는지(편향이 포함되어 있지 않은지), 컴플라이언스를 준수하고 있는지를 감사하고 최종적인 GO 사인을 내린다. 이 &amp;lsquo;책임을 지는&amp;rsquo; 행위 자체가 엔지니어의 중요한 업무 중 일부가 된다.&lt;/p>
&lt;hr>
&lt;h2 id="8-ai-페어-프로그래밍과-인지-부하cognitive-load-관리">8. AI 페어 프로그래밍과 인지 부하(Cognitive Load) 관리
&lt;/h2>&lt;p>AI와 함께 일함에 있어 인간의 &amp;lsquo;인지 부하(Cognitive Load)&amp;lsquo;의 성질도 변화하고 있다. 제로에서 코드를 작성할 때의 인지 부하와, AI가 생성한 수백 줄의 미지의 코드를 &amp;lsquo;읽고 리뷰하는&amp;rsquo; 때의 인지 부하는 전혀 다르다.&lt;/p>
&lt;p>심리학의 인지 부하 이론에 따르면, 기존의 스키마(머릿속의 지식 구조)와 일치하지 않는 복잡한 정보를 처리할 때 인간의 작업 기억(Working Memory)은 금세 고갈된다. AI가 생성한 코드는 때로 인간이 생각하지 못한 고도의 최적화를 포함하고 있는 반면, 컨텍스트를 무시한 &amp;lsquo;환각(Hallucination)&amp;lsquo;을 포함하고 있을 때도 있다.&lt;/p>
&lt;p>이를 방지하기 위해서는 AI에 대한 리뷰 프로세스를 시스템화할 필요가 있다.&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant H as "인간 엔지니어 (아키텍트)"
participant A as "AI 에이전트"
participant S as "CI/CD &amp; 테스팅"
H->>A: "엄격한 요구사항 및 제약 조건 정의"
A->>H: "아키텍처 및 초기 코드 제안"
Note over H,A: 리뷰 단계: 높은 인지 부하
H->>A: "설계 선택 비판, 리팩토링 요청"
A->>S: "최종 코드 생성 및 푸시"
S-->>H: "자동화된 테스트 결과 및 정적 분석"
H->>H: "최종 승인 및 책임 인수"&lt;/div>
&lt;p>인간은 &amp;lsquo;작성하는&amp;rsquo; 스킬 이상으로, &amp;lsquo;속독하고 논리적 결함을 순식간에 간파하는(Code Reading &amp;amp; Auditing)&amp;rsquo; 스킬을 극한까지 끌어올려야 한다. 테스트 주도 개발(TDD)의 중요성은 AI 시대에 더욱 커지고 있다. AI에게 코드를 작성하게 하기 전에 인간 또는 다른 AI가 엄밀한 테스트 코드를 작성하고, 그 테스트를 통과할 때까지 AI에게 코드를 수정하게 하는 접근법이 주류가 될 것이다.&lt;/p>
&lt;hr>
&lt;h2 id="9-구체적인-생존-전략-내일부터-무엇을-배워야-하는가">9. 구체적인 생존 전략: 내일부터 무엇을 배워야 하는가
&lt;/h2>&lt;p>지금까지의 분석을 바탕으로, 프로그래머가 AI 시대를 살아남기 위한 구체적인 액션 플랜을 제시한다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>기술의 &amp;lsquo;기초&amp;rsquo;를 철저하게 다시 배운다&lt;/strong>: 프레임워크의 사용법은 AI에게 맡기면 된다. 하지만 OS의 원리, 네트워크 프로토콜(TCP/IP, HTTP/3), 데이터베이스의 내부 구조(B-Tree, 트랜잭션 격리 수준), 자료 구조와 알고리즘에 대한 깊은 이해는 절대적으로 필요하다. AI의 출력이 올바른지 판단하기 위해서는 컴퓨터 과학의 확고한 기초가 필수적이다.&lt;/li>
&lt;li>&lt;strong>클라우드 아키텍처와 분산 시스템을 마스터한다&lt;/strong>: 개별 코드가 아니라 AWS, GCP, Azure 같은 클라우드 리소스를 어떻게 조합하여 확장 가능한 시스템을 구축할 것인가에 주력한다. Terraform 등 IaC(Infrastructure as Code)의 개념을 이해하고 시스템 전체를 코드로 설계하는 능력을 기른다.&lt;/li>
&lt;li>&lt;strong>비즈니스 도메인의 전문가가 된다&lt;/strong>: 자신이 속한 업계의 비즈니스 모델, 법적 규제, 사용자의 행동 심리를 깊이 배운다. 엔지니어의 틀을 넘어 프로덕트 매니저(PM)에 가까운 시각을 가져야 한다.&lt;/li>
&lt;li>&lt;strong>커뮤니케이션과 퍼실리테이션 스킬을 연마한다&lt;/strong>: 인간과 인간 사이에 있는 &amp;lsquo;모호함&amp;rsquo;을 해결하고 합의를 도출하는 프로세스는 AI가 대체할 수 없다. 이해관계자와 대화하고 진정한 과제를 발견하는 소프트 스킬은 가장 가치 있는 스킬이 될 것이다.&lt;/li>
&lt;li>&lt;strong>AI를 &amp;lsquo;동료&amp;rsquo;로 철저히 활용한다&lt;/strong>: AI 도구의 진화를 두려워할 것이 아니라 가장 강력한 무기로 활용한다. 최신 LLM이나 AI 코딩 에이전트를 일상적으로 사용하고, AI가 어디에서 실패하는지, 프롬프트를 어떻게 고안해야 최고의 퍼포먼스를 끌어낼 수 있는지에 대한 &amp;lsquo;암묵지&amp;rsquo;를 축적한다.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="결론-두려워하지-말고-파도에-올라타라">결론: 두려워하지 말고 파도에 올라타라
&lt;/h2>&lt;p>AI에 의한 프로그래밍의 자동화는 프로그래머라는 직업의 &amp;lsquo;죽음&amp;rsquo;을 의미하는 것이 아니다. 오히려 오타 수정이나 환경 구축의 트러블 슈팅, 지루한 보일러플레이트 작성과 같은 소프트웨어 개발에서의 &amp;lsquo;본질적이지 않은 노동&amp;rsquo;으로부터 우리를 해방시켜 주는 **&amp;lsquo;르네상스(문예 부흥)&amp;rsquo;**이다.&lt;/p>
&lt;p>역사상 자동 방직기가 등장했을 때도, 스프레드시트(Excel)가 등장했을 때도 일자리가 소멸할 것이라는 비관론이 만연했다. 하지만 현실은 생산성이 비약적으로 향상됨으로써 새로운 수요가 창출되었고, 더 고도화된 일자리가 생겨났다. 소프트웨어 세계에서도 같은 일이 일어날 것이다. &amp;lsquo;저렴하게 시스템을 만들 수 있게&amp;rsquo; 됨으로써, 지금까지 비용이 맞지 않아 IT화되지 않았던 모든 영역에 소프트웨어가 침투하고 엔지니어가 해결해야 할 과제(What)는 무한히 넓어질 것이다.&lt;/p>
&lt;p>우리 프로그래머는 지금, 코드를 작성하는 장인에서 AI라는 강대한 지능을 지휘하는 &amp;lsquo;오케스트라의 지휘자&amp;rsquo;로 진화할 기회를 부여받고 있다. 기술의 파도에 겁을 먹고 물가에 머무를 것이 아니라, 그 파도에 누구보다 먼저 올라타 더 크고 가치 있는 시스템을 창조하는 여행으로 노를 저어 나가자. AI 시대야말로 진정한 의미에서의 &amp;lsquo;엔지니어링&amp;rsquo;이 시작되는 시대인 것이다.&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>ChatGPT・Gemini・Claude의 API 철저 비교! 어떤 것을 선택해야 할까?</title><link>http://kenji.blog/ko/p/chatgpt-gemini-claude-api-comparison/</link><pubDate>Fri, 11 Sep 2026 12:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/chatgpt-gemini-claude-api-comparison/</guid><description>&lt;img src="http://kenji.blog/p/chatgpt-gemini-claude-api-comparison/img/eyecatch.jpg" alt="Featured image of post ChatGPT・Gemini・Claude의 API 철저 비교! 어떤 것을 선택해야 할까?" />&lt;h1 id="chatgptgeminiclaude의-api-철저-비교-어떤-것을-선택해야-할까">ChatGPT・Gemini・Claude의 API 철저 비교! 어떤 것을 선택해야 할까?
&lt;/h1>&lt;p>AI 기술의 진화는 눈부시며, 특히 대규모 언어 모델(LLM: Large Language Model) 분야에서는 OpenAI의 ChatGPT(GPT 시리즈), Google의 Gemini, Anthropic의 Claude가 삼파전의 치열한 패권 다툼을 벌이고 있습니다. 2026년 현재, 각 회사는 수개월, 아니 수주일 단위로 새로운 모델과 API 기능을 출시하고 있으며, 개발자나 기업의 IT 아키텍트에게 &amp;ldquo;어떤 API를 프로덕트에 통합해야 하는가&amp;quot;라는 질문은 프로젝트의 성공을 좌우하는 매우 중요한 의사 결정이 되었습니다.&lt;/p>
&lt;p>본 기사에서는 이 3대 AI 제공업체의 API에 대해 단순한 스펙의 나열에 그치지 않고, 아키텍처 설계, 상세한 요금 구조, 지연 시간(레이턴시)의 수학적 분석, Python 및 Node.js를 이용한 구체적인 구현 예시, 프롬프트 캐싱 등 최신 비용 최적화 기법에 이르기까지 개발자의 관점에서 철저하게 비교하고 해설합니다.&lt;/p>
&lt;p>독자 여러분이 자신의 유스케이스에 가장 적합한 LLM API를 선정하고, 확장 가능하며 비용 효율적인 AI 애플리케이션을 구축하기 위한 완전한 가이드가 되는 것을 목표로 합니다.&lt;/p>
&lt;hr>
&lt;h2 id="1-각-llm-api의-철학과-설계-사상">1. 각 LLM API의 철학과 설계 사상
&lt;/h2>&lt;p>기술 선정에 있어 우선 각 회사가 어떤 사상으로 모델과 API를 구축하고 있는지 이해하는 것은 매우 중요합니다.&lt;/p>
&lt;h3 id="11-openai-chatgpt">1.1 OpenAI (ChatGPT)
&lt;/h3>&lt;p>OpenAI는 &amp;ldquo;범용 인공지능(AGI)의 실현&amp;quot;을 미션으로 내걸고 항상 업계의 사실상 표준을 견인하고 있습니다. GPT-4o나 GPT-4o-mini, 그리고 추론 특화형인 o1 모델 등 유스케이스에 맞춘 다양한 모델을 제공합니다. 생태계가 가장 성숙해 있으며, 공식·비공식 여부를 불문하고 라이브러리와 문서가 가장 풍부합니다.&lt;/p>
&lt;h3 id="12-google-gemini">1.2 Google (Gemini)
&lt;/h3>&lt;p>Google은 &amp;ldquo;AI First&amp;quot;를 내걸고, 자사의 인프라(TPU 네트워크)를 최대한 활용한 확장성을 무기로 삼고 있습니다. Gemini 1.5 Pro/Flash는 최대 200만 토큰이라는 압도적인 컨텍스트 윈도우(문맥 길이)를 가지며, 방대한 문서나 수 시간의 동영상·음성을 한 번에 처리할 수 있는 것이 가장 큰 특징입니다. Google Cloud (Vertex AI)와의 강력한 통합 역시 엔터프라이즈에게 매력적입니다.&lt;/p>
&lt;h3 id="13-anthropic-claude">1.3 Anthropic (Claude)
&lt;/h3>&lt;p>Anthropic은 전 OpenAI 멤버들이 설립한 기업으로, &amp;ldquo;Constitutional AI(합헌적 AI)&amp;ldquo;라는 독자적인 안전성 접근 방식을 채택하고 있습니다. Claude 3.5 Sonnet과 Opus는 뛰어난 추론 능력, 코드 생성 능력, 그리고 무엇보다 &amp;ldquo;인간처럼 자연스러운 대화&amp;quot;와 &amp;ldquo;할루시네이션(환각)의 적음&amp;quot;으로 많은 개발자들로부터 열광적인 지지를 얻고 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="2-모델-패밀리의-스펙-철저-비교">2. 모델 패밀리의 스펙 철저 비교
&lt;/h2>&lt;p>2026년 현재 주력 모델의 스펙을 비교합니다.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>제공업체&lt;/th>
&lt;th>주력 모델&lt;/th>
&lt;th>최대 컨텍스트 길이&lt;/th>
&lt;th>주요 강점&lt;/th>
&lt;th>권장 유스케이스&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>OpenAI&lt;/strong>&lt;/td>
&lt;td>GPT-4o&lt;/td>
&lt;td>128K&lt;/td>
&lt;td>속도, 시각 인식, 음성 지원&lt;/td>
&lt;td>인터랙티브한 앱, 범용 작업&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>OpenAI&lt;/strong>&lt;/td>
&lt;td>o1-preview&lt;/td>
&lt;td>128K&lt;/td>
&lt;td>고도의 논리 추론, 수학, 코딩&lt;/td>
&lt;td>복잡한 알고리즘 생성, 연구 용도&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Google&lt;/strong>&lt;/td>
&lt;td>Gemini 1.5 Pro&lt;/td>
&lt;td>2,000K&lt;/td>
&lt;td>초장문 처리, 멀티모달(동영상·음성)&lt;/td>
&lt;td>거대한 코드베이스 분석, 동영상 요약&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Google&lt;/strong>&lt;/td>
&lt;td>Gemini 1.5 Flash&lt;/td>
&lt;td>2,000K&lt;/td>
&lt;td>저지연, 고처리량, 압도적인 저비용&lt;/td>
&lt;td>실시간 처리, 대량 데이터의 일괄 처리&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Anthropic&lt;/strong>&lt;/td>
&lt;td>Claude 3.5 Sonnet&lt;/td>
&lt;td>200K&lt;/td>
&lt;td>코딩 능력, 자연스러운 문장 생성&lt;/td>
&lt;td>소프트웨어 개발 지원, 고도화된 고객 지원&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Anthropic&lt;/strong>&lt;/td>
&lt;td>Claude 3.5 Haiku&lt;/td>
&lt;td>200K&lt;/td>
&lt;td>초고속 응답, 가성비&lt;/td>
&lt;td>엣지 AI, 실시간 챗봇&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="3-아키텍처-파고들기-api-요청의-이면">3. 아키텍처 파고들기: API 요청의 이면
&lt;/h2>&lt;p>LLM API를 호출했을 때, 백엔드에서는 어떤 처리가 이루어지고 있을까요? 성능을 최적화하기 위해서는 이 아키텍처를 이해할 필요가 있습니다.&lt;/p>
&lt;p>다음의 Mermaid 다이어그램은 클라이언트로부터 API 요청이 전송되고, 토큰이 스트리밍으로 반환되기까지의 전체적인 모습을 보여줍니다.&lt;/p>
&lt;div class="mermaid">graph TD
A["클라이언트 애플리케이션"] -->|HTTP/REST or gRPC| B["API 게이트웨이"]
B --> C["로드 밸런서"]
C --> D["추론 클러스터"]
D --> E["토크나이저 (BPE / SentencePiece)"]
E --> F["KV 캐시 및 어텐션 메커니즘"]
F --> G["트랜스포머 블록 (정방향 패스)"]
G --> H["출력 레이어 (Logits)"]
H --> I["샘플러 (Temperature, Top-p, Top-k)"]
I --> J["디토크나이저"]
J -->|스트리밍 응답 (Chunk)| A&lt;/div>
&lt;h3 id="31-tokenization토큰화-알고리즘">3.1 Tokenization(토큰화) 알고리즘
&lt;/h3>&lt;p>API에 입력된 텍스트는 내부적으로 &amp;lsquo;토큰&amp;rsquo;이라는 단위로 분할됩니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>OpenAI (tiktoken)&lt;/strong>: Byte-Pair Encoding (BPE) 채택. 특히 영어에서 매우 효율적으로 압축되지만, 한국어 등 비알파벳 언어에서는 토큰 수가 늘어나는 경향이 있습니다.&lt;/li>
&lt;li>&lt;strong>Google (Gemini)&lt;/strong>: SentencePiece (Unigram Language Model) 채택. 다국어에 강하며, 한국어 텍스트에서도 비교적 적은 토큰 수로 표현할 수 있는 경향이 있습니다.&lt;/li>
&lt;li>&lt;strong>Anthropic (Claude)&lt;/strong>: BPE의 맞춤형 버전 사용. 다국어 지원이 강화되어 있으며, Claude 3 이후로는 한국어 토큰 효율도 크게 개선되었습니다.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="4-지연-시간레이턴시과-성능의-수학적-분석">4. 지연 시간(레이턴시)과 성능의 수학적 분석
&lt;/h2>&lt;p>실시간 애플리케이션에서 지연 시간은 사용자 경험(UX)과 직결됩니다. LLM API의 지연 시간 $T_{total}$은 수학적으로 다음과 같이 모델링할 수 있습니다.&lt;/p>
$$ T_{total} = T_{network} + T_{TTFT} + (N \times T_{TPOT}) $$
&lt;p>여기서 각 변수는 다음의 의미를 갖습니다.&lt;/p>
&lt;ul>
&lt;li>$T_{network}$: 네트워크의 왕복 시간(RTT).&lt;/li>
&lt;li>$T_{TTFT}$ (Time To First Token): 첫 글자가 생성되기까지의 시간. 프롬프트 길이(입력 토큰 수)의 제곱에 비례하는 어텐션 계산 비용에 크게 의존합니다.&lt;/li>
&lt;li>$N$: 출력되는 토큰의 총 개수.&lt;/li>
&lt;li>$T_{TPOT}$ (Time Per Output Token): 1토큰 당 생성 시간. 자기 회귀(autoregressive) 모델이므로 이전 출력에 의존하여 직렬로 계산됩니다.&lt;/li>
&lt;/ul>
&lt;h3 id="41-자기-어텐션self-attention-메커니즘의-계산량">4.1 자기 어텐션(Self-Attention) 메커니즘의 계산량
&lt;/h3>&lt;p>Transformer 아키텍처에서 자기 어텐션(Self-Attention)의 계산량은 입력 시퀀스 길이 $L$에 대해 이차 함수적으로 증가합니다.&lt;/p>
$$ \text{Complexity} = O(L^2 \cdot d) $$
&lt;p>여기서 $d$는 임베딩 벡터의 차원 수입니다. 이 제약 때문에 일반적으로 프롬프트가 길어지면 $T_{TTFT}$가 급격히 악화됩니다.
하지만 Google의 Gemini 1.5는 &amp;ldquo;Ring Attention&amp;quot;이나 &amp;ldquo;Block-wise Compute&amp;quot;와 같은 혁신적인 최적화 아키텍처를 채택하여, 200만 토큰이라는 긴 문장을 입력해도 현실적인 시간(수 초~수십 초) 내에 첫 토큰을 생성하는 데 성공했습니다.&lt;/p>
&lt;hr>
&lt;h2 id="5-요금-체계-및-비용-최적화-전략">5. 요금 체계 및 비용 최적화 전략
&lt;/h2>&lt;p>API 비용은 기본적으로 입력 토큰 수와 출력 토큰 수를 기준으로 계산됩니다.&lt;/p>
$$ Cost = (Tokens_{in} \times Rate_{in}) + (Tokens_{out} \times Rate_{out}) $$
&lt;p>하지만 최신 API에서는 비용을 극적으로 낮추기 위한 새로운 메커니즘이 도입되고 있습니다.&lt;/p>
&lt;h3 id="51-프롬프트-캐싱-prompt-caching">5.1 프롬프트 캐싱 (Prompt Caching)
&lt;/h3>&lt;p>방대한 시스템 프롬프트나, RAG로 검색한 대량의 문서를 매번 전송하면 막대한 비용이 발생합니다. 이에 대응하기 위해 각 회사는 캐시 기능을 제공하고 있습니다.&lt;/p>
&lt;p>Anthropic(Claude)이나 Google(Gemini)에서는 특정 텍스트 블록을 캐싱함으로써 입력 비용을 대폭(최대 90%) 절감할 수 있습니다.&lt;/p>
&lt;p>캐시를 이용할 경우의 비용 모델은 다음과 같습니다.&lt;/p>
$$ Cost_{cached} = (Tokens_{cache\_write} \times Rate_{cache\_write}) + (Tokens_{cache\_read} \times Rate_{cache\_read}) + (Tokens_{out} \times Rate_{out}) $$
&lt;p>여기서 $Rate_{cache\_read}$는 일반적인 $Rate_{in}$의 10%~25% 수준으로 설정되어 있습니다. 이를 통해 수만 줄의 코드베이스를 배경 지식으로 항상 유지하면서도 저렴하게 챗봇을 운영할 수 있게 되었습니다.&lt;/p>
&lt;h3 id="52-배치-api-batch-api">5.2 배치 API (Batch API)
&lt;/h3>&lt;p>실시간성이 불필요한 작업(로그 분석, 대량의 데이터 분류 등)을 위해 OpenAI나 Anthropic은 배치 API를 제공합니다. 요청을 모아서 전송하고 24시간 이내에 결과를 받는 대신, 일반 API 요금의 절반(50% 할인)으로 이용할 수 있는 강력한 시스템입니다.&lt;/p>
&lt;hr>
&lt;h2 id="6-개발자-경험dx과-sdk-비교">6. 개발자 경험(DX)과 SDK 비교
&lt;/h2>&lt;p>개발 효율의 관점에서 각 회사가 제공하는 SDK(Software Development Kit)를 비교합니다.&lt;/p>
&lt;h3 id="61-openai-api">6.1 OpenAI API
&lt;/h3>&lt;p>가장 널리 사용되고 있으며, 서드파티 라이브러리(LangChain, LlamaIndex 등)의 지원도 가장 빠릅니다. 또한, Structured Outputs(구조화된 출력) 기능을 통해 JSON 스키마를 100%의 정확도로 준수한 응답을 반환하도록 보장되어 있어, 시스템 연동이 매우 용이합니다.&lt;/p>
&lt;h3 id="62-anthropic-api-claude">6.2 Anthropic API (Claude)
&lt;/h3>&lt;p>SDK 인터페이스가 세련되었으며, TypeScript의 타입 정의 등이 다루기 매우 쉽다는 평가를 받습니다. 특히 Message API의 구조가 직관적이고, 여러 장의 이미지를 포함한 멀티모달 요청도 간단하게 작성할 수 있습니다.&lt;/p>
&lt;h3 id="63-google-gemini-api">6.3 Google Gemini API
&lt;/h3>&lt;p>Google Cloud Vertex AI를 통한 액세스와 AI Studio를 통한 액세스(Google Gen AI SDK) 두 가지가 존재하여 초보자는 조금 혼란스러울 수 있습니다. 하지만 엔터프라이즈용 Vertex AI SDK는 GCP의 IAM(인증 및 인가 시스템)과 완벽하게 통합되어 있어 안전한 개발 환경을 구축할 수 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="7-실전-python을-활용한-다중-api-통합-테스트-구현">7. 실전! Python을 활용한 다중 API 통합 테스트 구현
&lt;/h2>&lt;p>여기서는 Python을 사용하여 OpenAI, Anthropic, Gemini 등 3개의 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;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;/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">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&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">openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">AsyncOpenAI&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">anthropic&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">AsyncAnthropic&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">google.generativeai&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">genai&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">openai_client&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">AsyncOpenAI&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">environ&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;OPENAI_API_KEY&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">anthropic_client&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">AsyncAnthropic&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">environ&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;ANTHROPIC_API_KEY&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">genai&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">configure&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">environ&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;GEMINI_API_KEY&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">prompt&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">fetch_openai&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">start_time&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">openai_client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">chat&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">completions&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create&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="s2">&amp;#34;gpt-4o&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">messages&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;content&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="n">max_tokens&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1024&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">elapsed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start_time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;OpenAI (GPT-4o)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">elapsed&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">choices&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&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">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">fetch_anthropic&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">start_time&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">anthropic_client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">messages&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create&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="s2">&amp;#34;claude-3-5-sonnet-20240620&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">messages&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;content&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="n">max_tokens&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1024&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">elapsed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start_time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;Anthropic (Claude 3.5 Sonnet)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">elapsed&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&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">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">fetch_gemini&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">start_time&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&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">genai&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">GenerativeModel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;gemini-1.5-pro&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="c1"># Gemini Python SDK의 비동기 메서드 활용&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">generate_content_async&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="n">elapsed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start_time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;Google (Gemini 1.5 Pro)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">elapsed&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&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">async&lt;/span> &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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;각 LLM API로 요청을 전송 중...&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개의 API를 병렬로 실행&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gather&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">fetch_openai&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">fetch_anthropic&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">fetch_gemini&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="k">for&lt;/span> &lt;span class="n">provider&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">latency&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">text&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">results&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">provider&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 class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Latency: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">latency&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.2f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> seconds&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="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Response (Excerpt): &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">[:&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">...&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="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">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&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;p>이 스크립트를 실행함으로써 실제 네트워크 환경에서 어떤 모델이 가장 빠르게($T_{total}$을 최소화하여) 응답하는지 쉽게 측정할 수 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="8-nodejs에-의한-tool-callingfunction-calling-구현">8. Node.js에 의한 Tool Calling(Function Calling) 구현
&lt;/h2>&lt;p>LLM을 단순한 챗봇이 아니라 외부 시스템과 연동하는 &amp;lsquo;AI 에이전트&amp;rsquo;로 기능하게 하려면 Tool Calling(또는 Function Calling)이 필수적입니다. 다음은 Node.js(TypeScript)를 사용하여 OpenAI API가 날씨 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;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;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nx">OpenAI&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s2">&amp;#34;openai&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="kr">const&lt;/span> &lt;span class="nx">openai&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">OpenAI&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">apiKey&lt;/span>: &lt;span class="kt">process.env.OPENAI_API_KEY&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="kr">async&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="nx">runAgent() {&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">tools&lt;/span> &lt;span class="o">=&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="kr">type&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;function&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="kd">function&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;get_weather&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="nx">description&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="nx">parameters&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">type&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;object&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="nx">properties&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">location&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">type&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;string&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="nx">description&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="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="nx">required&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;location&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="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="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="kr">const&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">openai&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">chat&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">completions&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">create&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">model&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;gpt-4o&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="nx">messages&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[{&lt;/span> &lt;span class="nx">role&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;content&amp;#34;&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="nx">tools&lt;/span>: &lt;span class="kt">tools&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">tool_choice&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="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="kr">const&lt;/span> &lt;span class="nx">message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">choices&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="nx">message&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="p">(&lt;/span>&lt;span class="nx">message&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">tool_calls&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">toolCall&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">message&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">tool_calls&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`LLM이 도구 호출을 요청했습니다: 함수 이름 = &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">toolCall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kd">function&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&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="kr">const&lt;/span> &lt;span class="nx">args&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">toolCall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="kd">function&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">arguments&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sb">`인수: &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nx">args&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">location&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="sb">`&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">// 여기서 실제 날씨 API(예: OpenWeatherMap)를 호출하는 로직을 구현합니다
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// const weather = await fetchWeatherFromAPI(args.location);
&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="c1">// 가져온 결과를 다시 LLM에 전달하여 최종 답변을 생성하게 합니다
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&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="nx">runAgent&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="k">catch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">error&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;p>Claude 3.5 Sonnet이나 Gemini 1.5 Pro도 동등한 Tool Calling 기능을 갖추고 있으며, 스키마 정의 방법에 약간의 차이는 있지만 기본적인 흐름은 동일합니다.&lt;/p>
&lt;hr>
&lt;h2 id="9-rag-vs-long-context-window-어떤-것을-채택해야-할까">9. RAG vs Long Context Window: 어떤 것을 채택해야 할까?
&lt;/h2>&lt;p>현재 엔터프라이즈 AI 아키텍처에서 가장 큰 논쟁 중 하나는 &amp;ldquo;외부 지식을 가져오기 위해 RAG(검색 증강 생성)를 사용해야 할지, 아니면 거대한 컨텍스트 윈도우(Long Context)에 모든 것을 맡겨야 할지&amp;rdquo; 하는 문제입니다.&lt;/p>
&lt;h3 id="rag-retrieval-augmented-generation의-장점과-과제">RAG (Retrieval-Augmented Generation)의 장점과 과제
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>장점&lt;/strong>: 비용이 저렴하며(필요한 청크만 프롬프트에 넣기 때문), 답변의 근거(출처)를 특정하기 쉽습니다.&lt;/li>
&lt;li>&lt;strong>과제&lt;/strong>: 시맨틱 검색의 정확도에 의존하기 때문에, 문맥이 여러 문서에 흩어져 있는 고도화된 추론 작업(예: &amp;ldquo;작년 전체 회의록에서 A 프로젝트가 지연된 근본 원인을 시계열로 분석해 줘&amp;rdquo;)에는 적합하지 않습니다.&lt;/li>
&lt;/ul>
&lt;h3 id="long-context-gemini-15-pro의-200만-토큰-등">Long Context (Gemini 1.5 Pro의 200만 토큰 등)
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>장점&lt;/strong>: 검색으로 인한 정보 누락이 없습니다. &amp;ldquo;건초 더미에서 바늘 찾기(Needle In A Haystack: NIAH)&amp;rdquo; 테스트에서도 Gemini 1.5 Pro나 Claude 3.5 Sonnet은 99% 이상의 정확도로 정보를 추출할 수 있습니다.&lt;/li>
&lt;li>&lt;strong>과제&lt;/strong>: 토큰 소비량이 방대해져 비용이 증가하며, 지연 시간($T_{TTFT}$)이 늘어납니다.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>결론&lt;/strong>: 2026년의 모범 사례(Best Practice)는 **&amp;ldquo;하이브리드 접근법&amp;rdquo;**입니다. 일상적인 Q&amp;amp;A에는 벡터 데이터베이스를 활용한 RAG를 사용하고, 복잡한 분석이나 코드 전체 리뷰가 필요한 특화된 작업에는 프롬프트 캐싱을 활용한 Long Context를 사용하는 설계가 주류가 되고 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="10-멀티모달-처리-능력-비교">10. 멀티모달 처리 능력 비교
&lt;/h2>&lt;p>차세대 AI 애플리케이션에서는 텍스트뿐만 아니라 이미지, 음성, 동영상을 직접 이해하는 능력이 요구됩니다.&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant User as "사용자"
participant Client as "프론트엔드 앱"
participant API as "LLM API (멀티모달)"
User->>Client: 동영상 및 텍스트 프롬프트 업로드
Client->>API: 동영상 바이트/URI + 텍스트 전송
Note over API: 동영상 청킹 및 음성 분리
Note over API: 멀티모달 임베딩 모델
API-->>Client: 텍스트 요약 및 타임스탬프 반환
Client-->>User: 인사이트 표시&lt;/div>
&lt;ul>
&lt;li>&lt;strong>OpenAI (GPT-4o)&lt;/strong>: 이미지 인식 정확도가 매우 뛰어나며, 손으로 그린 도면이나 복잡한 그래프를 읽어내는 데 탁월합니다. 또한, Realtime API를 이용한 초저지연(수백 밀리초) 네이티브 음성 대화 기능도 강력합니다.&lt;/li>
&lt;li>&lt;strong>Google (Gemini 1.5 Pro)&lt;/strong>: &lt;strong>동영상 분석에 있어서 다른 모델들을 압도합니다.&lt;/strong> 1시간 분량의 동영상 파일(프레임 및 음성)을 그대로 입력하고, &amp;ldquo;12분 45초에 화면 오른쪽 끝에 나타난 인물이 들고 있는 자료의 제목은?&amp;ldquo;과 같은 핀포인트 질문에 답변이 가능합니다.&lt;/li>
&lt;li>&lt;strong>Anthropic (Claude 3.5 Sonnet)&lt;/strong>: 이미지 인식(Vision) 능력은 GPT-4o와 동등한 수준으로 매우 우수합니다. UI 스크린샷을 넘겨주며 &amp;ldquo;이 화면의 React 컴포넌트 코드를 생성해 줘&amp;quot;라고 요구하는 프론트엔드 개발 지원에서 독보적인 강점을 발휘합니다.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="11-엔터프라이즈급-보안-및-컴플라이언스">11. 엔터프라이즈급 보안 및 컴플라이언스
&lt;/h2>&lt;p>기업이 LLM API를 프로덕션 환경에서 사용할 때 가장 우려하는 점은 &amp;ldquo;자사의 데이터가 AI 학습에 사용되지는 않는가&amp;quot;와 &amp;ldquo;컴플라이언스 요건을 충족하는가&amp;quot;입니다.&lt;/p>
&lt;p>3사 모두 API를 통해 전송된 데이터(프롬프트 및 응답)를 &lt;strong>모델 학습에 사용하지 않는다고(Zero Data Retention / No Training on Customer Data)&lt;/strong> 명시하고 있습니다(※무료 소비자용 웹 채팅 UI는 예외입니다).&lt;/p>
&lt;p>더 높은 보안 수준이 요구될 경우:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>OpenAI&lt;/strong>: Azure OpenAI Service를 경유함으로써 Microsoft의 엔터프라이즈급 보안, SLA, Azure Private Link를 통한 폐쇄망 연결을 이용할 수 있습니다.&lt;/li>
&lt;li>&lt;strong>Google&lt;/strong>: Google Cloud Vertex AI를 경유함으로써 VPC Service Controls를 이용한 엄격한 네트워크 분리 및 CMEK(고객 관리 암호화 키)를 통한 데이터 보호가 가능합니다.&lt;/li>
&lt;li>&lt;strong>Anthropic&lt;/strong>: AWS Bedrock 또는 Google Cloud Vertex AI를 경유하여 클라우드 제공업체의 견고한 보안 기반을 함께 이용할 수 있습니다.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="12-결론-유스케이스별-궁극의-선택-가이드">12. 결론: 유스케이스별 궁극의 선택 가이드
&lt;/h2>&lt;p>지금까지 다각도로 비교해 왔지만, 결국 &amp;ldquo;어떤 것을 선택해야 하는가?&amp;ldquo;에 대한 결론은 유스케이스에 따라 다릅니다.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>복잡한 소프트웨어 개발·코드 생성·고도의 추론&lt;/strong>:
&lt;strong>👑 승자: Claude 3.5 Sonnet (Anthropic)&lt;/strong>
코드의 문맥 이해, 리팩토링, 자연스럽고 인간적인 문장 작성에 있어 현재 최고의 성능을 발휘합니다. API의 사용 편의성과 프롬프트 캐싱을 통한 비용 효율성도 뛰어납니다.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>초장문 문서 분석·동영상/음성 일괄 처리&lt;/strong>:
&lt;strong>👑 승자: Gemini 1.5 Pro (Google)&lt;/strong>
200만 토큰의 컨텍스트 윈도우는 유일무이한 무기입니다. 수백 페이지의 PDF 매뉴얼 분석이나 장시간의 회의 녹화 요약 등 데이터의 전체적인 윤곽을 파악해야 하는 작업에서는 Gemini를 따라올 모델이 없습니다.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>범용성·실행 속도·안정적인 구조화된 출력(JSON)&lt;/strong>:
&lt;strong>👑 승자: GPT-4o / GPT-4o-mini (OpenAI)&lt;/strong>
모든 작업을 무난하게 소화하며, 서드파티 도구 지원도 가장 풍부합니다. Structured Outputs를 활용한 확실한 JSON 파싱이나, o1 모델을 이용한 초고도 논리 추론이 필요한 경우 OpenAI 생태계가 필수적입니다.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h3 id="멀티-모델-라우팅-권장">멀티 모델 라우팅 권장
&lt;/h3>&lt;p>단일 API에 의존(벤더 종속)하는 것이 아니라, 작업의 난이도나 중요도에 따라 모델을 동적으로 전환하는 &lt;strong>&amp;ldquo;LLM 라우팅&amp;rdquo;&lt;/strong> 아키텍처가 향후의 트렌드입니다.
예를 들어, 사용자의 단순한 질문에는 저렴하고 빠른 &lt;code>GPT-4o-mini&lt;/code>나 &lt;code>Gemini 1.5 Flash&lt;/code>로 응답하고, 복잡한 처리가 필요하다고 판단되는 경우에만 &lt;code>Claude 3.5 Sonnet&lt;/code>으로 작업을 폴백(fallback)시킴으로써 비용과 성능의 최적 균형을 실현할 수 있습니다.&lt;/p>
&lt;p>AI의 진화는 멈추지 않습니다. 각 API의 장단점과 아키텍처의 특성을 깊이 이해하여 유연하고 확장 가능한 AI 애플리케이션을 구축하시기 바랍니다.&lt;/p></description></item><item><title>llama.cpp 사용법 및 C++를 이용한 커스터마이징 입문</title><link>http://kenji.blog/ko/p/llama-cpp-cxx-customization/</link><pubDate>Fri, 11 Sep 2026 11:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/llama-cpp-cxx-customization/</guid><description>&lt;img src="http://kenji.blog/p/llama-cpp-cxx-customization/img/eyecatch.jpg" alt="Featured image of post llama.cpp 사용법 및 C++를 이용한 커스터마이징 입문" />&lt;p>최근 대규모 언어 모델(LLM)의 진화는 매우 빠르며, 그 응용 범위는 나날이 확대되고 있습니다. 하지만 수십억, 수백억 개의 매개변수를 가진 모델을 로컬 환경에서 구동하려면 보통 방대한 VRAM을 갖춘 하이엔드 GPU가 필요합니다. 이러한 &amp;lsquo;하드웨어의 벽&amp;rsquo;을 허물고, 일반적인 PC나 Mac, 심지어 Raspberry Pi와 같은 기기 위에서 LLM의 실용적인 추론을 가능하게 한 것이 바로 &lt;strong>llama.cpp&lt;/strong>입니다.&lt;/p>
&lt;p>본 기사에서는 단순한 명령줄 도구의 사용법에 그치지 않고, 그 기반 기술인 &lt;code>ggml&lt;/code>의 아키텍처, Transformer 및 양자화(Quantization)의 수학적 배경, 그리고 C++ API를 이용하여 독자적인 애플리케이션에 LLM을 통합하고 커스터마이징하는 방법까지 엔지니어를 위해 매우 상세하게 해설합니다.&lt;/p>
&lt;hr>
&lt;h2 id="1-llamacpp-및-ggml-개요">1. llama.cpp 및 ggml 개요
&lt;/h2>&lt;p>&lt;code>llama.cpp&lt;/code>는 Georgi Gerganov 씨가 개발한, C/C++로 작성된 경량 LLM 추론 엔진입니다. 원래는 Meta의 LLaMA 모델을 Apple Silicon(M1/M2 Mac) 위에서 고속으로 동작시키는 것을 목적으로 탄생했지만, 현재는 다양한 아키텍처와 모델을 지원하고 있습니다.&lt;/p>
&lt;p>가장 큰 특징은 &lt;strong>외부 의존성을 가지지 않는 순수한 C/C++ 구현&lt;/strong>이라는 점입니다. Python이나 PyTorch 등의 거대한 에코시스템을 필요로 하지 않고, 단일 실행 파일로 컴파일할 수 있기 때문에 배포가 매우 쉽습니다.&lt;/p>
&lt;p>이 &lt;code>llama.cpp&lt;/code>의 심장부 역할을 하는 것이 텐서 연산 라이브러리 &lt;strong>ggml&lt;/strong>입니다. ggml은 머신러닝에서의 행렬 연산을 CPU(및 일부 GPU) 상에서 극한까지 최적화하기 위해 처음부터 설계되었습니다.&lt;/p>
&lt;h3 id="11-llamacpp는-왜-빠른가">1.1 llama.cpp는 왜 빠른가?
&lt;/h3>&lt;ol>
&lt;li>&lt;strong>메모리 매핑(mmap) 활용&lt;/strong>: 모델의 가중치를 메모리에 로드할 때 OS의 &lt;code>mmap&lt;/code>을 이용함으로써 RAM 전체 로드를 피하고, 빠른 실행과 메모리 절약을 실현합니다.&lt;/li>
&lt;li>&lt;strong>SIMD 명령의 철저한 최적화&lt;/strong>: AVX2, AVX-512, ARM NEON, Apple AMX 등 CPU 고유의 명령 세트를 활용하여 행렬 곱을 초고속화하고 있습니다.&lt;/li>
&lt;li>&lt;strong>양자화(Quantization)&lt;/strong>: 16-bit 부동소수점(FP16) 가중치를 4-bit, 5-bit, 8-bit 정수로 압축하여 메모리 대역폭의 병목 현상을 해소합니다(자세한 내용은 후술).&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="2-수학적-배경-transformer와-양자화quantization">2. 수학적 배경: Transformer와 양자화(Quantization)
&lt;/h2>&lt;p>llama.cpp를 깊이 이해하기 위해서는 그것이 계산하고 있는 수식과, 어떻게 계산을 근사화하고 있는지 알아야 합니다.&lt;/p>
&lt;h3 id="21-transformer의-추론-프로세스">2.1 Transformer의 추론 프로세스
&lt;/h3>&lt;p>LLaMA 등의 모델은 자기회귀형(Auto-regressive) Transformer 디코더 아키텍처를 채택하고 있습니다. 텍스트 생성의 핵심이 되는 것은 &lt;strong>Self-Attention&lt;/strong> 메커니즘입니다.&lt;/p>
&lt;p>입력이 되는 은닉 상태의 행렬 $X \in \mathbb{R}^{N \times d}$에 대해, 쿼리 $Q$, 키 $K$, 값 $V$는 가중치 행렬과의 곱으로 계산됩니다.&lt;/p>
$$
Q = X W_Q, \quad K = X W_K, \quad V = X W_V
$$
&lt;p>여기서 Attention의 출력은 다음과 같이 정의됩니다.&lt;/p>
$$
\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V
$$
&lt;p>llama.cpp의 추론 루프에서 병목 현상이 발생하는 부분은 이 거대한 행렬 $W_Q, W_K, W_V$나 피드 포워드 네트워크(FFN)의 가중치 행렬과 벡터 $X$(생성 단계에서는 1토큰씩 처리하므로 $N=1$)의 곱, 즉 **GEMV (General Matrix-Vector Multiplication)**입니다.&lt;/p>
&lt;h3 id="22-양자화quantization의-수학적-기초">2.2 양자화(Quantization)의 수학적 기초
&lt;/h3>&lt;p>메모리 액세스 대역이 병목이 되는 추론에 있어서, 가중치 매개변수를 작은 비트 수로 표현하는 양자화는 필수적입니다. llama.cpp에서 널리 쓰이는 블록 단위의 양자화(예: &lt;code>Q4_K&lt;/code>나 &lt;code>Q4_0&lt;/code>)의 기본 원리를 설명합니다.&lt;/p>
&lt;p>예를 들어 FP16 가중치 행렬 $W$의 일부인 길이 $B$(보통 32나 64)의 블록 $w = [w_1, w_2, \dots, w_B]$를 생각합니다. 이 블록을 4-bit 정수 $q_i \in [-8, 7]$과 단일 스케일링 팩터 $\Delta$(FP16 또는 FP32)로 근사합니다.&lt;/p>
$$
w_i \approx \Delta \times q_i
$$
&lt;p>$\Delta$는 블록 내의 최대 절댓값을 바탕으로 결정됩니다.&lt;/p>
$$
\Delta = \frac{\max_i |w_i|}{7}
$$
&lt;p>양자화 후의 가중치를 이용해 내적 $y = w \cdot x$를 계산할 경우, 입력 벡터 $x$도 동일하게 양자화하여 $x_i \approx \Delta_x \times q_{x, i}$로 두면,&lt;/p>
$$
y = \sum_{i=1}^{B} w_i x_i \approx \Delta \Delta_x \sum_{i=1}^{B} q_i q_{x, i}
$$
&lt;p>이 $\sum q_i q_{x, i}$ 부분은 &lt;strong>순수한 정수 연산&lt;/strong>이 되며, SIMD 명령을 사용하여 매우 빠르게 병렬 계산할 수 있습니다. 이것이 llama.cpp가 CPU 상에서 경이로운 속도를 내는 수학적인 트릭입니다.&lt;/p>
&lt;hr>
&lt;h2 id="3-아키텍처-및-추론-흐름">3. 아키텍처 및 추론 흐름
&lt;/h2>&lt;p>llama.cpp의 내부 동작을 이해하기 위해, 다음의 Mermaid 다이어그램으로 시스템 전체의 아키텍처와 데이터 흐름을 나타냅니다.&lt;/p>
&lt;div class="mermaid">graph TD
A["사용자 입력 (String)"] --> B["llama.cpp Tokenizer"]
B --> C["Token IDs (int32 배열)"]
C --> D["컨텍스트 버퍼 (KV Cache)"]
D --> E["ggml 계산 그래프"]
E --> F["Transformer 레이어"]
subgraph "ggml 엔진"
F --> G["Self-Attention (RoPE)"]
G --> H["Feed Forward Network"]
H --> F
end
F --> I["Logits (어휘 크기)"]
I --> J["Sampler (Temperature, Top-K, Top-P)"]
J --> K["선택된 토큰 ID"]
K --> L["llama.cpp Detokenizer"]
L --> M["출력 문자열"]
K -. "자기회귀 루프" .-> D&lt;/div>
&lt;p>텍스트 생성은 하나의 토큰이 출력될 때마다 그것이 다음 입력으로 KV Cache에 추가되고, 다시 계산 그래프를 통과하는 자기회귀적인 루프로 되어 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="4-환경-구축-및-빌드-방법">4. 환경 구축 및 빌드 방법
&lt;/h2>&lt;p>llama.cpp를 C++ 프로젝트에 통합하기 전에 먼저 소스 코드를 빌드해 봅시다.&lt;/p>
&lt;h3 id="41-리포지토리-클론">4.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;/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">git clone https://github.com/ggerganov/llama.cpp.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> llama.cpp
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="42-cmake를-사용한-빌드">4.2 CMake를 사용한 빌드
&lt;/h3>&lt;p>C++ 프로젝트로서 다른 앱에 통합할 경우, CMake를 이용하는 것이 가장 표준적입니다. 플랫폼별 가속기(백엔드)를 활성화함으로써 연산을 고속화할 수 있습니다.&lt;/p>
&lt;p>&lt;strong>CPU 전용 (기본 빌드):&lt;/strong>&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;/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">mkdir build &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">cd&lt;/span> build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake --build . --config Release -j &lt;span class="m">8&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>NVIDIA GPU (CUDA)를 사용할 경우:&lt;/strong>&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;/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">mkdir build &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">cd&lt;/span> build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake .. -DGGML_CUDA&lt;span class="o">=&lt;/span>ON
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake --build . --config Release -j &lt;span class="m">8&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>Apple Silicon (Metal)을 사용할 경우:&lt;/strong>&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;/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">mkdir build &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">cd&lt;/span> build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake .. -DGGML_METAL&lt;span class="o">=&lt;/span>ON
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake --build . --config Release -j &lt;span class="m">8&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>빌드가 성공하면 &lt;code>build/bin/&lt;/code> 디렉터리에 &lt;code>llama-cli&lt;/code> 등의 실행 파일과 후술할 C++ API에서 링크하기 위한 &lt;code>llama&lt;/code> 라이브러리(및 &lt;code>ggml&lt;/code> 라이브러리)가 생성됩니다.&lt;/p>
&lt;hr>
&lt;h2 id="5-c-커스터마이징-입문-llamacpp-api-활용">5. C++ 커스터마이징 입문: llama.cpp API 활용
&lt;/h2>&lt;p>여기서부터는 본론인 C++ 코드를 통한 llama.cpp의 제어에 대해 해설합니다.
명령줄 도구를 사용하는 것뿐만 아니라, 자신의 애플리케이션(예: 게임 엔진, 데스크톱 앱, 임베디드 시스템 등)에 LLM을 포함시키려면 C++ API를 직접 호출해야 합니다.&lt;/p>
&lt;p>llama.cpp는 주로 &lt;code>llama.h&lt;/code>라는 헤더 파일로 C언어 인터페이스를 제공하고 있습니다. C++에서 호출할 때도 이 인터페이스를 이용합니다.&lt;/p>
&lt;h3 id="51-필요-최소한의-인클루드-및-설정">5.1 필요 최소한의 인클루드 및 설정
&lt;/h3>&lt;p>자신의 프로젝트에서 llama.cpp를 사용할 경우, 이하를 인클루드합니다.&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;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;#34;llama.h&amp;#34;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;vector&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;string&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;stdexcept&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&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="c1">&lt;/span>&lt;span class="cp">#define LLAMA_ASSERT(x) \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp"> do { \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp"> if (!(x)) { \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp"> std::cerr &amp;lt;&amp;lt; &amp;#34;Assertion failed: &amp;#34; &amp;lt;&amp;lt; #x &amp;lt;&amp;lt; std::endl; \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp"> std::terminate(); \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp"> } \
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp"> } while (0)
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="52-모델-로드-및-컨텍스트-초기화">5.2 모델 로드 및 컨텍스트 초기화
&lt;/h3>&lt;p>먼저 &lt;code>.gguf&lt;/code> 형식의 모델 파일을 로드하고, 추론을 위한 컨텍스트(메모리 공간과 KV 캐시)를 확보합니다.&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;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">int&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">argc&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">char&lt;/span> &lt;span class="o">**&lt;/span> &lt;span class="n">argv&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">argc&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cerr&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;Usage: &amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">argv&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34; &amp;lt;model.gguf&amp;gt;&amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">1&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">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">model_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">argv&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&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">// 1. 백엔드 초기화(CPU/GPU 등의 환경 셋업)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">llama_backend_init&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. 모델 매개변수의 기본 설정 가져오기
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">llama_model_params&lt;/span> &lt;span class="n">model_params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">llama_model_default_params&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_params&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">n_gpu_layers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">35&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// GPU로 오프로드할 레이어 수
&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="c1">// 3. 모델 로드
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">llama_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">llama_load_model_from_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model_path&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">c_str&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">model_params&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="k">nullptr&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cerr&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;Failed to load model&amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">1&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">// 4. 컨텍스트 매개변수 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">llama_context_params&lt;/span> &lt;span class="n">ctx_params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">llama_context_default_params&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ctx_params&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">n_ctx&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2048&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="c1">&lt;/span> &lt;span class="n">ctx_params&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">n_threads&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">// 추론에 사용할 CPU 스레드 수
&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="c1">// 5. 컨텍스트 생성
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">llama_context&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">ctx&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">llama_new_context_with_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">ctx_params&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ctx&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="k">nullptr&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cerr&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;Failed to create context&amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llama_free_model&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 class="k">return&lt;/span> &lt;span class="mi">1&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="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;Model and context loaded successfully!&amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&lt;/span>&lt;span class="p">;&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;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="53-프롬프트-토큰화-tokenization">5.3 프롬프트 토큰화 (Tokenization)
&lt;/h3>&lt;p>LLM은 텍스트를 직접 이해하는 것이 아니라, 정수 ID(토큰)의 나열로써 처리합니다. 입력 문자열을 토큰으로 변환할 필요가 있습니다.&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;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span> &lt;span class="n">prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">&amp;#34;Q: 일본의 수도는 어디인가요?&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s">A:&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">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">llama_token&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">tokens_list&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tokens_list&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">resize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">prompt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">4&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="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 특수 토큰(BOS: Begin of Sequence 등)을 맨 앞에 추가할지 여부
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">add_special&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 문자열을 토큰 ID의 배열로 변환
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">n_tokens&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">llama_tokenize&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="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="p">.&lt;/span>&lt;span class="n">c_str&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="p">.&lt;/span>&lt;span class="n">length&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tokens_list&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tokens_list&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">add_special&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">false&lt;/span> &lt;span class="c1">// parse_special
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&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="p">(&lt;/span>&lt;span class="n">n_tokens&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&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="c1">&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cerr&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;Failed to tokenize prompt&amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">1&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">tokens_list&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">resize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n_tokens&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="54-추론-루프-및-샘플링">5.4 추론 루프 및 샘플링
&lt;/h3>&lt;p>토큰을 모델에 입력하고, 다음 토큰의 확률 분포(Logits)를 구한 뒤 거기서 샘플링을 수행해 다음 토큰을 결정하는 루프를 구축합니다.&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;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&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="c1">&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">max_gen_tokens&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>&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="c1">&lt;/span> &lt;span class="n">llama_batch&lt;/span> &lt;span class="n">batch&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">llama_batch_init&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">512&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&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="c1">&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">size_t&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">tokens_list&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llama_batch_add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">batch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">tokens_list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="p">},&lt;/span> &lt;span class="nb">false&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="c1">// 프롬프트의 마지막 토큰에서만 로짓(예측 결과)을 출력하도록 설정
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">batch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">logits&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">batch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">n_tokens&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">true&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="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">llama_decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">batch&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cerr&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;llama_decode() failed&amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">1&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="kt">int&lt;/span> &lt;span class="n">n_cur&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">batch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">n_tokens&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="c1">&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">n_decode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&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">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s">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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 샘플러 컨텍스트 초기화(Temperature, Top-K, Top-P 등의 설정)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">llama_sampler&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">smpl&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">llama_sampler_chain_init&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">llama_sampler_chain_default_params&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llama_sampler_chain_add_top_k&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">smpl&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">40&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llama_sampler_chain_add_top_p&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">smpl&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">0.9f&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llama_sampler_chain_add_temp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">smpl&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">0.7f&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llama_sampler_chain_add_dist&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">smpl&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1234&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="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n_decode&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">max_gen_tokens&lt;/span>&lt;span class="p">)&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="c1">&lt;/span> &lt;span class="n">llama_token&lt;/span> &lt;span class="n">new_token_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">llama_sampler_sample&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">smpl&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&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. 토큰이 EOS (End of Sequence) 라면 루프 종료
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">llama_token_is_eog&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">new_token_id&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&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">// 3. 토큰을 문자열(텍스트)로 디코드하여 표시
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">char&lt;/span> &lt;span class="n">buf&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">128&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">n_chars&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">llama_token_to_piece&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">new_token_id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">buf&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">sizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">buf&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n_chars&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">buf&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n_chars&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">flush&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">// 4. 새롭게 생성된 토큰을 다음 배치로 준비
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">llama_batch_clear&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">batch&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llama_batch_add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">batch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">new_token_id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n_cur&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="p">},&lt;/span> &lt;span class="nb">true&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. 모델 평가(KV 캐시를 갱신하고, 다음을 예측)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">llama_decode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">batch&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cerr&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34;Failed to evaluate&amp;#34;&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&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="n">n_cur&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">n_decode&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="mi">1&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="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">endl&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="c1">&lt;/span> &lt;span class="n">llama_sampler_free&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">smpl&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llama_batch_free&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">batch&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llama_free&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ctx&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llama_free_model&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 class="n">llama_backend_free&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">return&lt;/span> &lt;span class="mi">0&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;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>이 코드는 llama.cpp의 기본 API를 사용하여 독자적인 추론 루프를 구현한 것입니다.
&lt;code>llama_batch&lt;/code> 구조체를 이용해 토큰 그룹을 관리하고, &lt;code>llama_decode&lt;/code>로 신경망의 순전파(Forward Pass)를 실행합니다.&lt;/p>
&lt;hr>
&lt;h2 id="6-고급-커스터마이징-사례-c에-의한-로짓-조작-및-페널티-제어">6. 고급 커스터마이징 사례: C++에 의한 로짓 조작 및 페널티 제어
&lt;/h2>&lt;p>단순한 텍스트 생성에 그치지 않고, 특정 포맷(예: JSON만)의 출력을 강제하거나, 특정한 금지 단어를 출력하지 않도록 제어하는 경우 샘플링 전의 **로짓(Logits)**을 C++ 쪽에서 직접 조작합니다.&lt;/p>
&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;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;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&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="c1">&lt;/span>&lt;span class="kt">float&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">logits&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">llama_get_logits_ith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">batch&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">n_tokens&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">int&lt;/span> &lt;span class="n">n_vocab&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">llama_n_vocab&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">// 금지 토큰의 ID 목록 (예: 1234, 5678)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">llama_token&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">forbidden_tokens&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="mi">1234&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">5678&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">// 금지 토큰의 출현 확률을 0(Logit을 마이너스 무한대)으로 만든다
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">llama_token&lt;/span> &lt;span class="nl">bad_tok&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">forbidden_tokens&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">logits&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">bad_tok&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">INFINITY&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;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>이처럼 C++ API를 직접 다룸으로써 LangChain이나 Python 경유로는 실현하기 어렵거나 오버헤드가 커지는 **&amp;lsquo;추론 사이클마다 마이크로 밀리초 단위의 개입&amp;rsquo;**이 가능해집니다.&lt;/p>
&lt;hr>
&lt;h2 id="7-성능-튜닝의-극의">7. 성능 튜닝의 극의
&lt;/h2>&lt;p>C++로 구현을 마친 후, 실제 운영을 위해 속도를 한계까지 높이기 위한 체크 포인트를 몇 가지 소개합니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>배치 처리 최적화:&lt;/strong> 여러 사용자의 요청을 동시에 처리할 경우, &lt;code>llama_batch&lt;/code>에 여러 시퀀스를 포함하여 한 번에 &lt;code>llama_decode&lt;/code>를 호출합니다(Continuous Batching). 이를 통해 메모리 액세스를 공유하여 처리량(스루풋)을 획기적으로 향상할 수 있습니다.&lt;/li>
&lt;li>&lt;strong>Flash Attention 활성화:&lt;/strong>
컨텍스트 매개변수에서 &lt;code>ctx_params.flash_attn = true;&lt;/code>를 설정함으로써, 메모리 사용량을 줄이면서 Attention 계산을 고속화할 수 있습니다. 긴 컨텍스트(수만 토큰)를 다룰 때는 필수적인 설정입니다.&lt;/li>
&lt;li>&lt;strong>NUMA 지원:&lt;/strong>
멀티 소켓 서버 환경에서는 &lt;code>llama_backend_init()&lt;/code> 전에 NUMA 설정을 적절히 수행함으로써 메모리 액세스 대기 시간(레이턴시)을 줄일 수 있습니다.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="8-마치며">8. 마치며
&lt;/h2>&lt;p>본 기사에서는 &lt;code>llama.cpp&lt;/code>의 수학적인 배경부터 시작해 아키텍처 해설, 그리고 C++ API를 구사한 맞춤형 추론 엔진 구축 방법까지 상세하게 해설했습니다.&lt;/p>
&lt;p>Python 생태계는 프로토타이핑에는 매우 편리하지만, 엣지 디바이스로의 배포, 게임에 통합, 실시간 처리가 요구되는 프로덕션 환경에서는 C/C++ 기반의 &lt;code>llama.cpp&lt;/code> 직접 제어가 압도적인 힘을 발휘합니다.&lt;/p>
&lt;p>여러분도 꼭 직접 C++ 코드를 작성하고, 로컬 환경에서 LLM을 자유자재로 다루는 즐거움을 경험해 보시길 바랍니다.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>참고 링크 모음&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a class="link" href="https://github.com/ggerganov/llama.cpp" target="_blank" rel="noopener"
>llama.cpp Official Repository&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://github.com/ggerganov/ggml" target="_blank" rel="noopener"
>ggml - Tensor Library&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://arxiv.org/abs/1706.03762" target="_blank" rel="noopener"
>Attention Is All You Need (Vaswani et al., 2017)&lt;/a>&lt;/li>
&lt;/ul>
&lt;/blockquote></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>