Verwendung von Open-Source-Bibliotheken für neuronale Netze in C++

Verwendung von Open-Source-Bibliotheken für neuronale Netze in C++

Verwendung von Open-Source-Bibliotheken für neuronale Netze in C++

Einführung

Moderne KI gelangen oft über Python, Notebooks, Demo-Umgebungen und die verständliche Aufregung, ein Modell zum ersten Mal arbeiten zu sehen. 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. Die Produktion stellt Fragen, 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 KI als Ingenieurpersönlichkeiten mit Stärken, blinden Flecken und Betriebsannahmen betrachten. Am Ende besteht das Ziel darin, zu verstehen, wann ONNX Runtime, LibTorch, oneDNN, OpenVINO, TensorFlow Lite und llama.cpp helfen, wann jedes einzelne zu schwer wird, wann jedes einzelne 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 Bereitstellung von KI, der klar benannt werden sollte, denn sobald man ihn sieht, 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 ermöglicht, 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 dort am wichtigsten, wo sie notwendig ist, und dort ist es sehr schwierig, sie mit Rhetorik vorzutä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 der Weg des geringsten Widerstands ein Python-Dienst mit einem stabilen Bereitstellungsframework und sehr wenig nativem Code. Daran ist nichts Beschämendes. Wenn andererseits dasselbe Team Inferenz in eine latenzempfindliche C++-Desktopanwendung einbettet, an ein Edge-Gerät mit begrenzten Ressourcen versendet 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++ bleibt eine der ernstesten 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 unsere 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 Recherchephase bereits an anderer Stelle stattgefunden hat und nur noch die nüchterne Arbeit übrig bleibt, Schlussfolgerungen 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-Geschichte. 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 das C++ Gesicht eines vollständigen Deep-Learning-Frameworks. Das macht es schwerer, aber auch ausdrucksstärker. Wenn eine native Anwendung wirklich Tensor-Besitz, Modellkonstruktion, schulungsähnliche Manipulationen oder eine enge PyTorch-Semantik in Entwicklung und Produktion benötigt, ist LibTorch überzeugender als ONNX Runtime. Es liegt eine gewisse Ehrlichkeit darin, es zu wählen, wenn das Produkt tatsächlich ein Framework und keine Laufzeitgrenze benötigt. Das Gegenbeispiel ist ebenso wichtig. Teams verwenden manchmal LibTorch für einfache statische Schlussfolgerungen, 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 die Bibliothek, die Sie zu schätzen wissen, wenn CPU-Kernel, Speicherformate und Effizienz auf Bedienerebene wichtig genug werden, um direkte Aufmerksamkeit zu verdienen. Viele Teams nutzen es indirekt über Laufzeiten auf höherer Ebene, was oft sinnvoll ist. OpenVINO hingegen befindet sich an einer strategischeren Stelle. Es hilft Teams, denen eine Intel-orientierte Bereitstellung, Grafikoptimierung 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 völlig 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 ist sinnvoll, 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 hauptsächlich ein stabiles Modell verwendet und eine tragbare, wohlbegrenzte Inferenz benötigt, ist ONNX Runtime oft die ruhigste Antwort. Wenn das System selbst in der Sprache von Tensoren, Modulen und Rahmensemantik 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 explizit darum geht, ein lokales quantisiertes Sprachmodell in einer nativen Umgebung auszuführen, gehört llama.cpp 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 macht es einfacher, um zwei Uhr morgens über das gesamte System nachzudenken; Benchmark-Siege allein entscheiden nicht.

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 versagen, wenn die Grenzen rund um die Laufzeiten unklar 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 Produktion KI ist ein Zehn-Millisekunden-Diagramm, umgeben von sechzig Millisekunden vermeidbarem Kleber, 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. Gute C++ KI-Technik sorgt dafür, dass das System so ruhig ist, 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 Visionsprototyp beginnt. Die Demo ist stark genug, um interne Unterstützung zu gewinnen, und bald dreht sich das Gespräch um die Integration mit einem 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 statische Schlussfolgerungen und nicht Framework-gesteuerte Experimente sind. 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. Monate später, wenn das Unternehmen eine lokale Assistentenfunktion hinzufügt, kann llama.cpp auch in die Architektur eintreten, wenn jedes Tool seinen Platz in einer anderen Ecke des Systems verdient hat.

Das ist die tiefere Lektion hinter all diesen Bibliotheken. Seriöse 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 Runtime-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

