nVidia NPU と GPU とは結局、何が違うのか？ 

# AIで利用される nVidia NPU とはなにか？GPUとの違いは？

近年、AIの進化とともに、**NPU（Neural Processing Unit）**という言葉を耳にする機会が増えました。この記事では、特にnVidia製品に焦点を当てて、NPUとは何か、GPUとの違いは何かについてわかりやすく解説します。

---

## ✅ NPUとは？

**NPU（ニューラル・プロセッシング・ユニット）**は、その名のとおり、**ニューラルネットワークの処理に特化した専用プロセッサ**です。

### 主な特徴：

- 行列演算に最適化されたアーキテクチャ  
- 高速かつ省電力でAI推論が可能  
- 特定用途（画像認識、音声認識など）に最適化

特にエッジAI（スマートフォン、車載機器、IoT端末など）においては、CPUやGPUよりも**低消費電力で高効率な推論処理**が求められるため、NPUの導入が進んでいます。

---

## ✅ nVidiaにおけるNPUの位置づけ

nVidiaのAI関連製品において、「NPU」という言葉はあまり前面には出てきません。しかし、実質的にNPU的な役割を果たしているコンポーネントがあります。

### 代表的なもの：

- **Tensor Cores（テンソルコア）**  
  → Volta世代（Tesla V100など）から搭載された、**行列乗算に特化したGPU内の演算ユニット**

- **Deep Learning Accelerator（DLA）**  
  → Jetsonシリーズなどに搭載されている、**AI推論専用の軽量NPU**。CPUやGPUの負荷を下げ、省電力に貢献。

---

## ✅ GPUとの違い

| 項目               | GPU                              | NPU（またはDLA）                  |
|------------------|----------------------------------|-----------------------------------|
| 主な用途           | 汎用的な並列演算（CG描画、AI学習など） | AI推論（特化用途）                  |
| 最適化対象         | 幅広い用途（グラフィックス・科学技術演算） | ニューラルネットワークの推論処理     |
| 柔軟性             | 高い（CUDAなどで汎用コードも実行可能） | 低い（特定モデル専用の命令セット）    |
| 消費電力           | 高め                             | 低め                                |
| レイテンシ         | 比較的高い                       | 極めて低い                          |
| nVidia製品の例     | RTX, Tesla, A100 など             | Jetson Orin の DLA, Grace Hopper など |

---

## ✅ どちらがAIに向いているのか？

用途に応じて使い分けることが重要です。

- **AI学習（トレーニング）** → 高度な行列演算が必要：**GPU（特にTensor Core）**
- **AI推論（インファレンス）** → 省電力かつ高速な応答が重要：**NPU / DLA**

たとえば、クラウド側ではA100やH100などのハイエンドGPUで学習を行い、エッジデバイスではJetson Orinに搭載されたDLAで推論する、といった使い分けが行われます。

---

## ✅  NPU vs GPU まとめ

- NPUはAI推論に特化したプロセッサ
- nVidia製品では、Tensor CoreやDLAがNPU的な役割を果たす
- GPUは学習、NPUは推論での活躍が期待される
- 用途やデバイスの制約に応じて使い分けることが大切

---

# NPUを利用したプログラムを書きたい。nVidiaのSDKは？

AI推論を高速・省電力で実行するために注目されている **NPU（Neural Processing Unit）**。nVidia製のJetsonシリーズなどに搭載されているNPU（たとえばDLA）を使ったプログラムを書くには、専用のSDKやツールが必要です。

この記事では、nVidia製NPUを活用するためのSDKや開発フローをわかりやすくまとめます。

---

## ✅ nVidia NPU（DLA）で使える主要SDK

### 1. JetPack SDK

Jetsonシリーズ（Jetson Nano / Xavier / Orinなど）向けに提供される**統合ソフトウェアパッケージ**です。

- CUDA / cuDNN / TensorRT / OpenCV などが同梱
- 開発ツールやサンプルコード、デバッグ環境も一式揃っている

