SUSE AI Factoryで始めるローカルLLMサービング解説 〜Ollamaの仕組みとモデル配置・推論API実行まで〜

はじめに

本記事では、SUSE AI Factory で提供されている推論エンジンの1つであるOllamaの仕組みと特徴を解説し、後半では、OllamaのAPIを利用したモデルサービングおよび推論APIの実行手順についてデモ形式で紹介します。なお、Ollamaのインストール方法については、Open WebUIのインストール時に一緒にインストールされるため本記事では扱っていません。インストールについて詳しく知りたい方は「SUSE AI FactoryでのOpen WebUI導入手順解説 〜AirGap環境へのOpen WebUI・Ollama構築〜」をご参照ください。

他のSUSE AI Factory の構成要素については、「SUSE AI Factoryを構成するAIアプリケーション徹底解説 〜OSSで構築するエンタープライズ生成AI基盤〜」をご覧ください。

モデルサービングとは

モデルサービングとは、学習済みのAIモデルをサーバー上に常駐させ、アプリケーションからAPI経由で推論を実行できるようにする仕組みです。

特に LLM(大規模言語モデル)では、数 GB 以上のデータを GPU メモリに読み込んで動作するため、リクエストのたびにロードするのではなく、あらかじめモデルを展開したサーバーを起動しておき、HTTP API でリクエストを受け付ける構成が一般的です。この「モデルを読み込んで推論を実行し、APIを提供するソフトウェア」を推論エンジンと呼びます。

Kubernetes でモデルサービングを行う場合は、推論エンジンをPodとして動かし、Serviceで APIを公開します。AirGap環境では、モデルデータはローカルストレージに保存して、そのストレージと連携しているPV経由で推論エンジンにモデルを読み込ませます。また、推論エンジンのイメージやHelmチャートはプライベートレジストリに保存し、そこから取得します。

Ollamaとは

Ollamaは、ローカル環境で手軽にLLMを動かせるオープンソースの推論エンジンです。 ollama pull / ollama run のように Dockerに似た操作感でモデルを管理し、API サーバーとして推論機能を提供します。

Go で書かれた Ollamaサーバーが APIとモデル管理を担い、モデルごとのランナーが推論を実行します。推論エンジンには、C++ で高速に動作する llama.cpp を採用しています。主な特徴は次の通りです。

  • コンテナイメージに似たモデル管理: モデルはマニフェストとレイヤーで構成され、llama3.1:8bのようにタグで指定する。Modelfileでシステムプロンプトなどを加えた独自モデルも作れる
  • 量子化(GGUF): 重みを4〜8ビットに圧縮したモデルを中心に利用する。8Bモデルの場合、FP16では約16GB必要な重みが、Q4_K_Mでは約5GBに収まる
  • 自動的なメモリ管理: モデルはリクエスト時に読み込まれ、既定では5 分使われないと解放される(OLLAMA_KEEP_ALIVEで設定可能)
  • 2 種類のAPI: 独自API(/api/chat、埋め込み用の /api/embed など)とOpenAI互換API (/v1/chat/completions、/v1/modelsなど)を提供する

Ollamaの設定

OllamaはOpen WebUIのインストール時に一緒にインストールされるため、具体的なインストール方法についてはこの記事では省略します。この記事では、主にOllamaで使用するモデルのダウンロードと配置手順について説明します。

前提条件

基本的な前提条件は、「SUSE AI FactoryでのOpen WebUI導入手順解説 〜AirGap環境へのOpen WebUI・Ollama構築〜」の前提条件をご参照ください。

上記に加えて、以下の条件が追加されます。

  • Open WebUIのインストール: SUSE AI Factoryコンポーネントとして Open WebUI のデプロイが完了していること

パラメータの説明

Open WebUIのインストール時に設定したOllama関連の主要パラメータについて説明します。

インストール時に利用した全体の設定については「Open WebUIの紹介のリンク」をご参照ください。また、以下の表の設定値は、インストール時に設定した値を記載していますが、この後のデモの説明に関係する一部のパラメータは設定値を「インストール時点では設定なし」にして記載しています。

デプロイ設定(ollama)

キー設定値意味
enabledtrueOpen WebUIのチャートの依存として、Ollamaを一緒にインストールするための設定
nodeSelectoraccelerator: nvidia-gpuOllamaのPodをGPUノードに配置するnodeSelector設定
tolerationsnvidia.com/gpu=dedicated:NoScheduleOllamaのPodをGPUノードに配置するtolerations設定
resources   requests:
      cpu: “2”
      memory: “4Gi”
    limits:
      cpu: “4”
      memory: “8Gi”
