<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>GGML on kenji.blog</title><link>http://kenji.blog/de/tags/ggml/</link><description>Recent content in GGML on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>de</language><copyright>kenjinote</copyright><lastBuildDate>Fri, 11 Sep 2026 17:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/de/tags/ggml/index.xml" rel="self" type="application/rss+xml"/><item><title>Python nicht nötig! Ich habe eine AI-Inferenz-Engine nur mit C++ aufgebaut</title><link>http://kenji.blog/de/p/building-ai-inference-engine-cpp-only/</link><pubDate>Fri, 11 Sep 2026 17:00:00 +0900</pubDate><guid>http://kenji.blog/de/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 nicht nötig! Ich habe eine AI-Inferenz-Engine nur mit C++ aufgebaut" />&lt;h2 id="1-einleitung-warum-python-aufgeben-und-eine-ki-inferenz-engine-in-c-schreiben">1. Einleitung: Warum Python aufgeben und eine KI-Inferenz-Engine in C++ schreiben?
&lt;/h2>&lt;p>In der modernen KI-Entwicklung ist Python der De-facto-Standard. Dank leistungsstarker Frameworks wie PyTorch und TensorFlow können komplexe neuronale Netze mit nur wenigen Codezeilen aufgebaut, trainiert und für die Inferenz genutzt werden. Hinter den Kulissen dieser Frameworks übernehmen jedoch Low-Level-Sprachen wie C++ und CUDA die rechenintensiven Aufgaben. Python fungiert hier lediglich als &amp;ldquo;Klebstoff&amp;rdquo; (Glue).&lt;/p>
&lt;p>Warum sollte man also Python explizit ausschließen und eine KI-Inferenz-Engine ausschließlich mit C++ erstellen? Dafür gibt es einige triftige Gründe:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Extreme Leistung und niedrige Latenz&lt;/strong>: Der Overhead durch Pythons GIL (Global Interpreter Lock) und die dynamische Typisierung kann vollständig eliminiert werden. Besonders bei Systemen, die Echtzeitfähigkeit erfordern, können Verzögerungen im Millisekundenbereich fatal sein.&lt;/li>
&lt;li>&lt;strong>Einfaches Deployment&lt;/strong>: Der Aufbau einer Python-Umgebung (riesige Bibliotheken, Abhängigkeitshölle) auf dem System des Endbenutzers ist äußerst schwierig. Mit C++ reicht es aus, eine einzige statisch verlinkte ausführbare Binärdatei (&lt;code>.exe&lt;/code> oder ELF-Binärdatei) zu verteilen.&lt;/li>
&lt;li>&lt;strong>Unterstützung für Edge-Geräte&lt;/strong>: In ressourcenbeschränkten Umgebungen wie Smartphones, eingebetteten Geräten oder dem Raspberry Pi gibt es keinen Spielraum, um eine Python-Laufzeitumgebung auszuführen, die mehrere Gigabyte an Speicher verbraucht.&lt;/li>
&lt;li>&lt;strong>Direkte Hardwaresteuerung&lt;/strong>: Low-Level-Steuerungen wie das Timing der Speicherzuweisung, die explizite Nutzung von SIMD-Befehlen und die Optimierung des Speichertransfers mit der GPU sind mit C++ möglich.&lt;/li>
&lt;/ol>
&lt;p>In diesem Artikel werden wir den Prozess des Aufbaus einer Inferenz-Engine für Large Language Models (LLMs) und andere Modelle komplett von Grund auf in C++ tiefgreifend und technisch erklären, stark inspiriert von der Architektur der Bibliothek &amp;ldquo;GGML&amp;rdquo;, die von Georgi Gerganov entwickelt wurde.&lt;/p>
&lt;hr>
&lt;h2 id="2-gesamtarchitektur-der-inferenz-engine">2. Gesamtarchitektur der Inferenz-Engine
&lt;/h2>&lt;p>KI-Inferenzprozesse sind im Wesentlichen &amp;ldquo;eine Reihe gigantischer Matrixberechnungen&amp;rdquo;. Um diese effizient auszuführen, muss eine Inferenz-Engine aus den folgenden Komponenten bestehen:&lt;/p>
&lt;div class="mermaid">graph TD
A["Eingabedaten (Tokens/Bilder)"] --> B["Tensor-Management"]
B --> C["Berechnungsgraph (DAG)"]
C --> D["Memory Arena &amp; Allocator"]
C --> E["Scheduler &amp; Thread-Pool"]
E --> F["CPU-Backend (AVX2/ARM NEON)"]
E --> G["GPU-Backend (CUDA/Metal)"]
F --> H["Ausgabeergebnisse"]
G --> H&lt;/div>
&lt;ol>
&lt;li>&lt;strong>Tensor-Management&lt;/strong>: Verwaltet mehrdimensionale Array-Datenstrukturen und die Strides pro Dimension.&lt;/li>
&lt;li>&lt;strong>Berechnungsgraph (Computation Graph)&lt;/strong>: Repräsentiert die Operationen jeder Schicht des neuronalen Netzes als gerichteten azyklischen Graphen (DAG).&lt;/li>
&lt;li>&lt;strong>Memory Arena&lt;/strong>: Ein Mechanismus zur vorab allokierten Speicherverwaltung, um den Overhead der dynamischen Speicherzuweisung (&lt;code>malloc&lt;/code> oder &lt;code>new&lt;/code>) zu vermeiden.&lt;/li>
&lt;li>&lt;strong>Backend&lt;/strong>: Für bestimmte Hardware wie CPU oder GPU optimierte Implementierungen der Operationen (Kernel).&lt;/li>
&lt;/ol>
&lt;p>Wir werden diese Komponenten unter Verwendung der mächtigen Funktionen von C++ (Templates, Zeigerarithmetik, RAII usw.) zusammensetzen.&lt;/p>
&lt;hr>
&lt;h2 id="3-das-geheimnis-der-speicherverwaltung-memory-arena-und-simd-alignment">3. Das Geheimnis der Speicherverwaltung: Memory Arena und SIMD-Alignment
&lt;/h2>&lt;p>Die Speicherverwaltung in einer Inferenz-Engine ist einer der wichtigsten Faktoren, der sich direkt auf die Leistung auswirkt. Während der Inferenz, insbesondere beim Durchlaufen der einzelnen Schichten eines Transformer-Modells, wird eine enorme Anzahl von Zwischen-Tensoren generiert. Wenn diese jedes Mal mit dem standardmäßigen &lt;code>malloc&lt;/code> zugewiesen und freigegeben werden, führt dies aufgrund von Heap-Fragmentierung und OS-Kontextwechseln zu massiven Leistungseinbußen.&lt;/p>
&lt;p>Daher verwenden wir den Ansatz einer &amp;ldquo;&lt;strong>Memory Arena&lt;/strong>&amp;rdquo;. Bei dieser Methode wird die maximale Speichermenge, die zu Beginn der Inferenz benötigt wird, berechnet (oder vorgegeben) und auf einmal zugewiesen. Der Speicher wird dann einfach durch das Inkrementieren eines Zeigers reserviert.&lt;/p>
&lt;h3 id="31-die-bedeutung-des-alignments">3.1 Die Bedeutung des Alignments
&lt;/h3>&lt;p>Moderne CPUs unterstützen SIMD-Befehle (Single Instruction, Multiple Data), wie AVX2/AVX-512 von Intel/AMD oder NEON von ARM. Diese Befehle verarbeiten 256 Bit (32 Byte) oder 512 Bit (64 Byte) Daten auf einmal, erfordern aber, dass der zu verarbeitende Datenspeicher an bestimmten Byte-Grenzen (normalerweise 32 Byte oder 64 Byte) ausgerichtet (aligned) ist.&lt;/p>
&lt;p>Hier ist ein C++-Implementierungsbeispiel für eine Memory Arena, die das Alignment berücksichtigt.&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">// Bei POSIX-Systemen posix_memalign verwenden, bei 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">// Berechnung des Alignments (Bestimmung des Paddings)
&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">// Speicherfreigabe erfolgt nur durch Rücksetzen des Zeigers (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>Auf diese Weise wird der Speicher beim Erstellen von Tensoren immer über diese Arena zugewiesen. Durch einfaches Aufrufen von &lt;code>reset()&lt;/code> am Ende jedes Inferenzschritts (z. B. nach jeder Token-Generierung) kann der Speicher sofort wiederverwendet werden.&lt;/p>
&lt;hr>
&lt;h2 id="4-tensor-datenstrukturen-und-die-magie-des-strides">4. Tensor-Datenstrukturen und die Magie des Strides
&lt;/h2>&lt;p>Ein Tensor ist ein generalisiertes Konzept von Skalaren, Vektoren und Matrizen. Bei der Implementierung ist es wichtig, das Konzept des &amp;ldquo;Strides&amp;rdquo; zu berücksichtigen, das es ermöglicht, die eigentlichen Daten, die als &lt;strong>eindimensionales fortlaufendes Array&lt;/strong> im Speicher liegen, als mehrdimensional zu interpretieren.&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">// Für Quantisierung
&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">// Für Quantisierung
&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">// Anzahl der Dimensionen
&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">// Anzahl der Elemente pro Dimension (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">// Stride pro Dimension (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">// Datentyp
&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">// Zeiger auf die Nutzdaten (Payload)
&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">// Für den Berechnungsgraphen
&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>Der Stride &lt;code>nb[i]&lt;/code> repräsentiert den Byte-Abstand im Speicher zwischen benachbarten Elementen in der Dimension &lt;code>i&lt;/code>.
Zum Beispiel sieht der Stride für eine Matrix mit $M \times N$ Elementen (FP32, 4 Bytes pro Element), die in Row-Major-Reihenfolge (zeilenweise) gespeichert ist, wie folgt aus:&lt;/p>
&lt;ul>
&lt;li>&lt;code>nb[0]&lt;/code> = 4 (Bytes): Bewegung in Spaltenrichtung&lt;/li>
&lt;li>&lt;code>nb[1]&lt;/code> = $N \times 4$ (Bytes): Bewegung in Zeilenrichtung&lt;/li>
&lt;/ul>
&lt;p>Durch die Nutzung dieses Konzepts können Operationen wie &amp;ldquo;Transponieren&amp;rdquo; (Transpose) oder &amp;ldquo;Ansicht&amp;rdquo; (View) durchgeführt werden, ohne Speicher zu kopieren, indem man lediglich die Stride-Werte austauscht. Das ist sehr elegant und schnell.&lt;/p>
&lt;hr>
&lt;h2 id="5-aufbau-des-berechnungsgraphen-dag-und-lazy-evaluation">5. Aufbau des Berechnungsgraphen (DAG) und Lazy Evaluation
&lt;/h2>&lt;p>Ähnlich wie PyTorch verwendet auch unsere Inferenz-Engine eine verzögerte Auswertung (Lazy Evaluation), die dem &amp;ldquo;Define-by-Run&amp;rdquo;-Konzept ähnelt. Das bedeutet, dass zum Zeitpunkt des Aufrufs einer mathematischen Funktion keine Berechnungen durchgeführt werden, sondern nur der Graph (die Abhängigkeiten zwischen den Knoten) aufgebaut wird.&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 ist meistens transponiert
&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>Der Ablauf des Inferenzprozesses ist wie folgt:&lt;/p>
&lt;div class="mermaid">graph LR
A["Tensoren definieren"] --> B["Graph über Ops erstellen"]
B --> C["Topologische Sortierung"]
C --> D["Speicher für Ausgaben reservieren"]
D --> E["Knoten der Reihe nach ausführen"]&lt;/div>
&lt;p>Bei der Auswertung des Graphen (Vorwärtspass) wird eine topologische Sortierung verwendet, um Knoten ohne Abhängigkeiten nacheinander auszuführen. Da wir bei reiner Inferenz keine Gradienten für die Backpropagation speichern müssen, bleibt die Speicherverwaltung sehr einfach.&lt;/p>
&lt;hr>
&lt;h2 id="6-der-kern-von-mathematik-und-optimierung-matrixmultiplikation-gemm">6. Der Kern von Mathematik und Optimierung: Matrixmultiplikation (GEMM)
&lt;/h2>&lt;p>Mehr als 90 % der Rechenleistung bei der KI-Inferenz werden für Matrixmultiplikationen (GEMM: General Matrix Multiply) aufgewendet. Der Kern von Transformer-Modellen, der Attention-Mechanismus und das Feed-Forward Network (FFN), sind letztlich gigantische Matrixmultiplikationen.&lt;/p>
&lt;p>Das Produkt $C = A B$ (Größe $M \times N$) von zwei Matrizen $A$ (Größe $M \times K$) und $B$ (Größe $K \times N$) wird mathematisch wie folgt ausgedrückt:&lt;/p>
$$
C_{i,j} = \sum_{k=0}^{K-1} A_{i,k} \cdot B_{k,j}
$$
&lt;p>Wenn dies mit einer naiven dreifachen Schleife implementiert wird, treten häufig Cache-Misses auf, und es wird kaum Leistung erbracht.&lt;/p>
&lt;h3 id="61-cache-blocking-auf-der-cpu-und-simd-optimierung">6.1 Cache-Blocking auf der CPU und SIMD-Optimierung
&lt;/h3>&lt;p>Die grundlegende Strategie zur Beschleunigung von GEMM auf der CPU sieht folgendermaßen aus:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Loop-Tiling (Cache-Blocking)&lt;/strong>: Die Matrix wird in kleine Blöcke unterteilt, die in den L1/L2-Cache passen, und blockweise berechnet.&lt;/li>
&lt;li>&lt;strong>Daten-Packing&lt;/strong>: Daten werden intern neu angeordnet, um kontinuierliche Speicherzugriffsmuster zu gewährleisten.&lt;/li>
&lt;li>&lt;strong>Nutzung von SIMD&lt;/strong>: Durch die Verwendung von FMA-Befehlen (Fused Multiply-Add) wie &lt;code>_mm512_fmadd_ps&lt;/code> in AVX-512 können in einem einzigen Taktzyklus viele Multiplikations-Additions-Operationen durchgeführt werden.&lt;/li>
&lt;/ol>
&lt;p>Hier ist ein Beispiel für ein vereinfachtes Vektor-Skalarprodukt (Dot Product) unter Verwendung von C++ und SIMD Intrinsics.&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">// Für AVX-Befehle
&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">// Schnelles FP32-Skalarprodukt mit AVX2
&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">// Verarbeite 8 Elemente auf einmal (256 Bit = 32 Byte = 8 * 4 Byte)
&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-Befehl: 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">// Horizontale Addition der Werte im SIMD-Register
&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">// Verarbeitung des Rests
&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>Schon dieser kleine Trick kann im Vergleich zu einer naiven Implementierung zu einer mehrfachen bis zu über zehnfachen Geschwindigkeitssteigerung führen.&lt;/p>
&lt;hr>
&lt;h2 id="7-hardwaregrenzen-überwinden-integration-von-cuda--und-metal-backends">7. Hardwaregrenzen überwinden: Integration von CUDA- und Metal-Backends
&lt;/h2>&lt;p>Eine reine C++-Implementierung läuft auf der CPU zwar einigermaßen gut, aber um riesige Modelle wie LLMs mit praktischer Geschwindigkeit auszuführen (z. B. mehr als 20 Tokens pro Sekunde), ist die parallele Rechenleistung einer GPU unerlässlich. Daher führen wir eine Backend-Abstraktionsschicht in unsere Engine ein.&lt;/p>
&lt;h3 id="71-backend-abstraktion">7.1 Backend-Abstraktion
&lt;/h3>&lt;p>Wir nutzen den C++-Polymorphismus, um die Ausführungseinheit (Executor) für die Berechnungen wechseln zu können.&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">// Ausführung verschiedener Operationen
&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-implementierung-des-nvidia-cuda-backends">7.2 Implementierung des NVIDIA CUDA-Backends
&lt;/h3>&lt;p>Um NVIDIA-GPUs zu nutzen, implementieren wir das Backend mit CUDA-C++-Erweiterungen. Obwohl es möglich ist, eigene Kernel zu schreiben, ist es bei Matrixmultiplikationen am besten, &amp;ldquo;cuBLAS&amp;rdquo;, die erstklassige Bibliothek von NVIDIA, zu verwenden.&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">// Bei CUDA ist Column-Major der Standard, daher muss man auf die Parameter achten
&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 wird als transponiert vorausgesetzt
&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>Da die Datenübertragung (&lt;code>cudaMemcpy&lt;/code>) zwischen CUDA-Speicher und Host-(CPU)-Speicher sehr ressourcenintensiv ist, ist es wichtig, die Engine so zu entwerfen, dass alle Gewichte (Gewichtstensoren) und Zwischentensoren während der Inferenz so weit wie möglich im VRAM verbleiben.&lt;/p>
&lt;h3 id="73-apple-silicon-metal-backend">7.3 Apple Silicon (Metal) Backend
&lt;/h3>&lt;p>In den letzten Jahren haben sich Macs mit M1/M2/M3-Chips (Apple Silicon) als hervorragende Maschinen für die KI-Inferenz erwiesen. Der Grund dafür ist der &amp;ldquo;Unified Memory&amp;rdquo;. Da sich CPU und GPU denselben Speicherbereich teilen, entfallen die teuren Host-Device-Speichertransfers über den PCIe-Bus, wie sie bei CUDA erforderlich sind, komplett.&lt;/p>
&lt;p>Um Metal von C++ aus aufzurufen, verwendet man entweder Objective-C++ (&lt;code>.mm&lt;/code>-Dateien) als Brücke oder die Bibliothek &lt;code>metal-cpp&lt;/code>.
Kernel werden mithilfe von Metal Compute-Shadern (in &lt;code>.metal&lt;/code>-Dateien C++-ähnlich geschrieben) implementiert.&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-Shader (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">// Vereinfacht
&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>In Apple Silicon-Umgebungen steht auch eine optimierte Bibliothek für Matrixmultiplikationen namens MPS (Metal Performance Shaders) zur Verfügung. Wenn man diese in der Praxis nutzt, lassen sich beeindruckende Inferenzgeschwindigkeiten erzielen.&lt;/p>
&lt;hr>
&lt;h2 id="8-spezifische-verarbeitung-für-transformer-modelle-attention-und-kv-cache">8. Spezifische Verarbeitung für Transformer-Modelle: Attention und KV-Cache
&lt;/h2>&lt;p>Hochmoderne LLMs wie LLaMA 2/3 oder GPT basieren auf der Transformer-Architektur. Um dies in C++ zu implementieren, ist es unerlässlich, die &amp;ldquo;Scaled Dot-Product Attention&amp;rdquo;, ausgedrückt durch folgende mathematische Formel, aufzubauen:&lt;/p>
$$
\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V
$$
&lt;p>Zudem müssen bei der autoregressiven Token-Generierung die Berechnungsergebnisse (Key und Value) vergangener Tokens aufbewahrt werden. Dies nennt man &amp;ldquo;&lt;strong>KV-Cache (Key-Value Cache)&lt;/strong>&amp;rdquo;.&lt;/p>
&lt;div class="mermaid">graph TD
T["Aktuelles Token"] --> Q["Query"]
T --> K["Key"]
T --> V["Value"]
K --> KCache["An KV-Cache anhängen"]
V --> VCache["An KV-Cache anhängen"]
Q --> Dot1["Q * K_Cache^T"]
KCache --> Dot1
Dot1 --> Scale["Skalieren (1/sqrt(d))"]
Scale --> Softmax["Softmax"]
Softmax --> Dot2["SoftmaxOut * V_Cache"]
VCache --> Dot2
Dot2 --> Out["Kontextvektor"]&lt;/div>
&lt;p>Auch für den KV-Cache wird der Speicher im Voraus reserviert. Dies erfolgt ähnlich wie bei einem Ringpuffer, indem man in der Arena Platz für die maximale Kontextlänge (z. B. 4096 oder 8192 Tokens) allokiert. Dadurch können Neuallokationen bei jedem Generierungsschritt vermieden werden.&lt;/p>
&lt;p>Für die Positionscodierung (Positional Encoding) implementieren wir &amp;ldquo;RoPE (Rotary Position Embedding)&amp;rdquo;, was sich in den letzten Jahren durchgesetzt hat. Dies ist eine Methode, bei der Positionsinformationen als Rotationsvektoren im komplexen Raum eingebettet werden. Die Optimierung der Aufrufe von &lt;code>sin&lt;/code>- und &lt;code>cos&lt;/code>-Funktionen in C++ (z. B. durch Look-up-Tabellen) ist hier der Schlüssel zur Leistung.&lt;/p>
&lt;hr>
&lt;h2 id="9-extreme-optimierung-durch-modellquantisierung-quantization">9. Extreme Optimierung durch Modellquantisierung (Quantization)
&lt;/h2>&lt;p>Wenn ein großes Modell (beispielsweise ein LLaMA-Modell mit 7 Milliarden Parametern) als FP32 (32-Bit-Fließkommazahl) geladen wird, verbrauchen allein die Gewichte etwa 28 GB Speicher (VRAM). Nimmt man den KV-Cache und die Inferenzpuffer hinzu, übersteigt der Verbrauch leicht 30 GB, was eine Ausführung auf normalen Consumer-GPUs unmöglich macht.&lt;/p>
&lt;p>Hier kommt die &amp;ldquo;&lt;strong>Quantisierung (Quantization)&lt;/strong>&amp;rdquo; ins Spiel. Sie ist auch die wahre Stärke des GGML-Formats.&lt;/p>
&lt;p>Bei der Quantisierung wird die Genauigkeit der Gewichte bewusst verringert.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>FP16 (16-bit)&lt;/strong>: Größe halbiert. Nahezu kein Genauigkeitsverlust.&lt;/li>
&lt;li>&lt;strong>INT8 (8-bit)&lt;/strong>: Ein Viertel der Größe. Leichte Verschlechterung.&lt;/li>
&lt;li>&lt;strong>INT4 (4-bit)&lt;/strong>: Ein Achtel der Größe. Bei Verwendung von eigenem Blocking und Skalierungsfaktoren ist eine praktische Inferenz möglich.&lt;/li>
&lt;/ul>
&lt;p>Auf der Seite der Inferenz-Engine werden die als INT4 (oder INT8) komprimierten Gewichte aus dem Speicher gelesen und &lt;strong>unmittelbar nach dem Laden in die CPU- oder GPU-Register auf FP16 oder FP32 entpackt (dequantisiert)&lt;/strong>, bevor die Berechnung erfolgt.&lt;/p>
&lt;p>Erstaunlicherweise ist es schneller, das aus dem Speicher gelesene Datenvolumen zu reduzieren, selbst wenn dies die Berechnungsmenge erhöht. Dies liegt daran, dass bei moderner Hardware der Engpass bei Inferenzaufgaben nicht in der &amp;ldquo;Rechenleistung (Compute Bound)&amp;rdquo;, sondern in der &amp;ldquo;&lt;strong>Speicherbandbreite (Memory Bandwidth Bound)&lt;/strong>&amp;rdquo; liegt. Mit einer in C++ implementierten Engine, die INT4-Quantisierung nutzt, ist es möglich, lokale LLMs selbst auf einem MacBook Air mit 8 GB VRAM flüssig auszuführen.&lt;/p>
&lt;hr>
&lt;h2 id="10-performance-tuning-numa-architektur-und-thread-pools">10. Performance-Tuning: NUMA-Architektur und Thread-Pools
&lt;/h2>&lt;p>Bei der Inferenz auf einer CPU ist Multithreading unerlässlich. Es ist jedoch nicht optimal, einfach viele &lt;code>std::thread&lt;/code>s zu starten.&lt;/p>
&lt;p>In modernen Multi-Socket-Servern oder High-End-CPUs wie dem Ryzen Threadripper wird eine &lt;strong>NUMA (Non-Uniform Memory Access)&lt;/strong>-Architektur verwendet. Der Zugriff auf physisch nahegelegenen Speicher (lokalen Speicher) von einem bestimmten CPU-Kern aus ist schnell, während der Zugriff auf Speicher, der mit einem anderen Prozessor verbunden ist, extrem langsam ist.&lt;/p>
&lt;p>Fortgeschrittene C++-Inferenz-Engines nutzen die folgenden Techniken:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Thread-Pinning&lt;/strong>: Jeder Thread wird an einen bestimmten CPU-Kern gebunden (Festlegen der Affinity), um zu verhindern, dass Caches durch Kontextwechsel verworfen werden.&lt;/li>
&lt;li>&lt;strong>NUMA-Aware Allocation&lt;/strong>: Der Speicher wird auf demselben NUMA-Knoten zugewiesen wie der Thread, der die Daten verarbeitet.&lt;/li>
&lt;li>&lt;strong>Work-Stealing Thread-Pool&lt;/strong>: Implementierung eines effizienten Schedulers, der jeden Knoten des Berechnungsgraphen in kleinere Aufgaben unterteilt. Freie Threads &amp;ldquo;stehlen&amp;rdquo; sich dann automatisch Aufgaben und führen diese aus.&lt;/li>
&lt;/ol>
&lt;p>Indem man all dies einsetzt, kann die CPU-Auslastung konstant nahe an 100 % gehalten und ein Durchsatz erreicht werden, der nah am theoretischen Maximum liegt.&lt;/p>
&lt;hr>
&lt;h2 id="11-fazit-der-spaß-daran-ki-mit-c-muskeln-anzutreiben">11. Fazit: Der Spaß daran, KI mit C++-&amp;ldquo;Muskeln&amp;rdquo; anzutreiben
&lt;/h2>&lt;p>Python ist zweifellos praktisch. In der Forschung, Entwicklung und beim Prototyping gibt es keine Sprache, die an seine Produktivität heranreicht. Wenn man jedoch in die Phase übergeht, in der man ein fertiges Modell &amp;ldquo;in der realen Welt, effizient und auf jedem Gerät ausführen&amp;rdquo; möchte, schlägt die Stunde von C++.&lt;/p>
&lt;p>Das Erfolgserlebnis, das man verspürt, wenn die eigens entwickelte Inferenz-Engine – erschaffen durch das direkte Manipulieren von Speicher-Bytefolgen, das Ausreizen von Registern durch SIMD-Befehle und den Kampf mit der VRAM-Bandbreite der GPU – nacheinander natürliche japanische (oder deutsche) Text-Tokens in der Konsole generiert, bietet eine &amp;ldquo;reine Ingenieursfreude&amp;rdquo;, die man beim Aufrufen von &lt;code>model.generate()&lt;/code> in einem Python-Framework niemals erleben wird.&lt;/p>
&lt;p>KI-Technologie neigt dazu, eine &amp;ldquo;Black Box&amp;rdquo; zu sein. Doch indem man von Tensor-Operationen bis hin zur Speicherverwaltung alles selbst in C++ schreibt, kann man ein tiefes Verständnis für die wahren Mechanismen erlangen und begreifen, wie ein LLM &amp;ldquo;denkt&amp;rdquo;.&lt;/p>
&lt;p>Wenn Sie über Grundkenntnisse in C++ verfügen und ein starkes Interesse an der aktuellen KI-Technologie haben, sollten Sie unbedingt versuchen, eine eigene Inferenz-Engine zu entwickeln. Die Quellcodes von GGML und llama.cpp werden Ihnen dabei als die besten &amp;ldquo;lebenden Lehrbücher&amp;rdquo; dienen.&lt;/p>
&lt;p>&lt;strong>Also, lassen Sie die schwere Python-Laufzeitumgebung hinter sich und treiben Sie modernste KI mit den Muskeln von C++ an!&lt;/strong>&lt;/p></description></item><item><title>Entwicklungsschritte für kleine KI-Modelle (wie TinyLLaMA) mit C++</title><link>http://kenji.blog/de/p/cpp-small-ai-model-tinyllama-dev-guide/</link><pubDate>Fri, 11 Sep 2026 14:00:00 +0900</pubDate><guid>http://kenji.blog/de/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 Entwicklungsschritte für kleine KI-Modelle (wie TinyLLaMA) mit C++" />&lt;h1 id="entwicklungsschritte-für-kleine-ki-modelle-wie-tinyllama-mit-c">Entwicklungsschritte für kleine KI-Modelle (wie TinyLLaMA) mit C++
&lt;/h1>&lt;p>In den letzten Jahren ist das Interesse an der Ausführung von Large Language Models (LLMs) in lokalen Umgebungen rasant gestiegen. Insbesondere kleine Modelle wie TinyLLaMA (1,1B Parameter) können selbst auf Edge-Geräten mit begrenzten Ressourcen oder herkömmlichen Laptops (einschließlich Windows-Umgebungen) mit praktischer Geschwindigkeit für Inferenzen genutzt werden. Während die Entwicklung hauptsächlich mit Python und PyTorch erfolgt, ist die Kombination aus C++ und „ggml“, einer C-basierten Tensor-Bibliothek, zum De-facto-Standard geworden, wenn es darum geht, die ultimative Leistung und Speichereffizienz zu erreichen.&lt;/p>
&lt;p>In diesem Artikel werden wir die detaillierten Entwicklungsschritte erläutern, um eine Inferenz-Engine von Grund auf aufzubauen (oder die interne Struktur der bestehenden llama.cpp tiefgehend zu verstehen), mit der TinyLLaMA in C++ geladen und Text generiert wird.&lt;/p>
&lt;hr>
&lt;h2 id="1-warum-c-und-ggml">1. Warum C++ und ggml?
&lt;/h2>&lt;p>In der Trainingsphase von KI ist Python mit seiner Flexibilität und seinem umfangreichen Ökosystem überwältigend im Vorteil. In der Bereitstellungs- oder „Inferenz“-Phase wird C++ jedoch aus den folgenden Gründen zu einer leistungsstarken Option.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Reduzierung von Overhead&lt;/strong>: Das Global Interpreter Lock (GIL) von Python und der Runtime-Overhead können vollständig eliminiert werden.&lt;/li>
&lt;li>&lt;strong>Speichereffizienz und Arena-Allokation&lt;/strong>: Da die Zuweisung und Freigabe von Speicher manuell gesteuert werden können, lassen sich unvorhersehbare Spitzen (Spikes) durch die Garbage Collection verhindern.&lt;/li>
&lt;li>&lt;strong>Direkter Zugriff auf die Hardware&lt;/strong>: SIMD-Intrinsics wie AVX-512, AVX2 und ARM NEON können direkt aufgerufen werden, um die Rechenleistung der CPU zu maximieren.&lt;/li>
&lt;li>&lt;strong>Keine Abhängigkeiten&lt;/strong>: ggml ist eine C/C++-Bibliothek ohne Abhängigkeiten (Zero dependencies) und kann problemlos auf Windows-Umgebungen mit MSVC kompiliert werden, solange ein Compiler vorhanden ist.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="2-gesamtbild-der-architektur">2. Gesamtbild der Architektur
&lt;/h2>&lt;p>Der Fluss der gesamten Inferenz-Pipeline ist im folgenden Mermaid-Diagramm dargestellt. Dies ist der Prozess vom Eingabetext des Benutzers bis zur endgültigen Generierung des nächsten Tokens.&lt;/p>
&lt;div class="mermaid">graph TD
A["Benutzereingabetext"] --> B["BPE Tokenizer"]
B --> C["Token-IDs Array"]
C --> D["Embedding Layer Lookup"]
D --> E["Transformer Blocks"]
E --> F["RMSNorm"]
F --> G["LM Head Layer"]
G --> H["Logits Array"]
H --> I["Sampler-Modul"]
I --> J["Nächste Token-ID"]
J --> K["Detokenizer"]
K --> L["Ausgabetext-Chunk"]
J -.-> |"Zum Kontext hinzufügen"| C&lt;/div>
&lt;p>Da es sich um ein autoregressives Modell handelt, wird das ausgegebene Token wieder dem Kontext hinzugefügt und als Eingabe für die Vorhersage des nächsten Tokens zyklisch verwendet (gestrichelte Linie im Diagramm).&lt;/p>
&lt;hr>
&lt;h2 id="3-modellformat-und-memory-mapping-mmap">3. Modellformat und Memory Mapping (mmap)
&lt;/h2>&lt;p>Das größte Hindernis beim Umgang mit den Gewichten riesiger neuronaler Netze sind Festplatten-I/O und Speicherverbrauch. In der C++-Implementierung wird dies durch &lt;strong>Memory Mapping (mmap)&lt;/strong> gelöst.&lt;/p>
&lt;h3 id="31-funktionsweise-von-memory-mapping-und-implementierung-in-windows">3.1 Funktionsweise von Memory Mapping und Implementierung in Windows
&lt;/h3>&lt;p>Mit mmap kann der Inhalt einer Datei direkt in den virtuellen Adressraum des Prozesses abgebildet (gemappt) werden.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Zero-copy&lt;/strong>: Die Daten werden von der Festplatte direkt in den Page-Cache des Kernels geladen, ohne dass eine zusätzliche Kopie in den Userspace erfolgt.&lt;/li>
&lt;li>&lt;strong>On-Demand-Laden (Page Fault)&lt;/strong>: In dem Moment, in dem die CPU tatsächlich auf diese Speicheradresse zugreift, tritt ein Page Fault (Seitenfehler) auf, und nur der benötigte Chunk (normalerweise 4 KB) wird in den physischen Speicher geladen.&lt;/li>
&lt;/ul>
&lt;p>In Windows-Umgebungen werden anstelle des POSIX &lt;code>mmap&lt;/code> die Win32-APIs &lt;code>CreateFileMapping&lt;/code> und &lt;code>MapViewOfFile&lt;/code> verwendet.&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant OS["Windows OS"]
participant RAM["Physischer Speicher"]
participant App["C++ Anwendung"]
App->>OS: "CreateFileMapping / MapViewOfFile"
OS-->>App: "Zeiger auf virtuelle Speicheradresse"
App->>App: "Tensor-Daten am Zeiger lesen"
OS->>RAM: "Page Fault / Seite von der Festplatte laden"
RAM-->>App: "Daten bereit für SIMD-Berechnung"&lt;/div>
&lt;h3 id="32-binäre-struktur-des-gguf-formats">3.2 Binäre Struktur des GGUF-Formats
&lt;/h3>&lt;p>Das aus Formaten wie &lt;code>.safetensors&lt;/code> von Hugging Face konvertierte &lt;strong>GGUF (GPT-Generated Unified Format)&lt;/strong> ist das ultimative Format für Inferenzen. Es verfügt über das folgende strikte binäre Layout.&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>: Die Versionsnummer des Formats.&lt;/li>
&lt;li>&lt;strong>Tensor Count &amp;amp; Metadata Count&lt;/strong>: Anzahl der Tensoren und der Metadaten-Schlüssel-Wert-Paare.&lt;/li>
&lt;li>&lt;strong>Metadata (Key-Value Pairs)&lt;/strong>: Schlüssel mit Präfix für die Zeichenfolgenlänge und typisierte Werte.&lt;/li>
&lt;li>&lt;strong>Tensor Info&lt;/strong>: Name, Dimensionen, Datentyp (FP16, Q4_K usw.) jedes Tensors und die Offset-Position in der Datei.&lt;/li>
&lt;li>&lt;strong>Padding&lt;/strong>: Polsterung (Padding), die eingefügt wird, damit die Tensordaten an bestimmten Grenzen (normalerweise 32 oder 64 Byte) ausgerichtet (aligned) werden. Dies ist entscheidend für den schnellen Speicherzugriff mit SIMD-Befehlen (insbesondere AVX).&lt;/li>
&lt;li>&lt;strong>Tensor Data&lt;/strong>: Das eigentliche Array der ausgerichteten Gewichtsdaten.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="4-mathematische-grundlagen-von-tinyllama-und-c-algorithmen">4. Mathematische Grundlagen von TinyLLaMA und C++-Algorithmen
&lt;/h2>&lt;p>TinyLLaMA beinhaltet einige fortgeschrittene architektonische Tricks zur Effizienzsteigerung. Die mathematischen Ausdrücke zur korrekten Implementierung in C++ werden hier erläutert.&lt;/p>
&lt;h3 id="41-rmsnorm-root-mean-square-normalization">4.1 RMSNorm (Root Mean Square Normalization)
&lt;/h3>&lt;p>Die Rechenkosten werden reduziert, indem die Mittelwert-Zentrierung aus LayerNorm weggelassen und nur die Varianz-Skalierung durchgeführt wird.&lt;/p>
$$ \text{RMSNorm}(x) = \frac{x}{\sqrt{\frac{1}{d}\sum_{i=1}^{d} x_i^2 + \epsilon}} \odot \gamma $$
&lt;p>$d$ ist die Anzahl der Dimensionen, $\gamma$ ist der gelernte Skalierungstensor.
Bei der Implementierung in C++ wird dies optimiert, indem zunächst die Summe der Quadrate des Arrays schnell mit AVX2s &lt;code>_mm256_fmadd_ps&lt;/code> etc. berechnet wird und dann mit der inversen Quadratwurzel (z. B. &lt;code>_mm256_rsqrt_ps&lt;/code> Befehl) multipliziert wird.&lt;/p>
&lt;h3 id="42-rope-rotary-position-embedding">4.2 RoPE (Rotary Position Embedding)
&lt;/h3>&lt;p>Eine Technik, um die Positionsinformationen von Tokens als Rotation im Tensorraum anzuwenden. Es kann als Rotation auf einer komplexen Ebene betrachtet werden, wobei für benachbarte Dimensionspaare $(x_1, x_2)$ des Vektors $x$ die folgende Rotation angewendet wird.&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>Hier ist $m$ der absolute Positionsindex des Tokens und $\theta$ die vorberechnete Grundfrequenz. In ggml wird es parallel ausgeführt, indem einfach der &lt;code>ggml_rope&lt;/code>-Operator während der Konstruktion des Inferenzgraphen hinzugefügt wird.&lt;/p>
&lt;h3 id="43-grouped-query-attention-gqa">4.3 Grouped-Query Attention (GQA)
&lt;/h3>&lt;p>Bei der normalen Multi-Head Attention (MHA) gibt es die gleiche Anzahl von Heads für Query, Key und Value. TinyLLaMA verwendet jedoch &lt;strong>Grouped-Query Attention (GQA)&lt;/strong>, um die Speicherbandbreite und den KV-Cache-Verbrauch drastisch zu reduzieren.&lt;/p>
$$ \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{Q K^T}{\sqrt{d_k}}\right) V $$
&lt;p>Bei GQA teilen sich mehrere Query-Heads einen Key/Value-Head. In der C++-Implementierung ist es erforderlich, den KV-Tensor so zu broadcasten (zu replizieren), dass er der Anzahl der Queries entspricht, bevor die Matrixmultiplikation &lt;code>ggml_mul_mat&lt;/code> ausgeführt wird.&lt;/p>
&lt;h3 id="44-swiglu-aktivierungsfunktion">4.4 SwiGLU-Aktivierungsfunktion
&lt;/h3>&lt;p>Im Feed-Forward Network (FFN)-Layer wird anstelle von GELU SwiGLU verwendet.&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>Im Berechnungsgraphen wird dies durch eine Kombination aus dem &lt;code>ggml_silu&lt;/code>-Operator und &lt;code>ggml_mul&lt;/code> dargestellt.&lt;/p>
&lt;hr>
&lt;h2 id="5-konstruktion-des-berechnungsgraphen-mit-ggml-und-speicherverwaltung">5. Konstruktion des Berechnungsgraphen mit ggml und Speicherverwaltung
&lt;/h2>&lt;p>ggml verfolgt einen „Define-and-Run“-Ansatz, bei dem ein statischer Berechnungsgraph für die Inferenz konstruiert und anschließend evaluiert wird.&lt;/p>
&lt;h3 id="51-ggml_context-und-arena-allocator">5.1 ggml_context und Arena-Allocator
&lt;/h3>&lt;p>Die einzigartigste Eigenschaft von ggml ist die „Arena-Allokation“, bei der in der Inferenzschleife keinerlei dynamische Speicherzuweisungen (&lt;code>malloc&lt;/code> oder &lt;code>new&lt;/code>) stattfinden.
Bei der Initialisierung wird ein riesiger zusammenhängender Speicherbereich (die Arena) reserviert, und bei jedem Aufruf von z. B. &lt;code>ggml_new_tensor&lt;/code> wird der Zeiger dieses Bereichs inkrementiert. Sobald ein Inferenzschritt abgeschlossen ist, wird der Allokationszeiger einfach auf seine Ausgangsposition zurückgesetzt, wodurch die Speicherzuweisung für den nächsten Inferenzschritt sofort abgeschlossen ist.&lt;/p>
&lt;h3 id="52-konkretes-beispiel-für-die-konstruktion-eines-graphen">5.2 Konkretes Beispiel für die Konstruktion eines Graphen
&lt;/h3>&lt;p>Für jeden Inferenzschritt wird der folgende Berechnungsgraph im Speicher aufgebaut.&lt;/p>
&lt;div class="mermaid">graph TD
A["Eingabe-Token-ID"] --> B["Embed Lookup"]
B --> C["ggml_rms_norm"]
C --> D["Q / K / V Projektionen"]
D --> E["ggml_rope Positional"]
E --> F["KV Cache Speichern"]
E --> G["KV Cache Laden"]
G --> H["Self Attention"]
H --> I["Skalierung &amp; Softmax"]
I --> J["Attention Ausgabe"]
J --> K["Out Projektion"]
K --> L["Residual Hinzufügen"]&lt;/div>
&lt;hr>
&lt;h2 id="6-quantisierung-quantization-und-windows--simd-optimierung">6. Quantisierung (Quantization) und Windows / SIMD-Optimierung
&lt;/h2>&lt;p>Wenn TinyLLaMA (1,1B) in FP16 verwendet wird, werden etwa 2,2 GB Speicher benötigt, aber durch 4-Bit-Quantisierung (wie Q4_K) kann dies drastisch auf etwa 600 MB komprimiert werden.&lt;/p>
&lt;h3 id="61-block-quantisierungsarchitektur">6.1 Block-Quantisierungsarchitektur
&lt;/h3>&lt;p>ggml quantisiert nicht den gesamten Tensor einheitlich, sondern in „Blöcken“.
Im &lt;code>Q4_0&lt;/code>-Format werden 32 FP16-Werte in einem Block zusammengefasst.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Skalierungsfaktor (Scale Factor)&lt;/strong>: Ein FP16-Wert (2 Byte)&lt;/li>
&lt;li>&lt;strong>Quantisierte Daten&lt;/strong>: 32 4-Bit-Werte (16 Byte)
Dadurch wird der Einfluss von lokalen Ausreißern (Outliers) minimiert.&lt;/li>
&lt;/ul>
&lt;h3 id="62-beschleunigung-des-skalarprodukts-durch-avx2">6.2 Beschleunigung des Skalarprodukts durch AVX2
&lt;/h3>&lt;p>Beim Kompilieren für die neuesten x86-CPUs in einer Windows-Umgebung werden Compiler-Flags wie &lt;code>/arch:AVX2&lt;/code> verwendet und die SIMD-Verarbeitung läuft im folgenden Fluss ab:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Laden&lt;/strong>: Die quantisierten 4-Bit-Daten werden aus dem Speicher in ein 256-Bit-AVX-Register geladen.&lt;/li>
&lt;li>&lt;strong>Entpacken (Unpack)&lt;/strong>: Die 4-Bit-Werte werden durch Bitmasken- und Shift-Operationen in Int8 oder Int16 entpackt.&lt;/li>
&lt;li>&lt;strong>Dequantisierung&lt;/strong>: Multiplikation mit dem Skalierungsfaktor zur Umwandlung in Gleitkommazahlen.&lt;/li>
&lt;li>&lt;strong>FMA-Berechnung&lt;/strong>: Parallele Ausführung der Multiply-Add-Operation mit den Aktivierungswerten über &lt;code>_mm256_fmadd_ps&lt;/code> (Fused Multiply-Add).&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="7-details-zur-implementierung-des-kv-caches">7. Details zur Implementierung des KV-Caches
&lt;/h2>&lt;p>Bei der autoregressiven Generierung ist ein „KV-Cache“, der die Berechnung von Key und Value vergangener Tokens überspringt, eine zwingend erforderliche Funktion.&lt;/p>
&lt;p>Bei der Implementierung in C++ sind die folgenden Punkte wichtig:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Vorabreservierung des Tensors&lt;/strong>: Ein riesiger Tensor für die maximale Kontextlänge (z. B. 2048 Tokens) wird für den KV-Cache initialisiert (FP16 wird empfohlen).&lt;/li>
&lt;li>&lt;strong>Offset-Kopie&lt;/strong>: Wenn die Berechnung für die Token-Position $N$ durchgeführt wird, werden die in diesem Schritt erhaltenen K- und V-Vektoren in der $N$-ten Zeile des KV-Cache-Tensors mithilfe von &lt;code>ggml_cpy&lt;/code> o. Ä. gespeichert.&lt;/li>
&lt;li>&lt;strong>Erstellung eines Views bei der Attention&lt;/strong>: Bei der Berechnung der Attention wird ein „View“ (Ansicht) erstellt, der nur auf den Bereich der Tokens 0 bis $N$ verweist, und dieser View wird an die Matrixmultiplikation übergeben.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="8-bpe-tokenizer-und-dekodierung">8. BPE Tokenizer und Dekodierung
&lt;/h2>&lt;p>Der Eingabe-String wird als Bytefolge in UTF-8 behandelt und mit einem vordefinierten Vokabular abgeglichen. Um die Vokabularsuche in C++ zu beschleunigen, werden Algorithmen implementiert, die &lt;strong>Trie-Bäume (Präfixbäume)&lt;/strong> oder Prioritätswarteschlangen verwenden.&lt;/p>
&lt;p>Die vom LM Head ausgegebenen Logits werden mithilfe des Temperature-Parameters skaliert, um Wahrscheinlichkeiten zu erhalten. Die Kandidaten werden durch Top-K-Extraktion oder Top-P-Methoden (Nucleus Sampling) eingegrenzt, und das endgültige nächste Token wird mithilfe von Zufallszahlen bestimmt.&lt;/p>
&lt;hr>
&lt;h2 id="9-aufbau-eines-c-projekts-windows--powershell-umgebung">9. Aufbau eines C++-Projekts (Windows / PowerShell-Umgebung)
&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"># Optimierungs- und AVX2-Flags für Windows (MSVC)
&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>Beispiel für einen Build-Befehl in 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-zusammenfassung">10. Zusammenfassung
&lt;/h2>&lt;p>Die Implementierung einer Inferenz-Engine für kleine KI-Modelle wie TinyLLaMA von Grund auf mit C++ und ggml ist eine hervorragende Gelegenheit, die Blackbox des Deep Learning zu entschlüsseln und die Schönheit der Low-Level-Hardwaresteuerung kennenzulernen. Durch den Einsatz von Zero-Copy-Laden mithilfe von Memory Mapping, SIMD-Optimierung und dem Aufbau von KV-Caches können Sie das Wesentliche der Systemprogrammierung in vollen Zügen genießen und gleichzeitig die Zukunft der Edge-KI mitgestalten.&lt;/p></description></item></channel></rss>