Am Linux oder macOS:

cmake -S . -B build
cmake --build build -j
./build/tiny_ort

Am 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:

  1. Ersetzen Sie den hartcodierten Eingabevektor durch Werte, die aus einer kleinen Text- oder Binärdatei geladen werden.
  2. Drucken Sie Eingabe- und Ausgabetensorformen dynamisch, anstatt sie anzunehmen.
  3. Fügen Sie eine einfache Latenzmessung um session.Run hinzu und vergleichen Sie 1, 2 und 4 Intra-Op-Threads.
  4. Tauschen Sie ONNX Runtime gegen LibTorch in einer ähnlichen Spielzeug-Inferenz-App aus und schreiben Sie auf, was einfacher und was schwerer wurde.
  5. 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 Netze 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 tatsächlich Tensor- und Modulbesitz im gesamten Modellpfad benötigt. oneDNN und OpenVINO sind von Bedeutung, wenn geringe Effizienz und 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 es wirklich ist: eine Toolbox, die reichhaltig genug ist, um ernsthafte native Produkte zu unterstützen.

Referenzen

  1. ONNX Runtime C/C++ API: https://onnxruntime.KI/docs/api/c/index.html
  2. Offizielles ONNX-Projekt: https://onnx.KI/
  3. PyTorch C++ Frontend-Dokumentation: https://docs.pytorch.org/cppdocs/frontend.html
  4. oneDNN offizielle Dokumentation: https://uxlfoundation.github.io/oneDNN/
  5. OpenVINO Dokumentation: https://docs.openvino.KI/
  6. LiteRT / TensorFlow Lite C++ API Dokumente: https://KI.google.dev/edge/litert/api_docs/cc
  7. llama.cpp-Repository: https://github.com/ggml-org/llama.cpp
  8. ONNX Runtime GitHub-Repository: https://github.com/microsoft/onnxruntime
  9. PyTorch Repository: https://github.com/pytorch/pytorch

    Wie das aussieht, wenn das System bereits unter Druck steht

C++ KI Die Wahl der Laufzeit wird in der Regel genau in dem Moment dringend, in dem ein Team auf ein ruhigeres Quartal gehofft hat. Ein Feature steht den Kunden bereits zur Verfügung, oder eine Plattform weist bereits interne Abhängigkeiten auf, und das System hat diese bestimmte Woche ausgewählt, um zu offenbaren, dass seine elegante Theorie und sein Laufzeitverhalten höflich getrennte Leben geführt haben. Aus diesem Grund beginnt so viel ernsthafte Ingenieursarbeit mit der Versöhnung. Das Team muss das, was das System seiner Meinung nach tut, mit dem in Einklang bringen, was das System tatsächlich unter Last, bei Veränderungen und unter Fristen tut, die alle etwas kreativer und etwas weniger klug machen.

Bei der nativen KI-Bereitstellung sind in der Regel die Inferenz tragbarer Server, die Edge-Bereitstellung auf eingeschränkter Hardware und die Einbettung von Modellen in vorhandene native Produkte am wichtigsten. Solche Situationen haben technische, budgetäre, vertrauenswürdige, Roadmap- und manchmal auch Reputationsfolgen. Ein technisches Problem wird politisch größer, sobald mehrere Teams darauf angewiesen sind und niemand so recht erklären kann, warum es sich innerhalb der Mauern immer noch wie ein Waschbär verhält: nachts laut, schwer zu lokalisieren und teuer zu ignorieren.

Deshalb empfehlen wir, das Problem aus der Sicht des Betriebsdrucks und der Förderrealität zu betrachten. Ein Entwurf kann theoretisch schön und funktionell ruinös sein. Ein anderes Design kann fast langweilig sein und das Produkt dennoch jahrelang weiterbringen, weil es messbar, reparierbar und ehrlich in Bezug auf seine Kompromisse ist. Ernsthafte Ingenieure lernen, die zweite Kategorie zu bevorzugen. Es führt zu weniger epischen Reden, aber auch zu weniger Notfallrückblicken, bei denen jeder im Passiv spricht und sich niemand daran erinnert, wer die Abkürzung genehmigt hat.

Praktiken, die dauerhaft gut altern