OllamaのPodが使えるCPUとメモリの上限と確保量
replicaCount1OllamaのPodの数
image image:
    registry: <コンテナレジストリのドメイン>
    repository: <ollamaイメージのリポジトリ>
    tag: <ollamaイメージのバージョン>
Ollamaのイメージ取得のための設定

GPU設定(ollama.ollama.gpu)

キー設定値意味
enabledtrueGPUを使用するための設定
typenvidiaGPUの種類
number1Podが要求するGPUの数

モデル設定(ollama.ollama.models)

キー設定値意味
pull[ ]起動時にインターネットから取得するモデルを指定
runインストール時点では設定なし起動時にメモリへロードするモデルを指定
createインストール時点では設定なし起動時にカスタムモデルにを作成するための設定

ストレージ設定(ollama.persistentVolume)

キー設定値意味
enabledtruePVを使用するための設定
existingClaimollama-models-pvc使用するPVC

環境変数(ollama.extraEnv)

キー設定値意味
OLLAMA_MAX_LOADED_MODELS“2”メモリに同時にロードできるモデルの最大数
OLLAMA_NUM_PARALLEL“4”1つのモデルが、同時に処理できるリクエストの数
OLLAMA_KEEP_ALIVEインストール時点では設定なしモデルをメモリに保持する時間(既定は5分)

公開設定(ollama.ingress)

キー設定値意味
enabledtrueOllamaのIngressを有効化するための設定
classNametraefik使用するIngressClass名
hosts<Ollama接続用のホスト名>Ollama接続用のホスト名

Ollamaのモデルサービングデモ

接続設定

OllamaのService(open-webui-ollama)はクラスター内部向け(ClusterIP)として構成されています。作業用端末から kubectl port-forward を実行し、API ポート(11434)をローカルに公開します。 

$ kubectl port-forward -n open-webui-system svc/open-webui-ollama 11434:11434 > /tmp/pf-ollama.log 2>&1 &

以降のcurlコマンドは、http://localhost:11434に対して実行します。

ダウンロードしたモデルの確認

利用可能なモデル一覧を取得する /api/tags を呼び出し、Open WebUIインストール時に格納したモデルデータが正しく認識されるか確認します。

$ curl -s http://localhost:11434/api/tags | jq .
# 出力例
ubuntu@ip-10-1-50-10:~$ curl -s http://localhost:11434/api/tags | jq .
{
  "models": [
    {
      "name": "gemma4:12b",
      "model": "gemma4:12b",
      "modified_at": "2026-10-02T07:10:22.444133991Z",
      "size": 8021618941,
      "digest": "6114515d63c17436a7c0417d82820ac65ad643e2806c5a3c89cb62846436ed0b",
      "details": {
        "parent_model": "imatrix-20260927-12b/model-q4_K_M.gguf",
        "format": "gguf",
        "family": "gemma4",
        "families": [
          "gemma4"
        ],
        "parameter_size": "11.9B",
        "quantization_level": "Q4_K_M",
        "context_length": 262144,
        "embedding_length": 3840
      },
      "capabilities": [
        "completion",
        "tools",
        "thinking",
        "vision"
      ]
    },
    {
      "name": "embeddinggemma:300m",
      "model": "embeddinggemma:300m",
      "modified_at": "2026-10-02T07:12:06.090479003Z",
      "size": 621875917,
      "digest": "85462619ee721b466c5927d109d4cb765861907d5417b9109caebc4e614679f1",
      "details": {
        "parent_model": "embeddinggemma:300m-bf16",
        "format": "gguf",
        "family": "gemma3",
        "families": [
          "gemma3"
        ],
        "parameter_size": "307.58M",
        "quantization_level": "BF16",
        "context_length": 2048,
        "embedding_length": 768
      },
      "capabilities": [
        "embedding"
      ]
    }
  ]
}

レスポンスに配置した gemma4:12b および embeddinggemma:300m が含まれていれば、格納したモデルが Ollama に正常に認識されています。

モデルのロード

Ollamaでテキスト生成モデルをロードするには、/api/generate を利用します。

プロンプトを含めずにモデル名とkeep_aliveを指定してリクエストを送信すると、モデルを事前にGPU メモリへ展開(プリロード)できます。

$ curl -s http://localhost:11434/api/generate -d '{
  "model": "gemma4:12b",
  "keep_alive": "10m"
}' | jq .
# 出力例
{
  "model": "gemma4:12b",
  "created_at": "2026-11-02T02:51:57.999170021Z",
  "response": "",
  "done": true,
  "done_reason": "load"
}