🔗 [JetPack SDK - nVidia 公式](https://developer.nvidia.com/embedded/jetpack)

---

### 2. TensorRT

nVidiaが提供する **AI推論最適化ライブラリ** です。

- PyTorch や TensorFlow で学習したモデルを推論向けに最適化
- DLA（Deep Learning Accelerator）に処理を割り当て可能
- FP16 や INT8 最適化にも対応

#### 💡 DLAを指定して使う例（擬似コード）：

```cpp
builder->setDLACore(0); // DLA Core 0 に割り当て
```

🔗 [TensorRT - nVidia 公式](https://developer.nvidia.com/tensorrt)

---

## ✅ 開発の流れ（Jetson + DLAを活用する場合）

以下の手順で、nVidiaのNPUを使った推論が可能です。

1. PyTorchやTensorFlowでモデルを学習
2. モデルを **ONNX形式** に変換
3. TensorRTで最適化（`trtexec` などを利用）
4. DLAに割り当てて推論を実行

---

## ✅ その他の主要NPUとSDK対応

nVidia以外の代表的なNPUと、それに対応するSDKは以下のとおりです。

| メーカー  | デバイス例                  | SDK・ツール名                     |
|-----------|-----------------------------|----------------------------------|
| Google    | Coral Edge TPU              | Edge TPU SDK / PyCoral           |
| Intel     | Myriad X / Neural Compute   | OpenVINO                         |
| Apple     | Apple Neural Engine (ANE)   | Core ML / Create ML              |
| MediaTek  | Dimensityシリーズ           | NeuroPilot SDK                   |
| Huawei    | Ascend シリーズ             | MindSpore / Ascend Toolkit       |

---

## ✅ 注意点とTips

- nVidiaのDLAは**推論専用**で、学習には使えません
- TensorRTでは、**サポート外のレイヤー構成**は自動的にGPUにフォールバックされます
- JetPackとTensorRTのバージョンは**互換性が重要**なので、必ず対応表を確認しましょう

---

## ✅ SDK まとめ

- JetsonなどnVidia製NPUを使うには **JetPack SDK + TensorRT** が王道
- 推論エンジンとしてTensorRTを使い、**DLAコアを明示的に指定**することでNPUを活用可能
- モデル変換にはONNX形式が広く使われており、PyTorchやTensorFlowから簡単に移行可能

---

# Jetson Orin での推論サンプルコード（TensorRT + ONNX）

このサンプルでは、Jetson Orin 上で TensorRT を用いて ONNX モデルを推論する基本的な C++ コードを紹介します。  
ONNX モデルはすでに変換済みであることを前提としています（例: `model.onnx`）。

---

## ✅ 環境準備（JetPack SDK がインストール済みであること）

```bash
sudo apt update
sudo apt install -y libnvinfer8 libnvinfer-dev libnvinfer-plugin8                     libnvonnxparsers8 libnvparsers8 libnvinfer-bin
```

---

## ✅ C++ サンプルコード（推論）

```cpp
#include <NvInfer.h>
#include <NvOnnxParser.h>
#include <cuda_runtime_api.h>
#include <fstream>
#include <iostream>
#include <vector>
using namespace nvinfer1;

class Logger : public ILogger {
public:
    void log(Severity severity, const char* msg) noexcept override {
        if (severity <= Severity::kINFO) std::cout << msg << std::endl;
    }
};

int main() {
    Logger logger;
    IBuilder* builder = createInferBuilder(logger);
    const auto explicitBatch = 1U << static_cast<uint32_t>(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
    INetworkDefinition* network = builder->createNetworkV2(explicitBatch);
    auto parser = nvonnxparser::createParser(*network, logger);

    if (!parser->parseFromFile("model.onnx", static_cast<int>(ILogger::Severity::kINFO))) {
        std::cerr << "Failed to parse ONNX model." << std::endl;
        return -1;
    }

    IBuilderConfig* config = builder->createBuilderConfig();
    config->setMaxWorkspaceSize(1 << 20);  // 1MB

    // ✅ DLAコアを使用したい場合は以下を有効にする
    if (builder->getNbDLACores() > 0) {
        config->setFlag(BuilderFlag::kGPU_FALLBACK);
        config->setDefaultDeviceType(DeviceType::kDLA);
        config->setDLACore(0);  // DLAコア0を指定
    }

    ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config);
    if (!engine) {
        std::cerr << "Failed to build engine." << std::endl;
        return -1;
    }

    std::cout << "Successfully built TensorRT engine." << std::endl;

    engine->destroy();
    network->destroy();
    config->destroy();
    builder->destroy();
    parser->destroy();

    return 0;
}
```

---

## ✅ ビルド方法（CMakeなしの場合）

```bash
g++ -std=c++17 -o infer_sample infer_sample.cpp \
    -I/usr/include/aarch64-linux-gnu \
    -lnvinfer -lnvonnxparser -lcudart
```

---

## ✅ 実行

```bash
./infer_sample
```

---

## ✅ 補足

- ONNXモデルの事前準備は、PyTorch → ONNX 変換などで済ませてください
- 入出力テンソルの設定、バッファの確保などはこの後の実装に含まれます
- 上記は最小構成の例で、実運用では `IExecutionContext` を使って推論を行います

---


# Jetson Orin での推論処理（バッファ確保 → 推論実行 → 出力取得）

前回のエンジン生成に加えて、ここでは **実際の推論処理部分** を含むコードを紹介します。

---

## ✅ 推論処理に必要な手順

1. エンジンの読み込みと ExecutionContext の作成  
2. 入出力バッファの確保（CPU側とGPU側）  
3. 入力データの転送 → 推論実行 → 出力データの取得  
4. 結果の確認と後処理

---

## ✅ 推論処理の実装（C++）

```cpp
#include <NvInfer.h>
#include <cuda_runtime_api.h>
#include <fstream>
#include <iostream>
#include <vector>
#include <cassert>
using namespace nvinfer1;

// ユーティリティ関数：ファイルからエンジンを読み込む
ICudaEngine* loadEngine(const std::string& engineFile, ILogger& logger) {
    std::ifstream file(engineFile, std::ios::binary);
    if (!file) return nullptr;

    file.seekg(0, file.end);
    size_t size = file.tellg();
    file.seekg(0, file.beg);
    std::vector<char> buffer(size);
    file.read(buffer.data(), size);

    IRuntime* runtime = createInferRuntime(logger);
    return runtime->deserializeCudaEngine(buffer.data(), size);
}

int main() {
    Logger logger;

    // 1. エンジンのロードと ExecutionContext の作成
    ICudaEngine* engine = loadEngine("model.trt", logger);
    assert(engine);
    IExecutionContext* context = engine->createExecutionContext();
    assert(context);

    // 2. 入出力インデックスとサイズ
    int inputIndex = engine->getBindingIndex("input");
    int outputIndex = engine->getBindingIndex("output");
    auto inputDims = engine->getBindingDimensions(inputIndex);
    auto outputDims = engine->getBindingDimensions(outputIndex);
    size_t inputSize = 1;
    size_t outputSize = 1;
    for (int i = 0; i < inputDims.nbDims; ++i) inputSize *= inputDims.d[i];
    for (int i = 0; i < outputDims.nbDims; ++i) outputSize *= outputDims.d[i];
    inputSize *= sizeof(float);
    outputSize *= sizeof(float);

    // 3. ホスト & デバイスメモリ確保
    float* inputHost = new float[inputSize / sizeof(float)];
    float* outputHost = new float[outputSize / sizeof(float)];
    void* inputDevice;
    void* outputDevice;
    cudaMalloc(&inputDevice, inputSize);
    cudaMalloc(&outputDevice, outputSize);

    // 任意の入力を用意（例: すべて 1.0f）
    for (size_t i = 0; i < inputSize / sizeof(float); ++i)
        inputHost[i] = 1.0f;

    // 4. 推論の流れ
    cudaMemcpy(inputDevice, inputHost, inputSize, cudaMemcpyHostToDevice);
    void* bindings[] = {inputDevice, outputDevice};
    context->executeV2(bindings);
    cudaMemcpy(outputHost, outputDevice, outputSize, cudaMemcpyDeviceToHost);

    // 出力を確認（先頭10個だけ表示）
    for (int i = 0; i < std::min((size_t)10, outputSize / sizeof(float)); ++i)
        std::cout << "output[" << i << "] = " << outputHost[i] << std::endl;

    // クリーンアップ
    cudaFree(inputDevice);
    cudaFree(outputDevice);
    delete[] inputHost;
    delete[] outputHost;
    context->destroy();
    engine->destroy();

    return 0;
}
```

---

## ✅ コンパイル方法

```bash
g++ -std=c++17 -o run_infer run_infer.cpp \
    -I/usr/include/aarch64-linux-gnu \
    -lnvinfer -lcudart
```

---

## ✅ 実行方法

```bash
./run_infer
```

---

## ✅ 補足

- `model.trt` は TensorRT エンジンファイルです（ONNX から事前に生成しておく）
- 入力名 `"input"` と出力名 `"output"` はモデルによって異なるため、確認が必要です
- 実運用ではバッチサイズ対応や非同期処理も考慮してください

---

C++からのTensorRT の使い方を紹介しました。
しかし、大半の処理が GPU or NPU 側に存在するため
CPU側のコードをC/C++で最適化しても、全体的に見たときの高速化は限定的です。
そのため、CPU側の制御をPythonで記述しても大差ないため、便宜性で圧倒する
PythonからのTensorRTがAI業界の主流となっています。
そこで、Python 版の TensorRT 推論コードについても紹介していきます。

# Jetson Orin での推論処理（Python + TensorRT）

このページでは、Python で TensorRT を使用して ONNX モデルを推論する方法を紹介します。Jetson Orin 上で簡潔に動作確認ができるスクリプトです。

---

## ✅ 必要なライブラリのインストール（Jetson 上）

JetPack SDK に含まれている TensorRT Python バインディングが必要です。以下のコマンドで確認できます：

```bash
python3 -c "import tensorrt; print(tensorrt.__version__)"
```

ONNX Runtime や NumPy も使用します：

```bash
sudo apt install python3-pip
pip3 install onnx onnxruntime numpy
```

---

## ✅ Python コード（TensorRT 推論）

```python
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit  # 自動でCUDAコンテキスト初期化
import numpy as np

TRT_LOGGER = trt.Logger(trt.Logger.WARNING)

def load_engine(trt_engine_path):
    with open(trt_engine_path, "rb") as f, trt.Runtime(TRT_LOGGER) as runtime:
        return runtime.deserialize_cuda_engine(f.read())

def allocate_buffers(engine):
    h_input = cuda.pagelocked_empty(trt.volume(engine.get_binding_shape(0)), dtype=np.float32)
    h_output = cuda.pagelocked_empty(trt.volume(engine.get_binding_shape(1)), dtype=np.float32)
    d_input = cuda.mem_alloc(h_input.nbytes)
    d_output = cuda.mem_alloc(h_output.nbytes)
    return h_input, d_input, h_output, d_output

def infer(engine, h_input, d_input, h_output, d_output):
    with engine.create_execution_context() as context:
        cuda.memcpy_htod(d_input, h_input)
        bindings = [int(d_input), int(d_output)]
        context.execute_v2(bindings)
        cuda.memcpy_dtoh(h_output, d_output)
        return h_output

# モデルの準備
engine = load_engine("model.trt")

# バッファの準備
h_input, d_input, h_output, d_output = allocate_buffers(engine)

# 任意の入力を設定（例: 全て1.0）
h_input[:] = 1.0

# 推論の実行
output = infer(engine, h_input, d_input, h_output, d_output)

# 結果表示（先頭10個）
for i in range(min(10, len(output))):
    print(f"output[{i}] = {output[i]}")
```

---

## ✅ 実行方法

```bash
python3 run_infer.py
```

---

## ✅ 補足

- `model.trt` は事前に `trtexec` などで作成しておいてください。
- 入力名や形状はモデルにより異なります。必要に応じて `engine.get_binding_name(0)` で確認できます。
- `pycuda` のインストールが必要です（`pip3 install pycuda`）

---

このコードはバッチサイズ1、単一入力/出力を想定した最小構成です。マルチバッチや動的形状対応が必要な場合は別途処理が必要です。