Die erste dauerhafte Praxis besteht darin, einen repräsentativen Pfad ständig zu messen. Teams sammeln oft zu viele vage Telemetriedaten und zu wenig Entscheidungsqualitätssignale. Wählen Sie den Weg, der wirklich wichtig ist, messen Sie ihn wiederholt und lassen Sie die Diskussion nicht in dekoratives Geschichtenerzählen abdriften. Bei der Arbeit rund um die Laufzeitauswahl von C++ KI sind die nützlichen Messgrößen normalerweise Laufzeitanpassung, Integrationsreibung, Verpackungskosten und Steady-State-Latenz. Sobald diese sichtbar sind, werden die restlichen Entscheidungen menschlicher und weniger mystisch.

Die zweite dauerhafte Praxis besteht darin, Beweise von Versprechen zu trennen. Ingenieure werden oft unter Druck gesetzt, zu sagen, dass eine Richtung richtig ist, bevor das System zu dieser Schlussfolgerung gelangt ist. Widerstehen Sie diesem Druck. Erstellen Sie zunächst einen engen Beweis, insbesondere wenn es um Kunden oder Geld geht. Eine kleine verifizierte Verbesserung hat einen größeren kommerziellen Wert als ein großes, unbestätigtes Ziel. Das klingt offensichtlich, bis eine Überprüfung am Quartalsende eine Hypothese in eine Frist verwandelt und die gesamte Organisation anfängt, Optimismus wie ein Planungsartefakt zu behandeln.

Die dritte dauerhafte Praxis besteht darin, Empfehlungen in der Sprache des Eigentümers zu verfassen. Ein Absatz, in dem es heißt „Leistung verbessern“ oder „Grenzen stärken“, ist emotional angenehm und operativ nutzlos. Ein Absatz, der besagt, wer was in welcher Reihenfolge und mit welcher Rollback-Bedingung ändert, ist derjenige, der am Montagmorgen tatsächlich überlebt hat. Hier scheitern viele technische Texte. Es möchte mehr fortgeschritten klingen als planbar sein.

Gegenbeispiele, die Zeit sparen

Eines der häufigsten Gegenbeispiele sieht so aus: Das Team hat einen deutlichen lokalen Erfolg, geht davon aus, dass das System nun verstanden ist, und skaliert die Idee dann in eine viel anspruchsvollere Umgebung, ohne die Messdisziplin zu verbessern. Das ist das technische Äquivalent dazu, in einem Hotelpool schwimmen zu lernen und dann einen selbstbewussten TED-Vortrag über das Wetter auf See zu halten. Wasser ist Wasser, solange es keins mehr ist.

Ein weiteres Gegenbeispiel ist die Werkzeuginflation. Ein neuer Profiler, eine neue Laufzeit, ein neues Dashboard, ein neuer Agent, eine neue Ebene der Automatisierung, ein neuer Wrapper, der verspricht, den alten Wrapper zu harmonisieren. Keines dieser Dinge ist von Natur aus schlecht. Das Problem ist, was passiert, wenn von ihnen verlangt wird, eine Grenze zu kompensieren, die niemand klar benannt hat. Das System wird dann instrumentierter, eindrucksvoller und nur gelegentlich verständlicher. Das spüren Käufer sehr schnell. Sie formulieren es vielleicht nicht so, aber sie riechen, wenn ein Stapel zu einem teuren Ersatz für eine Entscheidung geworden ist.

Das dritte Gegenbeispiel besteht darin, die menschliche Überprüfung als einen Fehler der Automatisierung zu betrachten. In realen Systemen ist die menschliche Überprüfung oft die Kontrolle, die die Automatisierung kommerziell akzeptabel hält. Reife Teams wissen, wo sie aggressiv automatisieren und wo sie Genehmigungen oder Interpretationen sichtbar halten müssen. Unreife Teams möchten, dass die Maschine alles kann, weil „alles“ auf einer Folie effizient klingt. Dann kommt es zum ersten schwerwiegenden Vorfall, und plötzlich wird die manuelle Überprüfung mit der Aufrichtigkeit einer Konvertierungserfahrung wiederentdeckt.

Ein Liefermuster, das wir empfehlen

Wenn die Arbeit gut gemacht wird, sollte das erste Ergebnis Stress reduzieren, indem es dem Team eine technische Lektüre gibt, die stark genug ist, um nicht mehr im Kreis zu streiten. Danach sollte die nächste begrenzte Implementierung einen entscheidenden Pfad verbessern und der erneute Test sollte die Richtung sowohl für die Technik als auch für die Führung lesbar machen. Diese Reihenfolge ist wichtiger als die genaue Wahl des Werkzeugs, denn sie ist es, die technisches Können in Vorwärtsbewegung umsetzt.