ロード完了後、現在のロード状況を以下のコマンドで確認します。

$ curl -s http://localhost:11434/api/ps | jq .
# 出力例
{
  "models": [
    {
      "name": "gemma4:12b",
      "model": "gemma4:12b",
      "size": 2534439649,
      "digest": "6114515d63c17436a7c0417d82820ac65ad643e2806c5a3c89cb62846436ed0b",
      "details": {
        "parent_model": "",
        "format": "gguf",
        "family": "gemma4",
        "families": [
          "gemma4"
        ],
        "parameter_size": "11.9B",
        "quantization_level": "Q4_K_M"
      },
      "expires_at": "2026-11-02T03:01:58.004382982Z",
      "size_vram": 2534439649,
      "context_length": 4096
    }
  ]
}

一覧に gemma4:12b が表示され、size_vram(GPU メモリ上の専有量)と expires_at(アンロード予定時刻)が確認できます。

また、Ollama ではテキスト生成モデルだけでなく、Embeddingモデルも同様にサービングできます。Embeddingモデルは推論用モデルをロードするAPI(/api/generate)に対応していないため、埋め込み用API(/api/embed)に適当なinputを設定してロードします。

$ curl -s http://localhost:11434/api/embed -d '{
  "model": "embeddinggemma:300m",
  "input": "test",
  "keep_alive": "10m"
}' | jq .
# 出力例
{
  "model": "embeddinggemma:300m",
  "embeddings": [
    [ベクトル変換結果]
  ],
  "total_duration": 12810597793,
  "load_duration": 12556493665,
  "prompt_eval_count": 3
}

埋め込みベクトル(数値配列)が返ってきたら、再度ロード状態を確認します。

$ curl -s http://localhost:11434/api/ps | jq . 
# 出力例
{
  "models": [
    {
      "name": "embeddinggemma:300m",
      "model": "embeddinggemma:300m",
      "size": 681417113,
      "digest": "85462619ee721b466c5927d109d4cb765861907d5417b9109caebc4e614679f1",
      "details": {
        "parent_model": "",
        "format": "gguf",
        "family": "gemma3",
        "families": [
          "gemma3"
        ],
        "parameter_size": "307.58M",
        "quantization_level": "BF16"
      },
      "expires_at": "2026-11-02T03:06:45.557738706Z",
      "size_vram": 681417113,
      "context_length": 2048
    },
    {
      "name": "gemma4:12b",
      "model": "gemma4:12b",
      "size": 2534439649,
      "digest": "6114515d63c17436a7c0417d82820ac65ad643e2806c5a3c89cb62846436ed0b",
      "details": {
        "parent_model": "",
        "format": "gguf",
        "family": "gemma4",
        "families": [
          "gemma4"
        ],
        "parameter_size": "11.9B",
        "quantization_level": "Q4_K_M"
      },
      "expires_at": "2026-11-02T03:01:58.004382982Z",
      "size_vram": 2534439649,
      "context_length": 4096
    }
  ]
}

gemma4:12bとembeddinggemma:300mの2つのモデルが GPU メモリ上にロードされていることが確認できます。

モデルのアンロード

/api/generateのリクエストでkeep_alive: 0を指定して送信すると、モデルを即座にメモリから解放できます。

$ curl -s http://localhost:11434/api/generate -d '{
  "model": "gemma4:12b",
  "keep_alive": 0
}' | jq .
# 出力例
{
  "model": "gemma4:12b",
  "created_at": "2026-11-02T03:05:20.847281172Z",
  "response": "",
  "done": true,
  "done_reason": "unload"
}

レスポンスに “done_reason”: “unload” が含まれていることを確認し、モデルのロード状態を再確認します。

$ curl -s http://localhost:11434/api/ps | jq .
# 出力例
{
  "models": []
}

一覧から gemma4:12b が消え、GPU メモリが解放されたことが確認できます。

※直前にロードした embeddinggemma:300m はkeep_aliveの期限切れ(または同様の手順で解放)によりアンロードされているものとします

推論APIの実行

/api/chatを使用し、モデルを指定してチャット推論を実行します。

ここでは gemma4:12bを指定し、リクエストパラメータに “think”: true を指定することで、モデルが回答を生成するまでの思考プロセス(thinking)もあわせて取得します。

