HN 日本語サマリー

← 一覧へ戻る
AI・機械学習

なぜ自社でCおよびC++推論エンジンを開発するのか

Why we write our own C and C++ inference engines (localai.io)

100 pointsby eatonphil40 コメント

要約

LocalAIは、既存のPythonベースの推論エンジンをラップするのではなく、独自のC/C++推論エンジンを開発する理由を説明しています。これにより、デプロイサイズの劇的な削減、メモリ使用量の予測可能性、そして多くの場合、同等またはそれ以上のパフォーマンスを実現します。このアプローチは、大規模なPython依存関係やCUDA専用スタックを回避し、ポータビリティを向上させます。

全文翻訳

ほとんどのLocalAIバックエンドは他者のエンジンをラップしており、それが正しいデフォルトです。llama.cpp、vLLM、whisper.cpp、stable-diffusion、MLXなどは、私たちよりもそれらのモデルに詳しい人々によってメンテナンスされており、それらをラップするにはDockerfileとgRPCシムのコストがかかります。 私たちのバックエンドの18個は何もラップしていません。これらは、私たちがゼロから作成したCまたはC++のポートであり、それぞれが、アップストリームエンジンをラップすることが、私たちが提供できないもの(数ギガバイトのPythonインストール、ポータブルでないCUDA専用スタック、またはC++実装が全くないモデル)を提供することになるため存在します。この記事では、それらのポートが何をもたらし、測定され、そして何がコストであるかについて説明します。 得られるもの:1つのファイルと、予測可能なメモリ Python推論スタックをデプロイすることは、ターゲットマシン上のCUDAとglibcが何であれ、インストール時に依存関係ツリーを解決することを意味します。ggmlポートをデプロイすることは、共有ライブラリとGGUFファイルをコピーすることを意味します。 その違いの最も明確な測定値は、vLLMのV1サービングアーキテクチャのC++20ポートであるvllm.cppです。vLLMをインストールすると、9.1 GiBの仮想環境が生成されます。vllm.cppをインストールすると、66 MiBのバイナリが生成されます。このエンジンは、Python、PyTorch、ggmlなしで推論を行うため、ページ化されたKVキャッシュ、連続バッチ処理、プレフィックスキャッシュ、スケジューラ、サンプラーなど、Pythonオリジナルのものと同じ機能を実装しています。 明白な疑問は、スループットでそれが何を犠牲にするかということです。NVIDIA GB10でQwen3.6-27BをNVFP4で、グリッドサーチ、クローズドループで、vLLMのプロダクショングラフ設定(--enforce-eagerではなく)と比較した場合: Concurrency | 1 | 24 | 8 | 16 | 32 vllm.cpp tok/s | 86.05 | 159.68 | 292.34 | 508.77 | 801.76 vLLM tok/s | 82.32 | 158.03 | 290.31 | 505.46 | 789.16 Ratio | 1.045x | 1.011x | 1.007x | 1.007x | 1.016x 私たちは6つのポイントすべてで先行しており、そのうち5つは同点です。私たちの実行ごとのノイズバンドは0.5%であり、同時実行数2から32は0.7%から1.7%の間にあるため、正直な読み取りは、単一ストリームケース(4.5%)のみがノイズを明確に外れているということです。出力は、そのカーブ上のすべてのポイントでvLLMとトークン単位で同一です。ピークホストメモリは28.18 GiBに対して24.88 GiBです。 成熟したCUDAスタックとの同点は、66 MiBのバイナリとしては良い結果であり、フットプリントの節約がスループットで支払われていないことを意味します。同じGGUFファイルからCPU上のllama.cppと比較すると、プリフィルは1.18倍高速(223.8対177.3 tok/s)であり、デコードはllama.cpp自身のスプレッド内で同点であり、トークンはグリッドサーチデコードとバイト単位で同一です。Apple M4上のMLX-LMと比較すると、最初のトークンまでのプリフィル時間は1.5%先行し、ウォームトータルスループットはMLX-LMの97.6%であり、実際の2.4%のギャップはデコードにのみ存在します。 時にはポートが単純に速い depth-anything.cppは、ByteDanceのDepth Anything 3のポートであり、1枚の普通の写真からメートル単位の深度、ピクセルごとの信頼度、カメラの内部・外部パラメータ、およびバックプロジェクションされた点群を提供します。CPU上では、同じモデルを実行するPyTorchよりも高速です。 Engine | Quant | Model | MBLoad ms | Infer ms | Peak RAM MB vs PyTorch | PyTorch | f32 | 5167 | 494 | 16.913 C++/ggml | q8_0 | 1424 | 031 | 9.436 | 31.31x 同じモデルで、1.31倍の速度、27%のメモリ、そしてRyzen 9 9950X3Dを504x336で16スレッドで使用した場合、ロードは749ミリ秒ではなく40ミリ秒で完了します。量子化されたq4_kビルドは99 MBのファイルで、ほぼロスレスのままです。出力は、37のパリティテスト全体で、コンポーネントごとにリファレンスフォワードパスと1.0で相関します。 それが速い理由は、PyTorchよりも優れたmatmulカーネルを書いていることとは関係ありません。2つの位置埋め込み、DPTヘッドのUV埋め込みとバックボーンのバイキュービック位置埋め込みは、入力ジオメトリにのみ依存し、呼び出しごとに同一であるにもかかわらず、シングルスレッドのスカラーsin、cos、バイキュービックループで、各フォワードパスで再計算されていました。それらをキャッシュすることで、フォワードごとに約95ミリ秒のホストサイドオーバーヘッドが削除され、ギャップの大部分が解消されました。PyTorchは、ベクトル化された操作で同じ埋め込みを構築し、そのコストを支払うことはありませんでした。 これらは、これらの勝利の一般的な形状です。重いGEMMは、誰もが同じクラスのBLASカーネルを呼び出しているため、ほぼ同等です。違いは、Pythonリファレンス実装が決して最適化しなかったホストサイドの作業と、推論を実行するためのインタープリタとフレームワークをロードしないことにあります。GPU上では、状況は再び同点に戻ります。ggml CUDAバックエンドとフラッシュアテンションをGB10で使用すると、depth-anything.cppはPyTorchのチューニングされたcuDNNと47.3ミリ秒/フォワードで同点になり、コールドスタートのみで勝利し、ロード時間は1.75倍から2.9倍速くなります。 同点がゲート、スピードがフォローアップ face-detect.cppとvoice-detect.cppは、LocalAIのPython insightfaceおよびspeaker-recognitionバックエンドを置き換えました。どちらもCPUでの速度勝ちは主張しないケースであり、それでも出荷されました。 face-detect.cppは、Pythonやonnxruntimeなしで、単一の自己完結型GGUFから、SCRFD検出、112x112への5つのランドマーク類似変換アライメント、およびArcFace埋め込みという、insightfaceの完全なチェーンを実行します。検出器ボックスとランドマークは、1ピクセル以内でinsightfaceと一致し、認識埋め込みは、任意のCPUスレッド数で保持されたまま、コサイン1.000000で一致します。CPU上では、onnxruntimeよりも遅いです。SCRFD検出は、1スレッドで約0.83倍、8スレッドで0.69倍で実行され、ArcFace埋め込みは約0.61倍および0.84倍で実行されます。onnxruntimeのMLAS畳み込みカーネルはFMAポートのピークに位置しており、カスタムAVX2 Winogradパスはギャップを狭めましたが、閉じることはありませんでした。GPU上では、同じ畳み込みをcuDNN経由でルーティングすると、SCRFDは14.8ミリ秒から6.4ミリ秒になり、torch-cuDNNと同点になります。 voice-detect.cppは、メモリ結果が付随する同じ話です。WeSpeaker検証は、CPU専用のPython、torch、onnxruntimeパスの約334 MBに対して、私たちのバイナリでは約62 MBでピークとなり、約5.4倍低く、判定と埋め込みコサインは1.000000で同一です。CPU上でのエンドツーエンドでは、両者はモデルとスレッド数によってリードを交換しながら、10%から15%の範囲で互角であり、GPU上では畳み込みエンコーダーはリファレンスと同等です。 生体認証パイプラインでは、リファレンスと完全に一致することが、それよりも速いことよりも重要です。4番目の小数点で異なる埋め込みは、しきい値での検証決定を変更し、デプロイメント内のすべての登録済みテンプレートを再計算する必要があります。同点が、置き換えを移行ではなくドロップインにするものです。 方法 すべてのポートは同じシーケンスに従い、その順序が重要です。 まず、重みを1つのGGUFに変換します。これには、トークナイザー、ボキャブラリー、および任意の補助モデルを埋め込みます。これにより、モデルのデプロイはファイルのコピーになります。 次に、グラフをポートし、コンポーネントごとに、元の実装からダンプされたリファレンステンソルに対してゲート処理します。depth-anything.cppには、前処理、バックボーン、アテンション、DPTヘッド、深度、ポーズ、レイヘッド、レイからポーズへのソルバー、およびエクスポートをカバーする37のctestケースがあります。parakeet.cppは、WER 0でNeMoとのトランスクリプト合意でゲート処理します。face-detect.cppは、ピクセル単位のボックスとランドマークの距離、および埋め込みコサインでゲート処理します。高速でわずかに間違っているポートは無価値であり、コンポーネントごとのゲートなしでは、数か月後に間違っていることが判明します。 3番目に、プロファイラーを使用して最適化します。これは、同点が保持された後のみです。parakeet.cppでは、決定的な勝利は、トランスデューサーデコード時間の97%を占め、ほとんどが冗長であった予測ネットワークLSTMフォワードパスのキャッシュでした。depth-anything.cppでは、2つのキャッシュされた位置埋め込みでした。どちらもカーネルの書き直しではなく、動作するベースラインなしでは見つけられなかったでしょう。 最後に、フラットなC ABIを公開します。LocalAIはpuregoを介して共有ライブラリをdlopenし、そのABIを直接呼び出すため、サブプロセス、PythonサーバーへのgRPCホップ、およびサービングパスでのインタープリタはありません。 コスト 主にメンテナンスです。各エンジンは、独自のCI、独自のベンチマークスイート、独自のGGUF変換スクリプト、および独自のパリティベースラインを持つリポジトリであり、アップストリームはコンバーター作業が必要な新しいチェックポイントをリリースし続けています。 GPUカーネルは弱点です。ggmlの汎用CUDA畳み込みおよびアテンションカーネルは、畳み込みが多いモデルではNVIDIAのチューニングされたcuDNNに劣ります。これが、face-detect.cppが