In der Praxis empfehlen wir einen engen ersten Zyklus: Artefakte sammeln, eine eindeutige Diagnose erstellen, eine begrenzte Änderung versenden, den tatsächlichen Pfad erneut testen und die nächste Entscheidung im Klartext verfassen. Klare Sprache ist wichtig. Ein Käufer bereut die Klarheit selten. Ein Käufer bereut es oft, beeindruckt zu sein, bevor die Quittungen eintreffen.

Auch hier kommt es auf den Ton an. Starke technische Arbeit sollte so klingen, als hätte sie die Produktion schon einmal erlebt. Ruhig, präzise und ein wenig amüsiert über den Hype, anstatt davon genährt zu werden. Dieser Ton trägt das Betriebssignal. Es zeigt, dass das Team die alte Wahrheit der Systemtechnik versteht: Maschinen sind schnell, Roadmaps sind fragil und früher oder später kommt die Rechnung für jede Annahme, die poetisch bleiben durfte.

Die Checkliste, die wir verwenden würden, bevor wir dies als „fertig“ bezeichnen

Bei der nativen KI-Bereitstellung ist Bereitschaft keine Stimmung. Es ist eine Checkliste mit Konsequenzen. Bevor wir die Arbeit rund um die Laufzeitauswahl C++ KI für einen breiteren Rollout vorbereiten, möchten wir, dass ein paar Dinge bestmöglich langweilig werden. Wir wollen einen Pfad, der sich unter repräsentativer Last vorhersehbar verhält. Wir wollen eine Reihe von Messungen, die sich nicht widersprechen. Wir möchten, dass das Team weiß, wo die Grenze liegt und was es bedeuten würde, sie zu durchbrechen. Und wir möchten, dass das Ergebnis der Arbeit klar genug ist, dass jemand außerhalb des Implementierungsraums dennoch eine fundierte Entscheidung daraus treffen kann.

Die Checkliste, die wir verwenden würden, bevor wir dies als „fertig“ bezeichnen

Hier erfahren die Teams auch, ob sie das eigentliche Problem gelöst oder lediglich Kompetenzen in dessen allgemeiner Umgebung geübt haben. Viele technische Bemühungen erweisen sich als erfolgreich, bis jemand nach Wiederholbarkeit, Produktionsnachweisen oder einer Entscheidung fragt, die sich auf das Budget auswirkt. In diesem Moment verschwimmt das schwache Werk und das starke Werk wird seltsam deutlich. Einfach ist gut. „Einfach“ bedeutet normalerweise, dass das System nicht mehr auf Charisma setzt.

Wie wir empfehlen, über das Ergebnis zu sprechen

Die abschließende Erklärung sollte kurz genug sein, um eine Führungsbesprechung zu überstehen, und konkret genug, um eine technische Überprüfung zu überstehen. Das ist schwieriger als es klingt. Eine zu technische Sprache verbirgt die Reihenfolge. Eine zu vereinfachte Sprache birgt Risiken. Der richtige Mittelweg besteht darin, den Weg, die Beweise, die begrenzte Veränderung und den nächsten empfohlenen Schritt auf eine Weise zu beschreiben, die eher ruhig als triumphierend klingt.

Wir empfehlen einen solchen Aufbau. Sagen Sie zunächst, welcher Pfad bewertet wurde und warum er wichtig war. Zweitens: Sagen Sie, was an diesem Weg falsch oder unsicher war. Drittens sagen Sie, was geändert, gemessen oder validiert wurde. Viertens sagen Sie, was noch ungelöst ist und was die nächste Investition bringen würde. Diese Struktur funktioniert, weil sie sowohl die Technik als auch das Kaufverhalten respektiert. Ingenieure wollen Einzelheiten. Käufer wollen eine Reihenfolge. Jeder möchte weniger Überraschungen, auch die Leute, die so tun, als würden sie sie genießen.

Der verborgene Vorteil dieser Art des Sprechens ist kultureller Natur. Teams, die technische Arbeiten klar erklären, führen diese in der Regel auch klarer aus. Sie hören auf, Mehrdeutigkeit als Raffinesse zu betrachten. Es wird schwieriger, sie mit Fachjargon zu beeindrucken und ihnen bei schwierigen Systemen leichter zu vertrauen. Das ist eine der am meisten unterschätzten Formen der technischen Reife.

