0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ryzen AI Max+395のNPUを利用するFastFlowLMコンテナを作成する

0
Last updated at Posted at 2026-04-06

はじめに

本記事のFastFlowLMコンテンな作成により、以前構築したollama-rocmのコンテナがcpuにフォールバックするようになってしまいました。ollama公式コンテナ(rocmなし)からVulkanサポートで使ってください。

色々試行錯誤した結果をまとめたものなので、不備があるかもしれません。ご了承ください。

カーネルバージョンアップの都合でその他のコンテナに影響があるかもしれません。必要に応じて再構築などをしてください。

Ryzen AI Max+ 395には50TOPSのNPUが搭載されていますが、なかなか自由に使えない状態でした。最近Ubuntu 24.04上でFastFlowLMを使ってNPUを動かす記事が出てきましたので、FastFlowLMのコンテナを作ってみることにしました。

この記事ではlemonade-serverは扱いません。

もう一つ、OSディストリビューションが違いますが、重要な情報を提供していただいた下記の記事に感謝します。

FastFlowLMについてはこちらをご覧ください。

NPUへアクセスするkernel driverはproprietaryとのことでライセンスにはご注意ください。年間1000万ドル(15億円)以上の売上ある組織が利用する場合はライセンス契約が必要です。

前提の環境

以下の環境で構築しました。それ以外の環境ではテストしていませんので、ご注意ください。

  • GMKTek EVO-X2 / 128GB (AMD Ryzen AI Max+ 395 / 128GB)
  • Ubuntu Server 24.04LTS (headless)
  • Docker CE 29.3.1

ホスト側の準備

素のUbuntu 24.04では色々足りないため、いくつかのパッケージのインストールと設定をします。

カーネルバージョンアップとブートオプションの調整

linuxカーネル6.17とそのヘッダファイルをインストールして再起動します。

sudo apt update
sudo apt install --install-recommends linux-image-generic-6.17 linux-headers-generic-6.17
sudo reboot

その後、ブートオプションの調整をします。amd_iommuはonでないとNPUを認識しないため、調整します。エディタは使い勝手の良いものを選んでください。

sudo vi /etc/default/grub

/etc/default/grubの中のGRUB_CMDLINE_LINUX_DEFAULTの行を以下のようにエディタで修正し保存します。

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash amd_iommu=on iommu=pt"

保存できたら、更新して再起動します。

sudo update-grub
sudo reboot

NPU関連パッケージのインストールと設定

NPUに関連するパッケージをインストールします。

sudo add-apt-repository ppa:amd-team/xrt
sudo apt update
sudo apt install -y libxrt2 libxrt-npu2 libxrt-dev libxrt-utils libxrt-utils-npu amdxdna-dkms

グループrenderに自分を登録して、再起動します。

sudo usermod -aG render "$USER"
sudo reboot

/dev/accel/accel0ができていればNPUが利用可能な状態になっています。以下の作業で適切なバージョンとなっていることを確認します。

uname -r
ls -l /dev/accel/accel0
modinfo amdxdna | grep -E '^filename:'

実行結果が以下のとおりなら良好です。カーネルのバージョンが上がっていて、accel0が見え、そのカーネルバージョンのdkms以下にamdxdna.ko.zstがあれば良いです。

$ uname -r
6.17.0-20-generic
$ ls -l /dev/accel/accel0 
crw-rw---- 1 root render 261, 0 Apr  5 00:16 /dev/accel/accel0
$ modinfo amdxdna | grep -E '^(filename|version):'
filename:       /lib/modules/6.17.0-20-generic/updates/dkms/amdxdna.ko.zst

コンテナの作成

ディレクトリ構成の調整

コンテナを置く場所を ~/Containers/fastflowlmとします。ここは皆さんの都合の良いように変更してください。このディレクトリ下にデータ等を永続的に保存するディレクトリを作成します。

mkdir -p ~/Containers/fastflowlm
cd ~/Containers/fastflowlm
mkdir -p data models

コンテナ作成用ファイルの作成

以下のDockerfileをカレントディレクトリに置きます。