$ curl -s http://localhost:11434/api/chat -d '{
  "model": "gemma4:12b",
  "messages": [{"role": "user", "content": "(50+1)x4は?"}],
  "stream": false,
  "think": true
}' | jq .
# 出力例
{
  "model": "gemma4:12b",
  "created_at": "2026-11-02T03:07:00.770889659Z",
  "message": {
    "role": "assistant",
    "content": "(50+1)x4の計算結果は **204** です。\n\n計算の順序:\n1. かっこの中を計算:50 + 1 = 51\n2. その結果を4倍する:51 × 4 = 204",
    "thinking": "*   Expression: $(50+1) \\times 4$\n    *   Goal: Solve the mathematical expression.\n\n    *   Parentheses first: $(50 + 1)$\n    *   Multiplication second: $(\\text{result}) \\times 4$\n\n    *   Step 1: $50 + 1 = 51$\n    *   Step 2: $51 \\times 4 = ?$\n        *   $50 \\times 4 = 200$\n        *   $1 \\times 4 = 4$\n        *   $200 + 4 = 204$\n\n    *   The answer is 204."
  },
  "done": true,
  "done_reason": "stop",
  "total_duration": 21696670723,
  "load_duration": 15345151661,
  "prompt_eval_count": 26,
  "prompt_eval_duration": 338108000,
  "eval_count": 224,
  "eval_duration": 5992482000
}

レスポンスのmessage.contentに計算結果が返ってきているほか、message.thinkingにモデルが段階的に計算手順を組み立てた思考過程が出力されていることが確認できます。

また、直前の手順でモデルをアンロードしたため、今回のリクエスト時には モデルの再ロードが発生し、ロードだけで約15.3秒かかっていることがload_durationの値から分かります。リクエスト時のレイテンシを抑えるためには、あらかじめモデルをメモリ上に常駐させておくことが重要です。

補足として、存在しないモデルを指定した場合の挙動を確認するため、存在しないモデル名を指定してリクエストを送信してみます。

$ curl -s http://localhost:11434/api/chat -d '{
  "model": "nothing",
  "messages": [{"role": "user", "content": "こんにちは"}],
  "stream": false
}' | jq .
# 出力例
{
  "error": "model 'nothing' not found"
}

期待通り、モデルが見つからない旨のエラー(model ‘nothing’ not found)が返り、指定したモデルのみが安全にルーティングされていることが確認できます。

外部からの推論APIの実行

KubernetesのIngressを利用して外部公開された推論APIを実行できます。

まず、以下のコマンドでollamaのIngressが存在するか確認します。Ingressとollama接続用のホスト名はOpen WebUIのインストール時に設定したものです。

$ kubectl get ingress -n open-webui-system | grep ollama
# 出力例
open-webui-ollama   traefik   <ollama接続用のホスト名>                  80      13d

作業用端末から、Ingress経由で、推論APIを呼び出します。

$ curl -sk <ollama接続用のホスト名>/api/chat -d '{"model":"gemma4:12b","messages":[{"role":"user","content":"こんにちは。一言で自己紹介してください。"}],"stream":false,"think":false}' | python3 -c 'import sys,json; d=json.load(sys.stdin); print("model:", d["model"]); print(d["message"]["content"])' | jq .
# 出力例
{
  "model": "gemma4:12b",
  "created_at": "2026-11-02T03:20:41.044425426Z",
  "message": {
    "role": "assistant",
    "content": "私はGoogle DeepMindによって開発された、テキストと画像を理解する大規模言語モデル「Gemma 4」です。"
  },
  "done": true,
  "done_reason": "stop",
  "total_duration": 1638960978,
  "load_duration": 682073257,
  "prompt_eval_count": 22,
  "prompt_eval_duration": 177826000,
  "eval_count": 25,
  "eval_duration": 757298000
}

Ingress を経由して正常に応答が返ってくることが確認できました。

また、今回は直前のリクエストによってモデルがすでに GPU メモリ上にロードされているため、再ロードが発生せず、全体で約1.6秒という非常に高速なレスポンスを実現できています。

おわりに

本記事では、SUSE AI Factoryで利用できる推論エンジン「Ollama」の仕組みを解説し、AirGap(完全閉域網)環境での導入手順から推論デモまでを紹介しました。

Ollamaは、量子化モデルのサポートと自動的なメモリ管理により、限られたリソースでも手軽にローカルLLMを動かせる点が大きな強みです。

今後の記事では、推論エンジンの1つであるvLLMについて紹介します。最後までお読みいただき、ありがとうございました。

参考

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。
エンジニア募集中!

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です