Was wir immer noch nicht fälschen würden

Selbst nachdem das System verbessert wurde, behalten erfahrene Teams die Unsicherheit bei der nativen KI-Bereitstellung bei. Schwache Messungen erfordern klarere Beweise, harte Grenzen erfordern eine klare Sprache und ruhigere Demos erfordern echte Einsatzbereitschaft. Eine gewisse Unsicherheit muss verringert werden; einige müssen ehrlich benannt werden. Durch die Verwechslung dieser beiden Berufe werden seriöse Projekte zu teuren Gleichnissen.

Die gleiche Regel gilt für Entscheidungen rund um die Laufzeitauswahl C++ KI. Wenn einem Team immer noch ein reproduzierbarer Benchmark, ein vertrauenswürdiger Rollback-Pfad oder ein klarer Eigentümer für die kritische Schnittstelle fehlt, dann ist das nützlichste Ergebnis möglicherweise ein schärferes Nein oder ein engerer nächster Schritt statt eines größeren Versprechens. Diese Disziplin sorgt dafür, dass die technische Arbeit mit der Realität in Einklang steht, die sie verbessern soll.

Es ist eine seltsame Erleichterung, auf diese Weise zu arbeiten. Sobald das System nicht mehr auf optimistisches Storytelling angewiesen ist, wird das technische Gespräch einfacher, auch wenn die Arbeit weiterhin hart bleibt. Und in der Produktion gilt das oft als Nebensache.

Feldnotizen aus einer echten technischen Überprüfung

Bei der Bereitstellung von C++-Systemen wird die Arbeit ernst, wenn die Demo auf echte Lieferung, echte Benutzer und echte Betriebskosten trifft. Das ist der Moment, in dem eine ordentliche Idee anfängt, sich wie ein System zu verhalten, und Systeme haben bekanntermaßen einen trockenen Sinn für Humor. Es ist ihnen egal, wie elegant das Kickoff-Deck aussah. Sie kümmern sich um Grenzen, Fehlermodi, Rollout-Pfade und darum, ob irgendjemand den nächsten Schritt erklären kann, ohne eine neue Mythologie rund um den Stack zu erfinden.

Für Using Open-Source Libraries for Neural Networks in C++ stellt sich in der Praxis die Frage, ob es einen stärkeren Lieferweg für einen Käufer schafft, der bereits Druck auf eine Roadmap, eine Plattform oder eine Sicherheitsüberprüfung hat. Dieser Käufer braucht keinen in Nebel gehüllten Vortrag. Sie benötigen eine technische Lektüre, die sie verwenden können.

Was wir zuerst inspizieren würden

Wir würden mit einem repräsentativen Pfad beginnen: native Inferenz, Profilierung, HFT-Pfade, DEX-Systeme und C++/Rust-Modernisierungsoptionen. Dieser Weg sollte schmal genug sein, um ihn zu messen, und breit genug, um die Wahrheit ans Licht zu bringen. Der erste Durchgang sollte das Zuordnungsverhalten, die p99-Latenz, den Profilnachweis, die ABI-Reibung und das Release-Vertrauen erfassen. Wenn diese Signale nicht verfügbar sind, handelt es sich bei dem Projekt größtenteils immer noch um eine Meinung, die einen Laborkittel trägt, und die Meinung hat eine lange Tradition darin, sich selbst als Strategie darzustellen.

Das erste nützliche Artefakt ist ein natives System-Read mit Benchmarks, Profiling-Beweisen und einem zielgerichteten Implementierungsplan. Es sollte das System so zeigen, wie es sich verhält, und nicht so, wie alle gehofft hatten, dass es sich in der Planungsbesprechung verhalten würde. Ein Trace, eine Wiederholung, ein kleiner Benchmark, eine Richtlinienmatrix, ein Parser-Fixture oder ein wiederholbarer Test erzählen die Geschichte oft schneller als eine andere abstrakte Architekturdiskussion. Gute Artefakte sind wunderbar unhöflich. Sie unterbrechen das Wunschdenken.

Ein Gegenbeispiel, das Zeit spart

Der kostspielige Fehler besteht darin, mit einer Lösung zu antworten, die größer ist als der erste nützliche Beweis. Ein Team erkennt Risiken oder Verzögerungen und greift sofort nach einer neuen Plattform, einer Neufassung, einem umfassenden Refactoring oder einem beschaffungsfreundlichen Dashboard mit einem Namen, der nach Yoga klingt. Manchmal ist dieser Maßstab gerechtfertigt. Sehr oft ist es eine Möglichkeit, die Messung zu verschieben.

