Verwendung von Open-Source-Bibliotheken für neuronale Netze in C++
Einführung
Moderne KI gelangt oft über Python, Notebooks, Demoumgebungen und die verständliche Aufregung, ein Modell zum ersten Mal funktionieren zu sehen, in ein Unternehmen. Diese Phase ist real, nützlich und sogar ein wenig magisch. Hier ist die Neugier günstig und die Iteration schnell. Doch das Leben eines echten Produkts endet nicht mit der Demo. Ein Modell, das Kunden bedienen, in ein Backend passen, auf werkseitiger Hardware laufen, in einem Desktop-Produkt funktionieren oder schlechte Netzwerkbedingungen überstehen muss, ist nicht mehr nur ein Modell. Es wird zu einer Komponente in einem System, und in Systemen beginnt die technische Reife eine Rolle zu spielen.
Das ist der Moment, in dem C++ in den Raum zurückkehrt. Nicht, weil Ingenieure sentimental gegenüber der Vergangenheit sind, und nicht, weil jedes KI-Problem zu einem nativen Problem werden sollte, sondern weil die Produktion Fragen aufwirft, die durch Experimente auf höherer Ebene nur begrenzt aufgeschoben werden können. Wie viel Speicher benötigt der Prozess wirklich? Wie hoch ist die stationäre Latenz unter Last? Kann die Startzeit die automatische Skalierung überleben? Kann die Laufzeit innerhalb einer vorhandenen nativen Anwendung leben? Können wir denselben Inferenzpfad an einen Server, eine Edge-Box und eine Bediener-Workstation senden, ohne das gesamte Produkt um einen Forschungsstapel herum neu aufzubauen?
Open-Source-Bibliotheken machen diesen Übergang möglich, ohne die Kontrolle an eine Blackbox des Anbieters abzugeben. Sie bieten uns stabile Laufzeiten, Tensorabstraktionen, optimierte Kernel, quantisierte Ausführungspfade, hardwarebewusste Backends und in der jüngsten LLM-Ära überraschend leistungsfähige lokale Inferenz-Engines. Aber die Fülle an Bibliotheken kann die Landschaft auch unübersichtlich machen. Ingenieure fragen oft, welche Bibliothek die beste ist, obwohl die bessere Frage lautet, welche Bibliothek ehrlich mit der vor uns liegenden Aufgabe umgeht.
Dieser Artikel geht diesen fundierteren Weg. Wir werden die wichtigsten C++-relevanten Bibliotheken in der KI nicht als Identitätszeichen betrachten, sondern als Ingenieurpersönlichkeiten mit Stärken, blinden Flecken und Betriebsannahmen. Am Ende besteht das Ziel nicht nur darin, die Namen ONNX Runtime, LibTorch, oneDNN, OpenVINO, TensorFlow Lite und llama.cpp zu kennen. Das Ziel besteht darin, zu verstehen, wann jedes einzelne hilft, wann es zu schwer wird, wann es zu eng wird und wie man wählt, ohne von der Mode herumgeschubst zu werden.
Warum KI-Systeme immer wieder zu C++ zurückkehren
Es gibt einen Rhythmus bei der KI-Bereitstellung, den es zu benennen lohnt, denn wenn man ihn erst einmal erkennt, werden viele Architekturentscheidungen leichter zu verstehen. Zuerst gibt es die Entdeckungsphase. Forscher und Produktingenieure lernen immer noch, was das Modell kann, welche Daten es benötigt und wo der Wert tatsächlich liegen könnte. In dieser Phase übertrifft Ausdruckskraft Disziplin. Schnelles Experimentieren, umfangreiche Python-Tools und flexible Forschungs-Frameworks sind genau das, was das Team braucht.
Dann kommt die weniger glamouröse zweite Phase, in der ein Prototyp beginnt, Verpflichtungen anzuhäufen. Ein Support-Team muss Fehler verstehen. Ein SRE-Team möchte ein vorhersehbares Start- und Speicherverhalten. Die Finanzabteilung möchte wissen, ob es sich bei der Versorgungsrechnung um einen vorübergehenden Anstieg oder ein dauerhaftes Leck handelt. Ein eingebetteter Kunde fragt, ob das Modell offline ausgeführt werden kann. Bei einer Sicherheitsüberprüfung wird gefragt, was genau in der Binärdatei enthalten ist und welche Teile überprüft werden können. Plötzlich hört das Modell auf, ein Forschungsartefakt zu sein, und wird zum Bürger einer Produktionsumgebung.
C++ kehrt immer wieder zu diesem Punkt zurück, weil es dem Ingenieur die Möglichkeit gibt, konkrete Fragen zu beantworten, anstatt mit ihnen herumzufuchteln. Ein nativer Dienst kann Zuordnungsstrategien, Thread-Pools, ABI-Grenzen, Paketierung, CPU-spezifische Optimierungen und die Integration mit vorhandenen leistungsempfindlichen Subsystemen steuern. Diese Kontrolle ist nicht immer notwendig. Aber wo es nötig ist, lässt es sich mit Rhetorik nur sehr schwer vortäuschen.
Hier hilft ein nützliches Gegenbeispiel. Wenn Ihr Team einen leicht ausgelasteten internen Dokumentenklassifikator erstellt, der einmal pro Stunde ausgeführt wird, ist möglicherweise ein Python-Dienst mit einem stabilen Bereitstellungsframework und sehr wenig nativem Code der Weg mit dem geringsten Widerstand. Daran ist nichts Beschämendes. Wenn andererseits dasselbe Team Inferenz in eine latenzempfindliche C++-Desktopanwendung einbettet, an ein Edge-Gerät mit begrenzten Ressourcen liefert oder die Modellausführung direkt in einen Hot-Backend-Pfad einfügt, wird es sehr schnell teuer, so zu tun, als ob die Laufzeitsprache keine Rolle spielt. Mit anderen Worten: C++ ist nicht die Antwort auf jedes KI-Problem, aber es bleibt eine der schwerwiegendsten Antworten, wenn das System selbst zum Problem wird.
Die Bibliotheken als Ingenieurpersönlichkeiten
Der einfachste Weg, sich in diesem Ökosystem zu verlieren, besteht darin, jede Bibliothek so zu behandeln, als ob sie um denselben Job konkurrieren würde. Das sind sie nicht. Ein schulungsorientiertes Framework, eine tragbare Inferenzlaufzeit, eine Kernel-Bibliothek und eine lokale LLM-Engine lösen unterschiedliche Probleme. Wenn wir sie in einer Kategorie namens KI-Bibliotheken zusammenfassen, treffen wir Entscheidungen am Ende eher auf der Grundlage der Markenbekanntheit als auf der Grundlage des Systemdesigns.
ONNX Runtime ist in vielen Produktionsumgebungen die disziplinierteste und am wenigsten theatralische Wahl. Es basiert auf einem klaren Versprechen: Exportieren Sie das Modell in ein stabiles Format, laden Sie es über eine Laufzeit, die sich auf die Ausführung konzentriert, und lassen Sie die Anwendung den Rest des Systems besitzen. Das klingt einfach, und gerade die Einfachheit ist der Grund, warum es leistungsstark ist. ONNX Runtime ist oft die richtige Antwort, wenn die Forschungsphase bereits an anderer Stelle stattgefunden hat und nur noch die nüchterne Arbeit besteht, Inferenzen wiederholt, portabel und mit vorhersehbarem Betriebsverhalten bereitzustellen. Ein Computer-Vision-Backend, das Bilder empfängt, Tensoren normalisiert, ein bekanntes Diagramm ausführt und Ergebnisse an einen vorhandenen C++-Dienst zurückgibt, ist eine ideale ONNX-Runtime-Story. Eine schlechte Anpassung wäre ein Produkt, dessen Kernwert vom dynamischen Trainingsverhalten, häufigen Diagrammoperationen innerhalb der Anwendung oder einem sich ständig ändernden Satz benutzerdefinierter Operatoren abhängt, die den Export spröde machen. In einem solchen Fall kann die zunächst sauber erscheinende Laufzeitgrenze zu einer Reibungsquelle werden.
LibTorch hat einen anderen Charakter. Es handelt sich nicht in erster Linie um eine leichte Ausführungsgrenze. Es ist die C++-Seite eines vollständigen Deep-Learning-Frameworks. Das macht es schwerer, aber auch ausdrucksstärker. Wenn eine native Anwendung wirklich über Tensoroperationen verfügen, Modelle erstellen, schulungsähnliche Manipulationen durchführen oder in der Entwicklung und Produktion nahe an der PyTorch-Semantik bleiben muss, ist LibTorch überzeugender als ONNX Runtime. Es liegt eine gewisse Ehrlichkeit bei der Auswahl, wenn das Produkt wirklich ein Framework und nicht nur eine Laufzeit benötigt. Das Gegenbeispiel ist ebenso wichtig. Teams verwenden LibTorch manchmal für einfache statische Inferenz, weil es sich prestigeträchtig oder zukunftssicher anfühlt. Dann stellen sie fest, dass sie eine viel größere konzeptionelle und operative Oberfläche importiert haben, als der Arbeitsaufwand erforderte. Ein kleiner Inferenzdienst, der nur das Laden eines stabilen Modelldiagramms erfordert, kann diese Entscheidung aufgrund der Paketgröße, der Komplexität und des Debugging-Aufwands wettmachen.
oneDNN und OpenVINO leben näher am Metall und belohnen eine leistungsbewusstere Denkweise. oneDNN ist normalerweise nicht die Bibliothek, nach der Sie greifen, weil Sie eine vollständige Produktgeschichte wünschen. Es ist die Bibliothek, die Sie zu schätzen wissen, wenn CPU-Kernel, Speicherformate und Effizienz auf Bedienerebene so wichtig werden, dass sie direkte Aufmerksamkeit verdienen. Viele Teams nutzen es indirekt über Laufzeiten auf höherer Ebene, was oft sinnvoll ist. OpenVINO befindet sich mittlerweile an einer strategischeren Stelle. Es hilft Teams, denen eine Intel-orientierte Bereitstellung, Diagrammoptimierung und hardwarebewusste Ausführung am Herzen liegt, ohne jedes Detail auf niedriger Ebene manuell verwalten zu müssen. In der Praxis gewinnen diese Tools dann an Bedeutung, wenn das Geschäftsproblem nicht mehr nur darin besteht, „das Modell auszuführen“, sondern „das Modell effizient auf der Hardware auszuführen, die wir tatsächlich kaufen, bereitstellen und warten können“. Dieser Unterschied klingt in einer Besprechung klein und wird in einem Budget sehr groß.
TensorFlow Lite repräsentiert ein ganz anderes Temperament. Es ist die Stimme der Zurückhaltung. Auf Edge-Geräten, mobilen Zielen und ressourcenbeschränkten Systemen ist Vollständigkeit oft weniger wertvoll als Fitness. Ingenieure brauchen dort keinen majestätischen Rahmen; Sie benötigen ein Modell, das lädt, ausführt und strenge Einschränkungen hinsichtlich Speicher, Paketgröße, Energieverbrauch und Startzeit einhält. TensorFlow Lite macht Sinn, wenn das Bereitstellungsziel selbst die primäre Kraft ist, die die Architektur prägt. Das Gegenbeispiel ist ebenfalls üblich: Ein Team beginnt mit einer Edge-Runtime, weil diese effizient klingt, und erweitert sie dann langsam auf eine breitere Serverplattform oder einen Workflow mit dynamischeren Anforderungen, als sie unterstützen soll. Effizienz am Rande führt nicht automatisch überall zu Komfort.
Dann gibt es noch llama.cpp, das besondere Aufmerksamkeit verdient, weil es die emotionale Landkarte lokaler Schlussfolgerungen verändert hat. Bevor llama.cpp und ähnliche Projekte zum Mainstream wurden, gingen viele Ingenieure davon aus, dass die lokale Bereitstellung großer Sprachmodelle entweder ein Forschungsspielzeug oder eine Unternehmensanwendung bleiben würde. llama.cpp zeigte etwas Interessanteres: Mit aggressiver Quantisierung, sorgfältiger Kernel-Arbeit und disziplinierter Entwicklung könnte ein modernes LLM zu einer lokalen nativen Komponente in gewöhnlichen Systemen werden. Diese Einsicht ist über ein Projekt hinaus von Bedeutung. Es erinnerte das gesamte Fachgebiet daran, dass native Ausführung, Modellkomprimierung und praktische Bereitstellung viel schneller voranschreiten können, als zentralisierte Narrative oft vermuten lassen. Aber llama.cpp hat auch eine natürliche Grenze. Es eignet sich hervorragend, wenn es darum geht, unterstützte Transformatormodelle lokal und effizient auszuführen. Es ist kein allgemeiner Ersatz für das gesamte Deep-Learning-Ökosystem, und Teams geraten in Schwierigkeiten, wenn sie darum bitten, eines zu werden.
Wie man wählt, ohne sich vom Hype verführen zu lassen
Der zuverlässigste Weg, zwischen diesen Bibliotheken auszuwählen, besteht darin, mit dem Produkt zu beginnen und dem Tool erst später einen Namen zu geben. Fragen Sie zunächst, was Ihre Anwendung wirklich besitzt und was sie lediglich verbraucht. Wenn das System überwiegend ein stabiles Modell nutzt und eine portable, gut begrenzte Inferenz benötigt, ist ONNX Runtime oft die ruhigste Antwort. Wenn das System selbst in der Sprache von Tensoren, Modulen und Framework-Semantik sprechen muss, verdient LibTorch die Diskussion. Wenn CPU-Effizienz, Grafikoptimierung oder Intel-lastige Bereitstellung der schwierige Teil sind, rücken oneDNN und OpenVINO näher in die Mitte. Wenn das Ziel klein, offline, batterieempfindlich oder eingebettet ist, wirkt TensorFlow Lite natürlicher. Wenn es bei dem Produkt ausdrücklich darum geht, ein lokales quantisiertes Sprachmodell in einer nativen Umgebung auszuführen, gehört llama.cpp schon früh auf den Tisch.
Eine zweite Frage ist ebenso wichtig: Wo wird der technische Aufwand eigentlich bezahlt? Teams wählen Bibliotheken oft nach Benchmark-Schlagzeilen aus und stellen dann fest, dass ihr eigentliches Problem woanders liegt. Eine Laufzeit mit spektakulären Durchsatzzahlen ist möglicherweise immer noch die falsche Lösung, wenn der Export instabil ist, die Vorverarbeitung chaotisch ist oder die Bereitstellungspaketierung brüchig wird. Eine etwas langsamere Laufzeit kann immer noch die bessere Wahl für Unternehmen sein, wenn sie eine klarere Grenze zwischen Modellerstellern und Systembetreuern schafft. Ingenieure, die mehr als ein KI-Produkt ausgeliefert haben, lernen diese Lektion tiefgreifend: Die beste Bibliothek ist nicht immer diejenige, die die Benchmark-Tabelle gewinnt, sondern diejenige, die es einfacher macht, um zwei Uhr morgens über das gesamte System nachzudenken.
Hier werden Gegenbeispiele gesund. Stellen Sie sich ein Team vor, das einen nativen Dokumentenanalysedienst aufbaut. Die modische Wahl könnte darin bestehen, zum schwersten verfügbaren Rahmen zu greifen, weil er sich zukunftssicher anfühlt. Wenn das Modell jedoch statisch ist, die Vorverarbeitungspipeline unkompliziert ist und der eigentliche Bedarf an einer stabilen Inferenz innerhalb eines vorhandenen C++-Dienstes besteht, wird ONNX Runtime wahrscheinlich weniger langfristigen Widerstand erzeugen. Betrachten Sie nun das Gegenteil. Ein Team führt native Experimente mit benutzerdefinierten Tensorflüssen, häufigen Architekturänderungen und enger Kopplung an PyTorch-basierte Trainingslogik durch. Alles über ONNX zu erzwingen, weil es sich „produktionsbereit“ anhört, kann zu einem fragilen exportorientierten Arbeitsablauf führen, der niemandem wirklich Spaß macht. In jedem Fall ist der Fehler derselbe: Das Team wählte eine Identität, bevor es eine Arbeitslast wählte.
Wie gute Integration tatsächlich aussieht
Ein ausgereifter Integrationsworkflow beginnt mit dem Datenvertrag, nicht mit der Bibliothek. Bevor Sie über Laufzeiten diskutieren, entscheiden Sie, was die Anwendung dem Modell gibt und was das Modell an die Anwendung zurückgibt. Benennen Sie die Tensorformen, D-Typen, Normalisierungsregeln, Tokenisierungspfade, Auffüllverhalten, Batch-Annahmen und Fehlerbedingungen. Das klingt fast bürokratisch, ist aber die stille Quelle vieler erfolgreicher Einsätze. Systeme scheitern nicht nur, weil die Laufzeiten falsch sind, sondern auch, weil die Grenzen um sie herum verschwommen sind.
Sobald der Datenvertrag stabil ist, lässt sich der Export oder die Modellverpackung viel einfacher validieren. Ein Team kann die Ergebnisse des Forschungspfads und des Produktionspfads anhand repräsentativer Eingaben vergleichen, Toleranzen messen und erkennen, wo sich die Wiedergabetreue ändert. Hier entdecken Ingenieure, ob ihre elegante Architektur die Realität überdauert. Manchmal ist das exportierte Diagramm in Ordnung und das einzige Problem ist eine nicht übereinstimmende Vorverarbeitung. Manchmal ist die Laufzeit einwandfrei und das eigentliche Problem ist die Überbelegung von Threads an anderer Stelle im Dienst. Manchmal kann ein vermeintlich kleines Modell den Speicherdruck echter Parallelität nicht überstehen. Jede dieser Entdeckungen ist nützlich. Das bedeutet, dass das System begonnen hat, sichtbar zu werden.
Danach folgen Benchmarking und Profiling, und hier gilt die gleiche alte Regel: Messen Sie das System, das Sie ausliefern möchten, und nicht das Spielzeug, bei dem Sie sich früher als schlau gefühlt haben. Vergleichen Sie das Modell unter realistischen Anforderungsformen, Chargengrößen, Eingabeschwankungen und Hardwarebedingungen. Auch die Profilvorverarbeitung und -nachbearbeitung, da viele Teams unbewusst nur den Modellkern bewerten und vergessen, dass Kunden für den gesamten Pfad bezahlen. In der Produktions-KI ist ein Zehn-Millisekunden-Diagramm, das von sechzig Millisekunden vermeidbarem Kleber umgeben ist, immer noch ein Siebzig-Millisekunden-Feature.
Machen Sie schließlich die Bereitstellung reproduzierbar. Native KI-Stacks belohnen Disziplin. Pin-Versionen, Dokument-Compiler- und Laufzeitannahmen, entscheiden, welche Ausführungsanbieter oder CPU-Funktionen erforderlich sind, und behalten einen engen Satz unterstützter Konfigurationen bei. Wenn ein Teamkollege denselben Inferenzpfad ohne Archäologie nicht auf einer anderen Maschine reproduzieren kann, ist der Stapel noch nicht fertig, egal wie beeindruckend die Demo auch gewesen sein mag. Bei gutem C++-KI-Engineering geht es nicht nur um Geschwindigkeit. Es geht darum, das System so ruhig zu machen, dass die Geschwindigkeit verständlich bleibt.
Fehler, die sich immer wieder wiederholen
Der häufigste Fehler besteht darin, Forschungswahrheiten mit Produktionswahrheiten zu verwechseln. Ein Modell, das in einem Notebook hervorragend aussieht, kann nach dem Exportieren, Quantisieren, Einbetten, Beobachten und Ausführen unter echter Parallelität unhandlich werden. Das bedeutet nicht, dass das Modell schlecht war. Das bedeutet, dass das System größer war als das Experiment. Der zweite wiederkehrende Fehler besteht darin, so zu tun, als wären Vorverarbeitung und Nachverarbeitung zweitrangig. In echten Produkten sind sie oft die halbe Arbeit. Bildgrößenänderungsrichtlinie, Tokenizer-Verhalten, Feature-Normalisierung, Kalibrierungsschwellenwerte und Ausgabedekodierung sorgen für alle Formkorrektheit und Latenz genauso sicher wie die Kernlaufzeit.
Ein dritter Fehler besteht darin, sich zu sehr auf ein Framework festzulegen, weil es sich modern oder umfassend anfühlt. Ingenieure wählen manchmal das größtmögliche Werkzeug aus, weil sie damit rechnen, dass Bedürfnisse nie eintreffen. Das Produkt bezahlt dann für Funktionen, die es nicht nutzt. Es gibt auch den umgekehrten Fehler: Man wählt im Namen der Reinheit die leichteste Laufzeit und stellt dann fest, dass dynamisches Verhalten, benutzerdefinierte Operationen oder Semantik auf Framework-Ebene doch nicht optional sind. Weisheit liegt darin, nur für die Macht zu bezahlen, die man tatsächlich erklären kann.
Es gibt auch ein subtileres Versagen der Einstellung. Einige Teams behandeln die Wahl der Bibliothek so, als würde damit die gesamte technische Geschichte geklärt. Das ist nicht der Fall. Gute Ergebnisse entstehen durch wiederholte, bescheidene Arbeit: Ausgaben validieren, Hot Paths messen, vermeidbare Kopien entfernen, Reibung beim Start reduzieren, Paketierung vereinfachen und die Laufzeitgrenze lesbar halten. Open-Source-Bibliotheken machen diese Arbeit möglich; Sie führen es nicht in unserem Namen durch.
Eine kleine, erinnerungswürdige Bereitstellungsgeschichte
Stellen Sie sich ein Team vor, das mit einem Python-Vision-Prototyp beginnt. Die Demo ist stark genug, um interne Unterstützung zu gewinnen, und bald dreht sich das Gespräch um die Integration in einen vorhandenen C++-Dienst, der bereits die Bildaufnahme, Regelauswertung und Berichterstellung übernimmt. Das Team hat mehrere Versuchungen. Eine besteht darin, das Modell für immer hinter einem separaten Python-Dienst zu belassen, da dies kurzfristig einfach ist. Eine andere Möglichkeit besteht darin, alles sofort in ein leistungsstarkes natives Framework zu verschieben, denn das klingt ernst. Eine dritte besteht darin, wochenlang über die Architektur zu streiten, bevor überhaupt der Eingangsvertrag stabilisiert wird.
Der ausgereiftere Weg ist ruhiger. Zunächst definiert das Team sorgfältig die Vorverarbeitungs- und Ausgabesemantik. Anschließend wird die Exporttreue anhand repräsentativer Bilder getestet. Es wählt ONNX Runtime, da das Problem in der statischen Inferenz und nicht im Framework-gesteuerten Experimentieren liegt. Später wird für eine Edge-Variante mit strengeren Hardware-Einschränkungen bewertet, ob TensorFlow Lite oder ein aggressiver optimierter Laufzeitpfad für diesen Produktzweig sinnvoll ist. Wenn das Unternehmen Monate später eine lokale Assistentenfunktion hinzufügt, wird möglicherweise auch llama.cpp in die Architektur aufgenommen, nicht weil eine Bibliothek die ganze Debatte gewonnen hat, sondern weil jedes Tool seinen Platz in einer anderen Ecke des Systems verdient hat.
Das ist die tiefere Lektion hinter all diesen Bibliotheken. Ernsthafte KI-Technik belohnt selten Reinheit. Es belohnt Passform. Die beste Open-Source-Bibliothek ist nicht diejenige mit der größten Fangemeinde. Es ist diejenige, die es Ihrem Modell ermöglicht, Teil eines realen Systems zu werden, ohne den Rest des Systems dazu zu zwingen, unvernünftig zu werden.
Praktisches Labor: Erstellen Sie eine kleine ONNX-Laufzeit-CLI
Die Theorie wird überzeugender, wenn sie kompiliert wird.
Lassen Sie uns das kleinste nützliche native Inferenzprogramm in C++ erstellen. Das Ziel besteht nicht darin, ein Modell zu trainieren. Ziel ist es, mit eigenen Händen zu spüren, wie eine native Laufzeitgrenze aussieht.
Für diese Übung benötigen Sie:
- ein C++17-Compiler
- CMake
- ein vorgefertigtes ONNX Runtime-Paket aus den offiziellen Versionen
- jedes kleine
.onnx-Modell, dessen Eingabe ein Flat-Float-Tensor ist
Projektlayout
tiny-ort/
CMakeLists.txt
main.cpp
third_party/
onnxruntime/
model.onnx
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(tiny_ort LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(ORT_ROOT "${CMAKE_SOURCE_DIR}/third_party/onnxruntime")
add_executable(tiny_ort main.cpp)
target_include_directories(tiny_ort PRIVATE "${ORT_ROOT}/include")
if (WIN32)
target_link_directories(tiny_ort PRIVATE "${ORT_ROOT}/lib")
target_link_libraries(tiny_ort PRIVATE onnxruntime)
else()
target_link_directories(tiny_ort PRIVATE "${ORT_ROOT}/lib")
target_link_libraries(tiny_ort PRIVATE onnxruntime)
endif()
main.cpp
#include <onnxruntime_cxx_api.h>
#include <array>
#include <iostream>
#include <numeric>
#include <vector>
int main() {
Ort::Env env{ORT_LOGGING_LEVEL_WARNING, "tiny-ort"};
Ort::SessionOptions opts;
opts.SetIntraOpNumThreads(1);
opts.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED);
const ORTCHAR_T* model_path = ORT_TSTR("model.onnx");
Ort::Session session{env, model_path, opts};
std::vector<int64_t> shape{1, 4};
std::vector<float> input{0.25f, 0.50f, 0.75f, 1.0f};
auto mem_info = Ort::MemoryInfo::CreateCpu(
OrtArenaAllocator,
OrtMemTypeDefault
);
Ort::Value tensor = Ort::Value::CreateTensor<float>(
mem_info,
input.data(),
input.size(),
shape.data(),
shape.size()
);
const char* input_names[] = {"input"};
const char* output_names[] = {"output"};
auto outputs = session.Run(
Ort::RunOptions{nullptr},
input_names,
&tensor,
1,
output_names,
1
);
float* out = outputs[0].GetTensorMutableData<float>();
auto out_shape = outputs[0].GetTensorTypeAndShapeInfo().GetShape();
auto out_count = std::accumulate(
out_shape.begin(),
out_shape.end(),
int64_t{1},
std::multiplies<int64_t>{}
);
std::cout << "Output values:\n";
for (int64_t i = 0; i < out_count; ++i) {
std::cout << " [" << i << "] = " << out[i] << "\n";
}
return 0;
}
Bauen
Unter Linux oder macOS:
cmake -S . -B build
cmake --build build -j
./build/tiny_ort
Unter Windows mit MSVC:
cmake -S . -B build
cmake --build build --config Release
.\build\Release\tiny_ort.exe
Was Sie daraus lernen
Dieses kleine Projekt zwingt Sie bereits dazu, sich mit mehreren Produktionsrealitäten auseinanderzusetzen:
- wo die Laufzeit lebt
- wie native Abhängigkeiten verpackt werden
- was Tensornamen und -formen eigentlich sind
- wie sich die explizite Speicherverarbeitung in einer nativen Inferenzgrenze anfühlt
Genau darum geht es. Eine Bibliothek ist kein Marketingbegriff mehr, sondern eine technische Entscheidung.
Testaufgaben für Enthusiasten
Wenn Sie den Artikel in ein Wochenendlabor verwandeln möchten, finden Sie hier nützliche nächste Schritte:
- Ersetzen Sie den hartcodierten Eingabevektor durch Werte, die aus einer kleinen Text- oder Binärdatei geladen werden.
- Drucken Sie Eingabe- und Ausgabetensorformen dynamisch, anstatt sie anzunehmen.
- Fügen Sie eine einfache Latenzmessung um
session.Runhinzu und vergleichen Sie1,2und4Intra-Op-Threads. - Tauschen Sie ONNX Runtime gegen LibTorch in einer ähnlichen Spielzeug-Inferenz-App aus und notieren Sie, was einfacher und was schwerer wurde.
- Exportieren Sie ein kleines Modell aus Python, laden Sie es in dieses C++-Programm und stellen Sie sicher, dass Vorverarbeitungsunterschiede das Ergebnis nicht stillschweigend ändern.
Wenn Sie diese fünf Aufgaben ehrlich erledigen, werden Sie mehr über die KI-Bereitstellung verstehen als viele Leute, die eine Stunde lang Framework-Namen aufsagen können.
Zusammenfassung
Open-Source-Bibliotheken für neuronale Netzwerke für C++ marschieren nicht in einer Parade. Sie sind aus unterschiedlichen technischen Anforderungen entstanden und bleiben am nützlichsten, wenn wir diese Ursprünge respektieren. ONNX Runtime ist leistungsstark, weil es das Problem eingrenzt und Produktionsteams eine stabile Inferenzgrenze bietet. LibTorch ist wertvoll, wenn die native Anwendung wirklich in Tensoren und Modulen denken muss, anstatt nur einen eingefrorenen Graphen zu nutzen. oneDNN und OpenVINO sind wichtig, wenn geringe Effizienz und die Bereitstellung auf bestimmten Hardwarefamilien keine zweitrangigen Anliegen mehr sind. TensorFlow Lite glänzt, wenn das Gerät selbst die harte Einschränkung darstellt. llama.cpp ist wichtig, weil es sehr öffentlich bewiesen hat, dass sorgfältiges natives Engineering moderne Sprachmodelle in praktische lokale Komponenten und nicht in entfernte Dienste umwandeln kann.
Die beste Wahl ist daher selten die modischste. Es ist derjenige, der das ganze System ruhiger macht. Eine gute Laufzeit ist eine Laufzeit, die Ihr Team ohne Mythologie verstehen, vergleichen, profilieren, verpacken, testen und betreiben kann. Wenn Ingenieure sich für diesen Ort entscheiden, sieht Open-Source-KI nicht mehr wie ein verwirrender Zoo von Frameworks aus, sondern sieht aus wie das, was sie wirklich ist: eine Toolbox, die reich genug ist, um ernsthafte native Produkte zu unterstützen.
Referenzen
- ONNX Runtime C/C++ API: https://onnxruntime.ai/docs/api/c/index.html
- Offizielles ONNX-Projekt: https://onnx.ai/
- PyTorch C++-Frontend-Dokumentation: https://docs.pytorch.org/cppdocs/frontend.html
- Offizielle oneDNN-Dokumentation: https://uxlfoundation.github.io/oneDNN/
- OpenVINO-Dokumentation: https://docs.openvino.ai/
- LiteRT/TensorFlow Lite C++ API-Dokumente: https://ai.google.dev/edge/litert/api_docs/cc
- llama.cpp-Repository: https://github.com/ggml-org/llama.cpp
- ONNX Runtime GitHub-Repository: https://github.com/microsoft/onnxruntime
- PyTorch-Repository: https://github.com/pytorch/pytorch