FROM ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive
SHELL ["/bin/bash", "-lc"]

RUN apt-get update && apt-get install -y --no-install-recommends \
    ca-certificates curl wget gnupg software-properties-common \
    lsb-release git build-essential cmake ninja-build pkg-config \
    python3 python3-pip pciutils usbutils libglib2.0-0 libstdc++6 \
    libjson-glib-dev libcurl4-openssl-dev uuid-dev rapidjson-dev \
    pybind11-dev ocl-icd-opencl-dev opencl-headers \
    libboost-all-dev libfftw3-dev libavformat-dev libavcodec-dev \
    libavutil-dev libswscale-dev libswresample-dev libdrm-dev \
    && rm -rf /var/lib/apt/lists/*

RUN add-apt-repository -y ppa:amd-team/xrt \
    && apt-get update \
    && apt-get install -y --no-install-recommends \
       libxrt2 libxrt-npu2 libxrt-dev \
    && rm -rf /var/lib/apt/lists/*

ENV RUSTUP_HOME=/opt/rustup
ENV CARGO_HOME=/opt/cargo
ENV PATH=/opt/cargo/bin:${PATH}

RUN curl https://sh.rustup.rs -sSf | bash -s -- -y --profile minimal --default-toolchain stable \
    && rustc --version \
    && cargo --version

WORKDIR /opt

RUN git clone --recursive https://github.com/FastFlowLM/FastFlowLM.git \
    && cd /opt/FastFlowLM \
    && git submodule update --init --recursive \
    && cmake -S src --preset linux-default \
    && ninja -C src/build -j1 -v \
    && cmake --install src/build

ENV LD_LIBRARY_PATH=/opt/fastflowlm/lib:/opt/xilinx/xrt/lib
ENV FLM_CONFIG_PATH=/opt/fastflowlm/share/flm/model_list.json
ENV FLM_MODEL_PATH=/models
ENV HF_HOME=/data/huggingface
ENV HUGGINGFACE_HUB_CACHE=/data/huggingface/hub
ENV XDG_CACHE_HOME=/data/.cache

RUN mkdir -p /workspace /data/huggingface /data/.cache /models
WORKDIR /workspace

EXPOSE 52625

CMD bash -lc '\
  echo "=== kernel ==="; uname -r; \
  echo "=== accel ==="; ls -l /dev/accel || true; \
  echo "=== flm validate ==="; /opt/fastflowlm/bin/flm validate || true; \
  echo "=== starting flm serve ==="; \
  exec /opt/fastflowlm/bin/flm serve --host 0.0.0.0 --port 52625 \
'

次にdocker-compose.ymlをカレントディレクトリに置きます。

services:
  fastflowlm:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: fastflowlm
    restart: unless-stopped

    ports:
      - "52625:52625"

    environment:
      LD_LIBRARY_PATH: "/opt/fastflowlm/lib:/opt/xilinx/xrt/lib"
      FLM_CONFIG_PATH: "/opt/fastflowlm/share/flm/model_list.json"
      FLM_MODEL_PATH: "/models"
      HF_HOME: "/data/huggingface"
      HUGGINGFACE_HUB_CACHE: "/data/huggingface/hub"
      XDG_CACHE_HOME: "/data/.cache"

    volumes:
      - ./data:/data
      - ./models:/models

    devices:
      - /dev/accel:/dev/accel

    ulimits:
      memlock:
        soft: -1
        hard: -1

    cap_add:
      - IPC_LOCK
      - SYS_NICE

    security_opt:
      - seccomp:unconfined

コンテナ構築

ビルドして起動します。

docker compose build --no-cache
docker compose up -d
docker compose logs -f 

最後のログ確認で以下のように表示されればOKです。

$ docker compose logs -f
fastflowlm  | === kernel ===
fastflowlm  | 6.17.0-20-generic
fastflowlm  | === accel ===
fastflowlm  | total 0
fastflowlm  | crw-rw---- 1 root 993 261, 0 Apr  5 05:26 accel0
fastflowlm  | === flm validate ===
fastflowlm  | [FLM]  Using custom model list path: /opt/fastflowlm/share/flm/model_list.json
fastflowlm  | [Linux]  Kernel: 6.17.0-20-generic
fastflowlm  | [Linux]  NPU: /dev/accel/accel0 with 8 columns
fastflowlm  | [Linux]  NPU FW Version: 1.1.2.65
fastflowlm  | [Linux]  amdxdna version: 0.6
fastflowlm  | [Linux]  Memlock Limit: infinity
fastflowlm  | === starting flm serve ===
fastflowlm  | [FLM]  Using custom model list path: /opt/fastflowlm/share/flm/model_list.json
fastflowlm  | [FLM]  Using user-specified port: 52625
fastflowlm  | [FLM]  Starting server on port 52625...
fastflowlm  | [FLM]  WebServer started on port 52625 with 10 I/O threads
fastflowlm  | [FLM]  Press Ctrl+C to stop.

動作確認

ホスト側からcurlでいくつか試してみましょう。

最初はモデルリストから。初期状態で32個のモデルが利用可能で、deepseek-r1:8b, gemma3:4b, gpt-oss:20b, qwen3.5:9bなどが含まれています。これらのモデルは切り替えながら利用可能という点でollamaと似ていますが、初めて利用する時にモデルのダウンロードを始めるので、その時の最初の応答が得られるまでにはそれなりに時間がかかります。

curl -s http://127.0.0.1:52625/v1/models | jq

結果は以下の通り。

{
  "data": [
    {
      "created": 1775367291,
      "id": "deepseek-r1:8b",
      "object": "model",
      "owned_by": "FastFlowLM"
    },
    (snip)
    {
      "created": 1775367291,
      "id": "gemma3:4b",
      "object": "model",
      "owned_by": "FastFlowLM"
    },
    {
      "created": 1775367291,
      "id": "gpt-oss:20b",
      "object": "model",
      "owned_by": "FastFlowLM"
    },
    (snip)
    {
      "created": 1775367291,
      "id": "qwen3.5:9b",
      "object": "model",
      "owned_by": "FastFlowLM"
    },
    {
      "created": 1775367291,
      "id": "qwen3vl-it:4b",
      "object": "model",
      "owned_by": "FastFlowLM"
    },
    {
      "created": 1775367291,
      "id": "translategemma:4b",
      "object": "model",
      "owned_by": "FastFlowLM"
    }
  ],
  "object": "list"
}

もう一つ。Open WebUIが投げそうなパラメータで試します。

$ curl -s http://localhost:52625/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "gpt-oss:20b",
    "messages": [
      {"role": "user", "content": "1+1は?"}
    ],
    "temperature": 0.7,
    "top_p": 0.9,
    "max_tokens": 64,
    "stream": false
  }' | jq .

結果は以下の通り。

{
  "id": "fastflowlm-chat-completion",
  "object": "chat.completion",
  "created": 1775368079,
  "model": "gpt-oss:20b",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "reasoning_content": "User asked \"1+1は?\". That is Japanese: \"What is 1+1?\" So answer: 2. But maybe they want explanation? The user didn't specify context. Just answer.\n\nWe should respond in Japanese or English? They wrote question in Japanese, so likely expect answer in Japanese",
        "content": ""
      },
      "logprobs": null,
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 72,
    "completion_tokens": 64,
    "total_tokens": 136,
    "load_duration": 252.474884096,
    "prefill_duration_ttft": 3.821402624,
    "decoding_duration": 3.244395,
    "prefill_speed_tps": 18.84124942705854,
    "decoding_speed_tps": 19.726328021094844
  },
  "service_tier": "default"
}

Open WebUIからも適切に設定すれば以下のように使えます。

image.png

ベンチマーク

fastflowlmのコンテナとollama(vulkan)コンテナのそれぞれで同じプロンプトをcurlから送信したときの結果は以下のとおりです。

モデル/計算ユニット prefill (token/s) decoding (token/s)
gpt-oss:20b/NPU 19.93 19.53
gpt-oss:20b/iGPU 862.32 45.47
qwen3.5:9b/NPU 16.59 7.83
qwen3.5:9b/iGPU 208.83 31.43
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?