Der bessere Zug ist kleiner und schärfer. Benennen Sie die Grenze. Erfassen Sie Beweise. Ändern Sie eine wichtige Sache. Testen Sie denselben Pfad erneut. Entscheiden Sie dann, ob die nächste Investition eine größere Investition verdient. Dieser Rhythmus ist weniger dramatisch als ein Transformationsprogramm, aber er übersteht tendenziell den Kontakt mit Budgets, Veröffentlichungskalendern und Produktionsvorfällen.

Das von uns empfohlene Liefermuster

Das zuverlässigste Muster besteht aus vier Schritten. Sammeln Sie zunächst repräsentative Artefakte. Zweitens: Verwandeln Sie diese Artefakte in eine konkrete technische Diagnose. Drittens: Veröffentlichen Sie eine lokale Änderung oder einen Prototyp. Viertens wiederholen Sie den Test mit demselben Messrahmen und dokumentieren Sie die nächste Entscheidung im Klartext. In dieser Art von Arbeit sind CMake-Vorrichtungen, Profilierungs-Harnesses, kleine native Repros und Compiler-/Laufzeitnotizen normalerweise wertvoller als ein weiteres Treffen über die allgemeine Richtung.

Klare Sprache ist wichtig. Ein Käufer sollte in der Lage sein, die Ausgabe zu lesen und zu verstehen, was sich geändert hat, was weiterhin riskant ist, was warten kann und was der nächste Schritt bewirken würde. Wenn die Empfehlung nicht geplant, getestet oder einem Eigentümer zugeordnet werden kann, ist sie immer noch zu dekorativ. Dekoratives technisches Schreiben ist angenehm, aber Produktionssysteme sind nicht dafür bekannt, Angenehmes zu belohnen.

So beurteilen Sie, ob das Ergebnis geholfen hat

Für Open-Source Neural Network Libraries in C++: ONNX Runtime, LibTorch, oneDNN, OpenVINO, TFLite, llama.cpp sollte das Ergebnis mindestens eines von drei Dingen verbessern: Liefergeschwindigkeit, Systemvertrauen oder kommerzielle Bereitschaft. Wenn dadurch keine Verbesserung erzielt wird, hat das Team vielleicht etwas gelernt, aber der Käufer hat noch kein brauchbares Ergebnis erhalten. Diese Unterscheidung ist wichtig. Lernen ist edel. Auch ein bezahltes Engagement soll das System bewegen.

Das stärkste Ergebnis kann eine engere Roadmap, die Weigerung, einen gefährlichen Weg zu automatisieren, eine bessere Abgrenzung eines Modells, eine sauberere native Integration, ein maßvoller Beweis dafür, dass eine Neufassung noch nicht erforderlich ist, oder eine kurze Abhilfeliste sein, die die Führung tatsächlich finanzieren kann. Seriöse Ingenieurskunst ist eine Abfolge besserer Entscheidungen, kein Kostümwettbewerb um Werkzeuge.

Wie SToFU es angehen würde

SToFU würde dies zunächst als Lieferproblem und dann als Technologieproblem behandeln. Wir würden die erforderliche technische Tiefe einbringen, aber das Engagement auf Fakten gründen: dem Weg, der Grenze, dem Risiko, der Messung und der nächsten Änderung, die es wert ist, vorgenommen zu werden. Es geht nicht darum, harte Arbeit einfach klingen zu lassen. Es geht darum, den nächsten ernsthaften Schritt so deutlich zu machen, dass er ausgeführt werden kann.

Das ist der Teil, den Käufer normalerweise am meisten schätzen. Sie können überall Meinungen einholen. Was sie brauchen, ist ein Team, das das System inspizieren, die tatsächliche Einschränkung benennen, den richtigen Slice erstellen oder validieren und Artefakte zurücklassen kann, die die Verwirrung nach Ende des Anrufs verringern. In einem lauten Markt ist Klarheit keine Soft Skills. Es ist Infrastruktur.

Philip P.

Philip P. – CTO

Zurück zu Blogs

Kontakt

Gespräch starten

Ein paar klare Zeilen genügen. Beschreiben Sie das System, den Druck, die blockierte Entscheidung. Oder schreiben Sie direkt an midgard@stofu.io.

0 / 10000
Keine Datei ausgewählt