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?

Mac mini (M6 / 32GB) でローカル LLM を試す

0
Last updated at Posted at 2026-10-18

はじめに

ローカル LLM は、2023年の llama.cpp や Ollama の登場によって、個人の PC でも動かしやすくなった

2024年11月の M4 Mac mini 発売以降は、Apple Silicon のユニファイドメモリや電力効率を活かしたローカル LLM 環境にも関心が集まっている。2025年には DeepSeek-R1 や gpt-oss などのモデルも登場し、個人で試せる選択肢が広がった

やや流行りに乗り遅れたが、今回は2026年9月発売の Mac mini M6 を購入し、ローカル LLM の検証環境を自宅ラボに構築した。本記事では、環境構築から初期動作確認までの手順を、実際の出力結果とともにまとめる

使用するのは Mac mini(M6 / 32GB)で、Ollama + gpt-oss 20B と Splash + Qwen3.8-27B の2構成を試す。購入直後のため、Homebrew 未導入の状態から準備を進めた

■ 概要図

macmini-llm-overview.png

同一 Mac 内の試験では 127.0.0.1、LAN 試験では今回の Mac mini に割り当てた IP アドレス 192.168.129.6 を待ち受け先に使う。LAN IP アドレスは利用環境に合わせて読み替える。2つのモデルは同時にロードせず、順番に検証する

■ Splash の WebUI

macmini-splash-webchat.png

本記事で実施する内容は下記のとおり。

  • 環境準備
    • Homebrew のインストールと PATH 設定
    • macOS ファイアウォールと API 接続元の制限
  • インストール
    • Ollama + gpt-oss 20B のインストールと起動
    • Splash + Qwen3.8-27B のインストールと起動
  • 動作確認
    • 日本語での質問と回答内容の確認
    • GPU の利用状況とメモリ使用量の確認
    • 同じ Mac と LAN 内の別PC・サーバからの API 呼び出し
    • API の応答時間と導入前後のディスク容量の確認

両構成で日本語の回答と API 経由の応答を確認できたが、技術的な説明には一部誤りがあった。設計や設定に使う内容は、公式資料との照合が必要となる

今回の結果は少数の質問による初期確認として掲載する。モデル・量子化・思考設定が異なる構成のため、モデルや実行ツール単体の性能・優劣を判断するものではない

実施環境

使用する環境は下記のとおり (2026.10.18 実施時点)

項目 内容
本体 Mac mini (Mac18,5)
チップ Apple M6
CPU 12コア(スーパーコア × 2、高性能コア × 4、高効率コア × 6)
GPU 12コア
ユニファイドメモリ 32GB(CPU・GPU で共有)
メモリ帯域幅 170GB/s(32GB構成の公称値)
内蔵 SSD 512GB
OS macOS 27 Golden Gate 27.0.1
Ollama 0.40.6
Splash 1.3.2
Homebrew 7.0.9
Command Line Tools for Xcode 27.0(Homebrew インストーラで導入)
Ollama のモデル gpt-oss:20b / MXFP4
Splash のモデル Qwen3.8-27B / Unsloth UD-IQ4_XS
Mac mini の LAN IP 192.168.129.6
API 接続を許可するネットワーク 192.168.129.0/24

使用する実行ツール

今回は Ollama と Splash を使う。どちらもローカルで LLM を動かし、HTTP API で呼び出せる。まず各ツールの役割と選定理由を整理する

普及状況の参考として GitHub のスター数や公式組織のフォロワー数も記載する。いずれも関心の目安で、実際の利用者数や利用率を示すものではなく、異なる指標同士の単純比較はできない

Ollama

Ollama は、LLM のダウンロードや実行、API 経由での利用ができるツールとなる

Ollama チームが開発・公開するオープンソースのツールで、macOS・Windows・Linux に対応している。2026年10月18日の確認時点で GitHub のスターは約18.4万あり、公式 README には多数の連携ツールも掲載されている。公式リポジトリ

今回は、モデルの取得から API サーバの操作までコマンドで手順を残せる Homebrew の CLI 版を選んだ。Splash と導入・更新方法を揃え、最初のチャット・API 試験の比較基準にする。Homebrew の Ollama

Splash

Splash は、Apple Silicon 向けの推論エンジン兼 API サーバとなる

Inco AI が開発・公開するオープンソースのソフトウェアとなる。2026年10月18日の確認時点で GitHub のスターは約1,500となる。公式リポジトリ

対応モデル向けの Metal カーネルや、ドラフトモデルの先読み候補を対象モデルで検証する投機的デコードなどで高速化する。この最適化を使った構成の使用感を確認するために選んだ。OpenAI 互換 API にも対応し、Ollama と同じ形式で質問を送れる

LM Studio(今回は未実施)

LM Studio は、GUI でモデルを検索し、設定や回答を確認しながら試せる。API 提供にも対応しており、llmster を使えば GUI なしでの運用もできる。ヘッドレス運用

Element Labs, Inc. が開発するアプリケーションで、公式 GitHub では CLI や SDK なども公開している。2026年10月18日の確認時点で、GitHub の公式組織のフォロワーは約1.23万となる。

今回は CLI・API 中心で導入する Ollama と、Apple Silicon 向けの高速化を試す Splash に絞るため、試してはいない

API の利用と認証

API では、モデル名と質問を JSON 形式で HTTP サーバに送り、生成された回答を受け取る。今回は両方が対応する POST /v1/chat/completions に curl からリクエストを送る。OpenAI 互換の形式を使うが、処理は本機にダウンロードしたモデルで行う

項目 Ollama Splash
既定の待ち受け先 127.0.0.1:11434 127.0.0.1:8000
今回使う API http://127.0.0.1:11434/v1/chat/completions http://127.0.0.1:8000/v1/chat/completions
既定の認証 ローカル API は認証不要 API キー未設定では認証なし
認証を付ける場合 認証付きリバースプロキシなどを別途設ける --api-key または SPLASH_API_KEY で設定できる

Ollama のローカル API は API キーを必要としない。Ollama のクラウド API 用キーは別の仕組みで、発行しても本機のローカル API に認証が追加されるわけではない。Ollama の認証仕様

Splash はキーを設定すると、API リクエストに Authorization: Bearer <キー> または x-api-key: <キー> が必要になる。ただしチャット画面・ヘルスチェックなど、一部の経路は認証の対象外となる。Splash の認証仕様

最初の同一 Mac 内での試験は既定のローカル待ち受けを使い、curl に認証ヘッダーを付けない。127.0.0.1 は同じ Mac から接続するアドレスで、既定では別端末から直接接続できない。ただし同じ Mac 内のプログラムからは呼び出せるため、ローカル待ち受け自体がユーザー認証になるわけではない。Ollama の待ち受け仕様

後半では待ち受けを Mac Mini の NIC IP アドレスの 192.168.129.6 に変更し、LAN 内からも応答確認する。Splash は API キー認証を有効にする。Ollama の直接接続は認証なしのため、PF で 192.168.129.0/24 に接続元を制限した検証用 LAN で短時間の疎通確認に使う。継続運用やインターネット経由の利用では、認証・接続制限・TLS または VPN による通信保護の別途設計が必要(今回は対象外)

試験モデル

初回は「API 提供の基準を作る構成」と「Apple Silicon 向けの高速化を試す構成」の2つに絞っている

構成 選定理由・補足 配布サイズの目安
Ollama + gpt-oss 20B / MXFP4 公式導入ガイドで16GB以上の VRAM またはユニファイドメモリでの利用が案内されており、32GB環境の初期候補とした。テキスト API の比較基準にし、今後は関数呼び出しや構造化出力も評価したい 約14GB
Splash + Qwen3.8-27B / Unsloth UD-IQ4_XS Splash 1.3.2 の対応モデルで、技術的な質問への回答と投機的デコードを使った構成の使用感を確認するために選定。32GB環境でのメモリの余裕を重視し、UD-Q4_K_M(約16.5GB)より約2.2GB小さい UD-IQ4_XS を採用した 対象 GGUF 約14.3GB。ドラフトモデル(先読み用)約3.85GBは別途必要で、合計約18.1GB

配布サイズは実行時メモリとは異なり、小さい方が必ず高速とは限らない。
モデル選定は詳しくないので別途試験が必要で今回は試すこと優先で選んだだけとなっている

事前準備

購入直後の環境を整えるため、Ollama と Splash の導入に使う Homebrew を先に準備する

利用バージョンの方針

検証には実施環境の表に記載した安定版を使用した。Homebrew の配布版は更新されるため、後日同じコマンドを実行しても同じ版が入るとは限らない。導入時にバージョンを確認し、比較期間中は brew pin で更新を止める。具体的な確認・設定は各ツールのインストール手順に記載する

モデルもタグの内容が更新される場合があるため、Ollama のモデル ID と Splash の対象・ドラフトのリビジョンを記録する。Splash の SSD キャッシュはディスク容量の見積もりに含める

ディスク容量の見積もり

導入時の容量を下表のとおり見積もり、更新や一時ファイルの余裕を含めて100GB程度の空きを確保する

用途 ディスク容量の目安 補足
Ollama の gpt-oss 20B 約14GB モデルの配布サイズ
Splash の Qwen3.8 とドラフトなど 約17〜22GB 対象 GGUF 約14.3GBとドラフトなどの概算。保存済みの重みを再利用するため、モデルとダウンロードキャッシュを二重計上しない
アプリ・Homebrew・依存関係など 約5〜10GB Command Line Tools などを含む概算
Splash の SSD キャッシュ 上限32GiB(約34GB) 今回の既定設定。空き容量に応じて自動調整され、常に全量を使うわけではない。今回の起動方法では終了時に解放される
合計 初期導入:約36〜46GB/キャッシュ込み:約70〜80GB キャッシュ込みは上限の約34GBを加えた場合

上記は概算で、実測値や最低動作要件ではない。macOS 本体・他のアプリ・スワップ・追加モデルは含めず、実際の空き容量の変化は導入前後に測定する

初期起動からセットアップ

購入直後の Mac は、下記の設定を済ませておく

設定項目 操作 補足
初回起動の設定 言語設定(日本語)、アカウント作成、Apple Account 認証などを進める 画面の案内に従う
OS アップデート 「システム設定 → 一般 → ソフトウェアアップデート」で最新版に更新する 更新後の OS バージョンを記録する
自動スリープの解除 「システム設定 → エネルギー」で「ディスプレイがオフの時に自動でスリープさせない」をオンにする API サーバの利用中にスリープしないようにする
ネットワーク設定 環境に合わせて固定 IP などを設定し、Wi-Fi が不要ならオフにする 必要に応じて設定する
画面共有 「システム設定 → 一般 → 共有 → 画面共有」をオンにする リモートで画面操作する場合に設定する
SSH 接続 「システム設定 → 一般 → 共有 → リモートログイン」をオンにする リモートでターミナル操作する場合に設定する

導入前のディスク容量を記録する

Homebrew やモデルのインストールを始める前に、Mac mini 側でデータボリュームの空き容量を保存する。最後に同じボリュームを測定し、空き容量の減少分を表示する

df -h /System/Volumes/Data
mkdir -p "$HOME/local-llm-measurements"
LC_ALL=C df -kP /System/Volumes/Data > "$HOME/local-llm-measurements/disk-before.txt"
cat "$HOME/local-llm-measurements/disk-before.txt"

df -h は目視確認用、保存する df -kP は差分計算用となる。記録はファイルに残すため、途中でターミナルを閉じても比較できる。導入後にこの手順を再実行して、導入前の記録を上書きしないようにする

導入前のディスク容量の測定ログ
suzuyu@macmini-m6 ~ % df -h /System/Volumes/Data
mkdir -p "$HOME/local-llm-measurements"
LC_ALL=C df -kP /System/Volumes/Data > "$HOME/local-llm-measurements/disk-before.txt"
cat "$HOME/local-llm-measurements/disk-before.txt"
Filesystem      Size    Used   Avail Capacity iused ifree %iused  Mounted on
/dev/disk3s5   460Gi    35Gi   400Gi     8%    343k  4.2G    0%   /System/Volumes/Data
Filesystem   1024-blocks     Used Available Capacity  Mounted on
/dev/disk3s5   482746452 36382052 419935276     8%    /System/Volumes/Data
suzuyu@macmini-m6 ~ % 

Homebrew のインストール

Splash のインストールに使う Homebrew を導入する。macOS の標準ターミナル(zsh)で、公式サイト に掲載されているコマンドを実行する

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Homebrew のインストールログ
suzuyu@macmini-m6 ~ % /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

==> Checking for `sudo` access (which may request your password)...
Password:
==> This script will install:
/opt/homebrew/bin/brew
/opt/homebrew/share/doc/homebrew
/opt/homebrew/share/man/man1/brew.1
/opt/homebrew/share/zsh/site-functions/_brew
/opt/homebrew/etc/bash_completion.d/brew
/opt/homebrew
/etc/paths.d/homebrew
==> The following new directories will be created:
/opt/homebrew/bin
/opt/homebrew/etc
/opt/homebrew/include
/opt/homebrew/lib
/opt/homebrew/sbin
/opt/homebrew/share
/opt/homebrew/var
/opt/homebrew/opt
/opt/homebrew/share/zsh
/opt/homebrew/share/zsh/site-functions
/opt/homebrew/var/homebrew
/opt/homebrew/var/homebrew/linked
/opt/homebrew/Cellar
/opt/homebrew/Caskroom
/opt/homebrew/Frameworks
==> The Xcode Command Line Tools will be installed.

Press RETURN/ENTER to continue or any other key to abort:
==> /usr/bin/sudo /usr/bin/install -d -o suzuyu -g admin -m 0755 /opt/homebrew
==> Searching online for the Command Line Tools
==> Installing Command Line Tools for Xcode 27.0-27.0
Software Update Tool

Finding available software

Downloading Command Line Tools for Xcode 27.0
Downloaded Command Line Tools for Xcode 27.0
Installing Command Line Tools for Xcode 27.0
Done with Command Line Tools for Xcode 27.0
Done.
==> /usr/bin/sudo /usr/bin/xcode-select --switch /Library/Developer/CommandLineTools
==> Found Git: /Library/Developer/CommandLineTools/usr/bin/git
==> Downloading and installing Homebrew...
remote: Enumerating objects: 367837, done.
remote: Counting objects: 100% (997/997), done.
remote: Compressing objects: 100% (219/219), done.
remote: Total 367837 (delta 870), reused 823 (delta 778), pack-reused 366840 (from 2)
remote: Enumerating objects: 55, done.
remote: Counting objects: 100% (33/33), done.
remote: Total 55 (delta 33), reused 33 (delta 33), pack-reused 22 (from 1)
==> /usr/bin/sudo /usr/bin/install -o root -g wheel -m 0644 /var/folders/vm/x0l5wqcx7njgdqpfvbpp20n00000gn/T/tmp.vXAXi5cpXX /etc/paths.d/homebrew
==> Updating Homebrew...
==> Downloading https://ghcr.io/v2/homebrew/core/portable-ruby/blobs/sha256:e0088dff5614b39387300136ec7a5f95bf1e07589547245c919524fc9e8b4197
######################################################################################################################################################################################################################################################### 100.0%
==> Pouring portable-ruby-4.0.7.arm64_big_sur.bottle.tar.gz
==> Installation successful!

==> Homebrew has enabled anonymous aggregate formulae and cask analytics.
Read the analytics documentation (and how to opt-out) here:
  https://docs.brew.sh/Analytics
No analytics data has been sent yet (nor will any be during this install run).

==> Homebrew is run entirely by unpaid volunteers. Please consider donating:
  https://github.com/Homebrew/brew#donations

==> Next steps:
- Run these commands in your terminal to add Homebrew to your PATH:
    echo >> /Users/suzuyu/.zprofile
    echo 'eval "$(/opt/homebrew/bin/brew shellenv zsh)"' >> /Users/suzuyu/.zprofile
    eval "$(/opt/homebrew/bin/brew shellenv zsh)"
- Run brew help to get started
- Further documentation:
    https://docs.brew.sh

suzuyu@macmini-m6 ~ % 

表示されるインストール内容を確認し、案内に従って進める。パスワードを求められた場合は Mac のログインパスワードを入力する。入力中は文字が表示されない

今回の環境では、Homebrew インストーラが Command Line Tools for Xcode 27.0 を自動でインストールした。別途 xcode-select --install を実行する必要はなかった

Homebrew の PATH 設定と確認

インストール完了時の Next steps に従って設定する。Apple Silicon の標準インストール先は /opt/homebrew となる。zsh では下記を一度実行し、今後開くターミナルと現在のターミナルの両方で brew を使えるようにする

echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"

command -v brew
brew --version
brew --prefix
Homebrew の PATH・バージョン確認ログ
suzuyu@macmini-m6 ~ % echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile

suzuyu@macmini-m6 ~ % eval "$(/opt/homebrew/bin/brew shellenv)"

suzuyu@macmini-m6 ~ % command -v brew

/opt/homebrew/bin/brew
suzuyu@macmini-m6 ~ % brew --version

Homebrew 7.0.9
suzuyu@macmini-m6 ~ % brew --prefix

/opt/homebrew
suzuyu@macmini-m6 ~ % 

Homebrew インストール後の出力で出るものと同じのため、表示される Next steps の操作ですでに .zprofile へ同じ設定を追加した場合、上記の echo は省略する。
command -v brew が /opt/homebrew/bin/brew、brew --prefix が /opt/homebrew となり、バージョンが表示されることを確認する

今回の環境では、Homebrew 7.0.9、インストール先 /opt/homebrew を確認した

macOS ファイアウォールと接続元の制限

LAN 向けにサーバを起動する前に、今回の API の2ポートを下記の範囲に制限する。アプリ単位の許可には macOS ファイアウォール、接続元の制限には PF を使う

送信元 宛先 プロトコル・ポート 扱い
192.168.129.0/24 192.168.129.6 TCP 11434 / 8000 許可
上記以外の IPv4 アドレス 192.168.129.6 TCP 11434 / 8000 拒否

LAN 試験時は、サーバを 192.168.129.6 にバインドする。Ollama は許可範囲内で認証なしに利用できるため、信頼できる検証用 LAN を前提とする

アプリケーションファイアウォール

「システム設定 → ネットワーク → ファイアウォール」をオンにし、「オプション」の「すべての受信接続をブロック」はオフにする。Ollama / Splash の導入・起動後、実際に待ち受けるサーバの実行ファイルを確認し、受信接続を許可する。Apple の設定手順

PF のルールを作成する

購入直後で、VPN などが独自の PF 設定を管理していない環境向けの例。メイン設定の再読込は既存ルールに影響する場合があるため、既存設定がある場合は先に確認する。操作は Mac 本体のターミナルで行う

設定をバックアップし、専用のルールファイルを作成する。cp -n は既存のバックアップを上書きしない

sudo cp -p -n /etc/pf.conf /etc/pf.conf.before-local-llm
sudo tee /etc/pf.anchors/local.llm >/dev/null <<'EOF'
llm_host = "192.168.129.6"
llm_net = "192.168.129.0/24"
llm_ports = "{ 11434, 8000 }"

pass in inet proto tcp from $llm_net to $llm_host port $llm_ports flags S/SA keep state
block return in quick inet proto tcp from ! $llm_net to $llm_host port $llm_ports
EOF
PF ルールの作成ログ
suzuyu@macmini-m6 ~ % sudo cp -p -n /etc/pf.conf /etc/pf.conf.before-local-llm

Password:
suzuyu@macmini-m6 ~ % sudo tee /etc/pf.anchors/local.llm >/dev/null <<'EOF'
llm_host = "192.168.129.6"
llm_net = "192.168.129.0/24"
llm_ports = "{ 11434, 8000 }"

pass in inet proto tcp from $llm_net to $llm_host port $llm_ports flags S/SA keep state
block return in quick inet proto tcp from ! $llm_net to $llm_host port $llm_ports
EOF
suzuyu@macmini-m6 ~ % sudo nano /etc/pf.conf

suzuyu@macmini-m6 ~ % cat /etc/pf.conf
#
# Default PF configuration file.
#
# This file contains the main ruleset, which gets automatically loaded
# at startup.  PF will not be automatically enabled, however.  Instead,
# each component which utilizes PF is responsible for enabling and disabling
# PF via -E and -X as documented in pfctl(8).  That will ensure that PF
# is disabled only when the last enable reference is released.
#
# Care must be taken to ensure that the main ruleset does not get flushed,
# as the nested anchors rely on the anchor point defined here. In addition,
# to the anchors loaded by this file, some system services would dynamically 
# insert anchors into the main ruleset. These anchors will be added only when
# the system service is used and would removed on termination of the service.
#
# See pf.conf(5) for syntax.
#

#
# com.apple anchor point
#
scrub-anchor "com.apple/*"
nat-anchor "com.apple/*"
rdr-anchor "com.apple/*"
dummynet-anchor "com.apple/*"
anchor "com.apple/*"
load anchor "com.apple" from "/etc/pf.anchors/com.apple"
suzuyu@macmini-m6 ~ % cat /etc/pf.anchors/local.llm 
llm_host = "192.168.129.6"
llm_net = "192.168.129.0/24"
llm_ports = "{ 11434, 8000 }"

pass in inet proto tcp from $llm_net to $llm_host port $llm_ports flags S/SA keep state
block return in quick inet proto tcp from ! $llm_net to $llm_host port $llm_ports
suzuyu@macmini-m6 ~ % 

メイン設定から読み込む

sudo nano /etc/pf.conf

既存の Apple 用設定を残し、下記の local.llm を含む2行だけを追加する。anchor "local.llm" はフィルタ用の最初の anchor 行の直前(scrub-anchor・nat-anchor・rdr-anchor より後)、load anchor "local.llm" ... はファイル末尾に置く

下記は標準構成の該当部分の抜粋。ファイル全体を置き換えず、再実行時も同じ行を重複させない

anchor "local.llm"
anchor "com.apple/*"
load anchor "com.apple" from "/etc/pf.anchors/com.apple"
load anchor "local.llm" from "/etc/pf.anchors/local.llm"
PF メイン設定の確認ログ
suzuyu@macmini-m6 ~ % cat /etc/pf.conf
#
# Default PF configuration file.
#
# This file contains the main ruleset, which gets automatically loaded
# at startup.  PF will not be automatically enabled, however.  Instead,
# each component which utilizes PF is responsible for enabling and disabling
# PF via -E and -X as documented in pfctl(8).  That will ensure that PF
# is disabled only when the last enable reference is released.
#
# Care must be taken to ensure that the main ruleset does not get flushed,
# as the nested anchors rely on the anchor point defined here. In addition,
# to the anchors loaded by this file, some system services would dynamically 
# insert anchors into the main ruleset. These anchors will be added only when
# the system service is used and would removed on termination of the service.
#
# See pf.conf(5) for syntax.
#

#
# com.apple anchor point
#
scrub-anchor "com.apple/*"
nat-anchor "com.apple/*"
rdr-anchor "com.apple/*"
dummynet-anchor "com.apple/*"
anchor "local.llm"
anchor "com.apple/*"
load anchor "com.apple" from "/etc/pf.anchors/com.apple"
load anchor "local.llm" from "/etc/pf.anchors/local.llm"

suzuyu@macmini-m6 ~ % 

構文確認と適用

構文を確認し、直後の終了コードが 0 なら読み込みへ進む。エラーがあれば修正し、再確認する

sudo pfctl -nf /etc/pf.conf
echo $?
sudo pfctl -f /etc/pf.conf
echo $?

読込の終了コードも 0 なら、PF を有効化して状態とルールを確認する。-E は1回だけ実行し、出力される Token は設定を戻す際に使うため控える

sudo pfctl -E
sudo pfctl -s info
sudo pfctl -sr
sudo pfctl -a local.llm -sr
確認対象 成功時の表示
PF の状態 Status: Enabled
メインルール anchor "local.llm"
専用 anchor TCP 11434 / 8000 の許可2件・拒否2件

今回の環境では上記を確認できた。実通信の確認は、後半の「別PC・サーバからの API 応答確認」で新しい接続を使って行う

pfctl の注意書きと ALTQ の通知について
pfctl: Use of -f option, could result in flushing of rules
present in the main ruleset added by the system at startup.
See /etc/pf.conf for further details.

-f でメインルールを読み込む際の一般的な注意書きで、この表示自体は構文エラーではない。-nf は構文確認のみで、実際のルールは変更しない

No ALTQ support in kernel
ALTQ related functions disabled

ALTQ による帯域制御・キュー制御がカーネルで利用できないという通知。今回の IP・ポートによる制限では ALTQ を使わないため、この表示自体は読込失敗を意味しない。成否は終了コードと読み込まれたルールで確認する

PF の適用・状態確認ログ
suzuyu@macmini-m6 ~ % sudo pfctl -f /etc/pf.conf

pfctl: Use of -f option, could result in flushing of rules
present in the main ruleset added by the system at startup.
See /etc/pf.conf for further details.

No ALTQ support in kernel
ALTQ related functions disabled
suzuyu@macmini-m6 ~ % echo $?
0
suzuyu@macmini-m6 ~ % sudo pfctl -E
No ALTQ support in kernel
ALTQ related functions disabled
pf enabled
Token : 923634586225060667
suzuyu@macmini-m6 ~ % sudo pfctl -s info
No ALTQ support in kernel
ALTQ related functions disabled
Status: Enabled for 0 days 00:00:05           Debug: Urgent

State Table                          Total             Rate
  current entries                        0               
  searches                             441           88.2/s
  inserts                                0            0.0/s
  removals                               0            0.0/s
Counters
  match                                379           75.8/s
  bad-offset                             0            0.0/s
  fragment                               0            0.0/s
  short                                  0            0.0/s
  normalize                              0            0.0/s
  memory                                 0            0.0/s
  bad-timestamp                          0            0.0/s
  congestion                             0            0.0/s
  ip-option                              0            0.0/s
  proto-cksum                            0            0.0/s
  state-mismatch                         0            0.0/s
  state-insert                           0            0.0/s
  state-limit                            0            0.0/s
  src-limit                              0            0.0/s
  synproxy                               0            0.0/s
  dummynet                               0            0.0/s
  invalid-port                           0            0.0/s
suzuyu@macmini-m6 ~ % sudo pfctl -sr
No ALTQ support in kernel
ALTQ related functions disabled
scrub-anchor "com.apple/*" all fragment reassemble
anchor "local.llm" all
anchor "com.apple/*" all
suzuyu@macmini-m6 ~ % sudo pfctl -a local.llm -sr
No ALTQ support in kernel
ALTQ related functions disabled
pass in inet proto tcp from 192.168.129.0/24 to 192.168.129.6 port = 11434 flags S/SA keep state
pass in inet proto tcp from 192.168.129.0/24 to 192.168.129.6 port = 8000 flags S/SA keep state
block return in quick inet proto tcp from ! 192.168.129.0/24 to 192.168.129.6 port = 11434
block return in quick inet proto tcp from ! 192.168.129.0/24 to 192.168.129.6 port = 8000
suzuyu@macmini-m6 ~ % 

今回は再起動時の自動適用を設定しない。Mac を再起動した場合は、LAN 向けサーバの起動前に構文確認・読込・有効化と状態確認をやり直す。再起動していなければ -E は重ねず、状態確認のみ行う

設定を解除する場合は、後半の「利用後の停止と設定の切り戻し」を参照する

Ollama のインストール・起動・応答確認

公式ドキュメントでは macOS Sonoma 14 以降が必要で、Apple M シリーズでは CPU と GPU がサポートされている。macOS の要件

Homebrew でインストールする

事前準備で導入した Homebrew を使い、CLI 版の ollama をインストールする。2026-10-18 時点の配布版は0.40.6となる。Homebrew の配布情報、Ollama 0.40.6

まず配布バージョンを確認する

brew update
brew info ollama
Ollama の配布情報の確認ログ
suzuyu@macmini-m6 ~ % brew update
brew info ollama
==> Updating Homebrew...
==> Homebrew collects anonymous analytics.
Read the analytics documentation (and how to opt-out) here:
  https://docs.brew.sh/Analytics
No analytics have been recorded yet (nor will be during this `brew` run).

==> Homebrew is run entirely by unpaid volunteers. Please consider donating:
  https://github.com/Homebrew/brew#-donations

Updated 1 tap (homebrew/core).
==> New Formulae
bqlog: Cross-language logging library
==> ollama: stable 0.40.6 (bottled), HEAD
Create, run, and share large language models (LLMs)
https://ollama.com/
Not installed
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/o/ollama.rb
License: MIT
==> Dependencies
Required (1): mlx-c
==> Options
--HEAD
        Install HEAD version
==> Caveats
To start ollama now and restart at login:
  brew services start ollama
Or, if you don't want/need a background service you can just run:
  OLLAMA_FLASH_ATTENTION="1" OLLAMA_KV_CACHE_TYPE="q8_0" /opt/homebrew/opt/ollama/bin/ollama serve
==> Downloading https://formulae.brew.sh/api/formula/ollama.json
==> Analytics
install: 54,285 (30 days), 143,032 (90 days), 639,496 (365 days)
install-on-request: 54,186 (30 days), 142,813 (90 days), 639,009 (365 days)
build-error: 160 (30 days)
suzuyu@macmini-m6 ~ % 

配布版が0.40.6であることを確認してインストールする。将来の実行時に別の版になっている場合、このコマンドでは0.40.6を指定できないため、導入版と検証条件を見直して記録する

brew install ollama
brew list --versions ollama
command -v ollama
Ollama のインストールログ
suzuyu@macmini-m6 ~ % brew install ollama
==> Downloading bottle manifests
✔︎ Bottle Manifest ollama (0.40.6)                                                                                                                                                                                                                                        Downloaded   50.4KB/ 50.4KB
==> Would install 1 formula:
ollama 0.40.6
==> Would install 11 dependencies for ollama:
ca-certificates
mpdecimal
openssl@4
readline
sqlite
xz
lz4
zstd
python@3.15
mlx
mlx-c
==> Do you want to proceed with the installation? [y/n]
==> Fetching downloads for: ollama
✔︎ Bottle Manifest ca-certificates (2026-09-25)                                                                                                                                                                                                                           Downloaded    8.1KB/  8.1KB
✔︎ Bottle Manifest openssl@4 (4.0.3)                                                                                                                                                                                                                                      Downloaded   19.5KB/ 19.5KB
✔︎ Bottle Manifest sqlite (3.54.1)                                                                                                                                                                                                                                        Downloaded   22.6KB/ 22.6KB
✔︎ Bottle Manifest xz (5.8.4)                                                                                                                                                                                                                                             Downloaded   14.6KB/ 14.6KB
✔︎ Bottle Manifest zstd (1.5.7_1)                                                                                                                                                                                                                                         Downloaded   17.8KB/ 17.8KB
✔︎ Bottle Manifest lz4 (1.10.0)                                                                                                                                                                                                                                           Downloaded   13.1KB/ 13.1KB
✔︎ Bottle Manifest mlx (0.32.4)                                                                                                                                                                                                                                           Downloaded   36.4KB/ 36.4KB
✔︎ Bottle Manifest mpdecimal (4.0.1)                                                                                                                                                                                                                                      Downloaded   16.6KB/ 16.6KB
✔︎ Bottle Manifest mlx-c (0.7.0_1)                                                                                                                                                                                                                                        Downloaded   38.9KB/ 38.9KB
✔︎ Bottle Manifest readline (8.3.6)                                                                                                                                                                                                                                       Downloaded   15.3KB/ 15.3KB
✔︎ Bottle Manifest python@3.15 (3.15.0)                                                                                                                                                                                                                                   Downloaded  108.8KB/108.8KB
✔︎ Bottle ca-certificates (2026-09-25)                                                                                                                                                                                                                                    Downloaded  112.2KB/112.2KB
✔︎ Bottle mpdecimal (4.0.1)                                                                                                                                                                                                                                               Downloaded  185.8KB/185.8KB
✔︎ Bottle lz4 (1.10.0)                                                                                                                                                                                                                                                    Downloaded  286.9KB/286.9KB
✔︎ Bottle xz (5.8.4)                                                                                                                                                                                                                                                      Downloaded  788.8KB/788.8KB
✔︎ Bottle readline (8.3.6)                                                                                                                                                                                                                                                Downloaded  757.0KB/757.0KB
✔︎ Bottle mlx-c (0.7.0_1)                                                                                                                                                                                                                                                 Downloaded  225.7KB/225.7KB
✔︎ Bottle zstd (1.5.7_1)                                                                                                                                                                                                                                                  Downloaded  795.2KB/795.2KB
✔︎ Bottle sqlite (3.54.1)                                                                                                                                                                                                                                                 Downloaded    2.5MB/  2.5MB
✔︎ Bottle openssl@4 (4.0.3)                                                                                                                                                                                                                                               Downloaded   11.2MB/ 11.2MB
✔︎ Bottle ollama (0.40.6)                                                                                                                                                                                                                                                 Downloaded   17.6MB/ 17.6MB
✔︎ Bottle python@3.15 (3.15.0)                                                                                                                                                                                                                                            Downloaded   20.2MB/ 20.2MB
✔︎ Bottle mlx (0.32.4)                                                                                                                                                                                                                                                    Downloaded   76.9MB/ 76.9MB
==> Installing dependencies for ollama: ca-certificates, mpdecimal, openssl@4, readline, sqlite, xz, lz4, zstd, python@3.15, mlx and mlx-c
==> Installing ollama dependency: ca-certificates (2026-09-25)
==> Pouring ca-certificates--2026-09-25.all.bottle.tar.gz
🍺  /opt/homebrew/Cellar/ca-certificates/2026-09-25: 5 files, 204.4KB
==> Installing ollama dependency: mpdecimal (4.0.1)
==> Pouring mpdecimal--4.0.1.arm64_golden_gate.bottle.1.tar.gz
🍺  /opt/homebrew/Cellar/mpdecimal/4.0.1: 22 files, 627.4KB
==> Installing ollama dependency: openssl@4 (4.0.3)
==> Pouring openssl@4--4.0.3.arm64_golden_gate.bottle.tar.gz
🍺  /opt/homebrew/Cellar/openssl@4/4.0.3: 6,820 files, 23.8MB
==> Installing ollama dependency: readline (8.3.6)
==> Pouring readline--8.3.6.arm64_golden_gate.bottle.tar.gz
🍺  /opt/homebrew/Cellar/readline/8.3.6: 56 files, 2.6MB
==> Installing ollama dependency: sqlite (3.54.1)
==> Pouring sqlite--3.54.1.arm64_golden_gate.bottle.tar.gz
🍺  /opt/homebrew/Cellar/sqlite/3.54.1: 13 files, 5.4MB
==> Installing ollama dependency: xz (5.8.4)
==> Pouring xz--5.8.4.arm64_golden_gate.bottle.tar.gz
🍺  /opt/homebrew/Cellar/xz/5.8.4: 96 files, 2.7MB
==> Installing ollama dependency: lz4 (1.10.0)
==> Pouring lz4--1.10.0.arm64_golden_gate.bottle.3.tar.gz
🍺  /opt/homebrew/Cellar/lz4/1.10.0: 28 files, 774.1KB
==> Installing ollama dependency: zstd (1.5.7_1)
==> Pouring zstd--1.5.7_1.arm64_golden_gate.bottle.tar.gz
🍺  /opt/homebrew/Cellar/zstd/1.5.7_1: 32 files, 2.3MB
==> Installing ollama dependency: python@3.15 (3.15.0)
==> Pouring python@3.15--3.15.0.arm64_golden_gate.bottle.tar.gz
🍺  /opt/homebrew/Cellar/python@3.15/3.15.0: 3,983 files, 79MB
==> Installing ollama dependency: mlx (0.32.4)
==> Pouring mlx--0.32.4.arm64_golden_gate.bottle.tar.gz
🍺  /opt/homebrew/Cellar/mlx/0.32.4: 466 files, 240MB
==> Installing ollama dependency: mlx-c (0.7.0_1)
==> Pouring mlx-c--0.7.0_1.arm64_golden_gate.bottle.tar.gz
🍺  /opt/homebrew/Cellar/mlx-c/0.7.0_1: 40 files, 1MB
==> Installing ollama
==> Pouring ollama--0.40.6.arm64_golden_gate.bottle.tar.gz
🍺  /opt/homebrew/Cellar/ollama/0.40.6: 15 files, 54MB
==> Caveats
==> ollama
To start ollama now and restart at login:
  brew services start ollama
Or, if you don't want/need a background service you can just run:
  OLLAMA_FLASH_ATTENTION="1" OLLAMA_KV_CACHE_TYPE="q8_0" /opt/homebrew/opt/ollama/bin/ollama serve
suzuyu@macmini-m6 ~ % brew list --versions ollama
ollama 0.40.6
suzuyu@macmini-m6 ~ % command -v ollama

/opt/homebrew/bin/ollama
suzuyu@macmini-m6 ~ % 

導入版が0.40.6、コマンドのパスが /opt/homebrew/bin/ollama であることを確認し、比較期間中の更新を防ぐ

brew pin ollama

pin は導入済みの版を保持する操作で、特定の旧版を取得するものではない。比較を終えて更新する際は brew unpin ollama で解除する

起動確認

Homebrew のサービスとして起動する。手動の ollama serve が動いている場合は、そのターミナルで Ctrl+C を押して終了してから実行する

brew services start ollama
brew services list

ollama が started となることを確認する。今回は sudo を付けず、ログインユーザーのサービスとして登録する。ターミナルを閉じても動作し、次回ログイン時にも起動する。OS 起動時からログイン前に動かす設定は今回の対象外となる。Homebrew services

続けて、クライアントとサーバのバージョン、待ち受け先を確認する。サービスの起動直後に接続できない場合は、起動を待って再確認する

ollama --version
curl --fail-with-body http://127.0.0.1:11434/api/version
lsof -nP -iTCP:11434 -sTCP:LISTEN

バージョンが0.40.6、待ち受け先が 127.0.0.1:11434 であることを確認する。サーバの再起動は brew services restart ollama、停止とログイン時の自動起動解除は brew services stop ollama で行う

Homebrew のサービスには、今回の配布情報で OLLAMA_FLASH_ATTENTION=1 と OLLAMA_KV_CACHE_TYPE=q8_0 が設定されている。比較時にはサービス設定とサーバログを確認し、実際の適用状況を記録する

% curl --fail-with-body http://127.0.0.1:11434/api/version

{"version":"0.40.6"}%                   

続けてモデル一覧を確認する

ollama ls

初回はモデルが未登録でも問題ない。サーバへの接続エラーが出ず、一覧を取得できることを確認する

Ollama の起動確認ログ
suzuyu@macmini-m6 ~ % command -v ollama

/opt/homebrew/bin/ollama
suzuyu@macmini-m6 ~ % 
suzuyu@macmini-m6 ~ % brew pin ollama
suzuyu@macmini-m6 ~ % 
suzuyu@macmini-m6 ~ % ollama --version
ollama version is 0.40.6
suzuyu@macmini-m6 ~ % curl --fail-with-body http://127.0.0.1:11434/api/version

{"version":"0.40.6"}%                                                                                                                   
suzuyu@macmini-m6 ~ % lsof -nP -iTCP:11434 -sTCP:LISTEN
COMMAND  PID   USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
ollama  6835 suzuyu    4u  IPv4 0xa5d1408b43cabbe0      0t0  TCP 127.0.0.1:11434 (LISTEN)
suzuyu@macmini-m6 ~ % ollama ls
NAME    ID    SIZE    MODIFIED 
suzuyu@macmini-m6 ~ %

gpt-oss 20B のダウンロードと起動

ダウンロード

下記でモデルをダウンロードする

ollama pull gpt-oss:20b

完了後、一覧を確認する

ollama ls
% ollama ls
NAME           ID              SIZE     MODIFIED       
gpt-oss:20b    f38aa0c53da5    13 GB    51 seconds ago  

モデル名に加えて ID とサイズも記録しておく。同じタグでも配布内容が更新されることがあるため、検証時点の情報を残す

gpt-oss 20B のダウンロードログ
suzuyu@macmini-m6 ~ % ollama pull gpt-oss:20b
pulling manifest 
pulling 8ecb5d4ad617: 100% ▕███████████████████████████▏  13 GB                         
pulling d8ba2f9a17b3: 100% ▕███████████████████████████▏   18 B                         
pulling e103573196b6: 100% ▕███████████████████████████▏  139 B                         
verifying sha256 digest 
writing manifest 
success 
suzuyu@macmini-m6 ~ % ollama ls
NAME           ID              SIZE     MODIFIED       
gpt-oss:20b    f38aa0c53da5    13 GB    51 seconds ago    
suzuyu@macmini-m6 ~ % 

コンテキスト長を8Kに設定する

検証用の作業ディレクトリを作成する。以降の設定・リクエスト・結果ファイルはここに保存する

mkdir -p ~/macmini-localllm/verification
cd ~/macmini-localllm/verification

OpenAI 互換 API ではコンテキスト長を直接指定できないため、8Kを設定したモデル名を作る。再学習ではなく、取得済みモデルに設定を付ける操作となる。設定方法

cat > Modelfile.gpt-oss-20b <<'EOF'
FROM gpt-oss:20b
PARAMETER num_ctx 8192
EOF

ollama create gpt-oss-20b-8k -f Modelfile.gpt-oss-20b
ollama show --modelfile gpt-oss-20b-8k

以下の実行では gpt-oss-20b-8k を使う

モデル作成ログと Modelfile 全文
suzuyu@macmini-m6 ~ % mkdir -p ~/macmini-localllm/verification
cd ~/macmini-localllm/verification
suzuyu@macmini-m6 verification % cat > Modelfile.gpt-oss-20b <<'EOF'
FROM gpt-oss:20b
PARAMETER num_ctx 8192
EOF
suzuyu@macmini-m6 verification % ollama create gpt-oss-20b-8k -f Modelfile.gpt-oss-20b

gathering model components 
using existing layer sha256:8ecb5d4ad6172c63f9cbf1a07a04e525985a8a11a6e516da02b674c4010de716 
using existing layer sha256:fa6710a93d78da62641e192361344be7a8c0a1c3737f139cf89f20ce1626b99c 
using existing layer sha256:f60356777647e927149cbd4c0ec1314a90caba9400ad205ddc4ce47ed001c2d6 
creating new layer sha256:e8fcfac0b1234eeb8c811092c47d0dfa6f9368b9171ba2abfcabed9e22ea9dc5 
writing manifest 
success 
suzuyu@macmini-m6 verification % ollama show --modelfile gpt-oss-20b-8k
# Modelfile generated by "ollama show"
# To build a new Modelfile based on this, replace FROM with:
# FROM gpt-oss-20b-8k:latest

FROM /Users/suzuyu/.ollama/models/blobs/sha256-8ecb5d4ad6172c63f9cbf1a07a04e525985a8a11a6e516da02b674c4010de716
TEMPLATE """<|start|>system<|message|>You are ChatGPT, a large language model trained by OpenAI.
Knowledge cutoff: 2024-06
Current date: {{ currentDate }}
{{- if and .IsThinkSet .Think (ne .ThinkLevel "") }}

Reasoning: {{ .ThinkLevel }}
{{- else if or (not .IsThinkSet) (and .IsThinkSet .Think) }}

Reasoning: medium
{{- end }}

{{- $hasNonBuiltinTools := false }}
{{- if .Tools -}}
{{- $hasBrowserSearch := false }}
{{- $hasBrowserOpen := false }}
{{- $hasBrowserFind := false }}
{{- $hasPython := false }}
  {{- range .Tools }}
    {{- if eq .Function.Name "browser.search" -}}{{- $hasBrowserSearch = true -}}
    {{- else if eq .Function.Name "browser.open" -}}{{- $hasBrowserOpen = true -}}
    {{- else if eq .Function.Name "browser.find" -}}{{- $hasBrowserFind = true -}}
    {{- else if eq .Function.Name "python" -}}{{- $hasPython = true -}}
    {{- else }}{{ $hasNonBuiltinTools = true -}}
    {{- end }}
  {{- end }}
{{- if or $hasBrowserSearch $hasBrowserOpen $hasBrowserFind $hasPython }}

# Tools
{{- if or $hasBrowserSearch $hasBrowserOpen $hasBrowserFind }}

## browser

// Tool for browsing.
// The `cursor` appears in brackets before each browsing display: `[{cursor}]`.
// Cite information from the tool using the following format:
// `【{cursor}†L{line_start}(-L{line_end})?】`, for example: `【6†L9-L11】` or `【8†L3】`.
// Do not quote more than 10 words directly from the tool output.
// sources=web (default: web)
namespace browser {
{{- if $hasBrowserSearch }}

// Searches for information related to `query` and displays `topn` results.
type search = (_: {
query: string,
topn?: number, // default: 10
source?: string,
}) => any;
{{- end }}
{{- if $hasBrowserOpen }}

// Opens the link `id` from the page indicated by `cursor` starting at line number `loc`, showing `num_lines` lines.
// Valid link ids are displayed with the formatting: `【{id}†.*】`.
// If `cursor` is not provided, the most recent page is implied.
// If `id` is a string, it is treated as a fully qualified URL associated with `source`.
// If `loc` is not provided, the viewport will be positioned at the beginning of the document or centered on the most relevant passage, if available.
// Use this function without `id` to scroll to a new location of an opened page.
type open = (_: {
id?: number | string, // default: -1
cursor?: number, // default: -1
loc?: number, // default: -1
num_lines?: number, // default: -1
view_source?: boolean, // default: false
source?: string,
}) => any;
{{- end }}
{{- if $hasBrowserFind }}

// Finds exact matches of `pattern` in the current page, or the page given by `cursor`.
type find = (_: {
pattern: string,
cursor?: number, // default: -1
}) => any;
{{- end }}

} // namespace browser
{{- end }}{{/* end if has browser tools */}}
{{- if $hasPython }}

## python

Use this tool to execute Python code in your chain of thought. The code will not be shown to the user. This tool should be used for internal reasoning, but not for code that is intended to be visible to the user (e.g. when creating plots, tables, or files).

When you send a message containing Python code to python, it will be executed in a stateful Jupyter notebook environment. python will respond with the output of the execution or time out after 120.0 seconds. The drive at '/mnt/data' can be used to save and persist user files. Internet access for this session is UNKNOWN. Depends on the cluster.
{{- end }}{{/* end if hasPython */}}
{{- end }}{{/* end if has any built-in tools */}}
{{- end }}{{/* end if .Tools */}}

# Valid channels: analysis, commentary, final. Channel must be included for every message.{{ if $hasNonBuiltinTools }}
Calls to these tools must go to the commentary channel: 'functions'.
{{- end -}}<|end|>{{/* end of system */ -}}
{{- if or $hasNonBuiltinTools .System -}}
<|start|>developer<|message|>{{- if $hasNonBuiltinTools }}# Tools

## functions

namespace functions {
{{- range .Tools }}
{{- if not (or (eq .Function.Name "browser.search") (eq .Function.Name "browser.open") (eq .Function.Name "browser.find") (eq .Function.Name "python")) }}
{{if .Function.Description }}
// {{ .Function.Description }}
{{- end }}
{{- if and .Function.Parameters.Properties (gt (len .Function.Parameters.Properties) 0) }}
type {{ .Function.Name }} = (_: {
{{- range $name, $prop := .Function.Parameters.Properties }}
{{- if $prop.Description }}
  // {{ $prop.Description }}
{{- end }}
  {{ $name }}: {{ $prop | toTypeScriptType }},
{{- end }}
}) => any;
{{- else }}
type {{ .Function.Name }} = () => any;
{{- end }}
{{- end }}{{/* end if not browser tool */}}
{{- end }}{{/* end of range .Tools */}}

} // namespace functions
{{- end }}{{/* end if hasNonBuiltinTools */}}
{{- if .System}}

# Instructions

{{ .System }}
{{- end -}}
<|end|>
{{- end -}}
{{- /* Find the index of the last user message */ -}}
{{- $lastUserIdx := -1 }}
{{- $prefillingContent := false }}
{{- $prefillingThinkingOnly := false }}
{{- range $i, $msg := .Messages }}
  {{- $last := eq (len (slice $.Messages $i)) 1 -}}
  {{- if eq $msg.Role "user" }}
    {{- $lastUserIdx = $i }}
  {{- end -}}
  {{- if and $last (eq $msg.Role "assistant") (gt (len $msg.Content) 0) }}
    {{- $prefillingContent = true }}
  {{- else if and $last (eq $msg.Role "assistant") (gt (len $msg.Thinking) 0) }}
    {{- $prefillingThinkingOnly = true }}
  {{- end }}
{{- end -}}
{{- /* Now render messages */ -}}
{{- range $i, $msg := .Messages }}
  {{- $last := eq (len (slice $.Messages $i)) 1 -}}
  {{- if (ne $msg.Role "system") -}}
    {{- if eq $msg.Role "tool" -}}
      {{- if or (eq $msg.ToolName "python") (eq $msg.ToolName "browser.search") (eq $msg.ToolName "browser.open") (eq $msg.ToolName "browser.find") -}}
        <|start|>{{ $msg.ToolName }} to=assistant<|message|>{{ $msg.Content }}<|end|>
      {{- else -}}
        <|start|>functions.{{ $msg.ToolName }} to=assistant<|message|>{{ $msg.Content }}<|end|>
      {{- end -}}
    {{- else if eq $msg.Role "assistant" -}}
      {{- if and $msg.Thinking (gt $i $lastUserIdx) -}}{{- /* Show thinking only after last user message */ -}}
      <|start|>assistant<|channel|>analysis<|message|>{{ $msg.Thinking }}{{- if not $prefillingThinkingOnly -}}<|end|>{{- end -}}
      {{- end -}}
      {{- if gt (len $msg.Content) 0 -}}
        <|start|>assistant<|channel|>final<|message|>{{ $msg.Content }}{{- if not $prefillingContent -}}<|end|>{{- end -}}
      {{- end -}}
      {{- if gt (len $msg.ToolCalls) 0 -}}
        {{- range $j, $toolCall := $msg.ToolCalls -}}
          {{- $isBuiltin := or (eq $toolCall.Function.Name "python") (eq $toolCall.Function.Name "browser.search") (eq $toolCall.Function.Name "browser.open") (eq $toolCall.Function.Name "browser.find") -}}
          <|start|>assistant<|channel|>{{ if $isBuiltin }}analysis{{ else }}commentary{{ end }} to={{ if not $isBuiltin}}functions.{{end}}{{ $toolCall.Function.Name }} <|constrain|>json<|message|>{{ $toolCall.Function.Arguments }}<|call|>
        {{- end -}}
      {{- end -}}
    {{- else if eq $msg.Role "user" -}}
      <|start|>{{ $msg.Role }}<|message|>{{ $msg.Content }}<|end|>
    {{- end }}
  {{- else }}
  {{- end }}
{{- end -}}
{{- if not (or $prefillingContent $prefillingThinkingOnly) -}}
<|start|>assistant
{{- end -}}"""
PARAMETER temperature 1
PARAMETER num_ctx 8192
LICENSE """
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.
   Copyright [yyyy] [name of copyright owner]
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
       http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License."""

suzuyu@macmini-m6 verification % 

起動

下記で対話モードを開始する

ollama run gpt-oss-20b-8k

プロンプトが表示されたら、質問を入力する

日本語で回答してください。
ローカル LLM とは何かを、初めて使う人向けに3点で説明してください。

下記は実際に gpt-oss-20b が回答した結果

## ローカルLLM(Local LLM)とは?

1. **自分のコンピュータで動くAIモデル**  
   - インターネットに接続せず、PCやサーバーなど自分のハードウェア上で動作します。  
   - 例えば、メモリが16GB〜32GBのGPUがあれば、ローカルでLLMを走らせることが可能です。

2. **プライバシーとデータの安全性が高い**  
   - すべての入力・出力が自分の端末内で完結するため、外部へ情報が漏れる心配がありません。  
   - 個人情報や機密データを扱う際に、第三者にデータを送信するリスクを回避できます。

3. **高速応答とカスタマイズ性**  
   - ネットワーク遅延がなく、応答速度が高速です。  
   - モデルを自分のニーズに合わせて微調整(ファインチューニング)したり、独自のデータセットで学習させたりすることができます。  

これらが「ローカルLLM」の主な特徴です。初めて使う際は、ハードウェアのスペックやモデルのサイズに合わせて設定を調整するとスムーズに利
用できます。

上記はモデルの回答原文。「外部へ情報が漏れる心配がない」「応答速度が高速」という断定は注意で、ローカル実行だけで情報漏えい防止や高速応答が保証されるわけではないので、内容には疑問あり

終了する場合は下記を入力する

/bye

モデルの取得と起動、対話モードの終了方法は Quickstart を参照

初回起動・回答・終了のログ
suzuyu@macmini-m6 verification % ollama run gpt-oss-20b-8k
>>> 日本語で回答してください。
... ローカル LLM とは何かを、初めて使う人向けに3点で説明してください。
Thinking...
The user wants explanation in Japanese: what is a local LLM? They ask to explain in 3 points for first-time users. So we need 3 
bullet points in Japanese that explain what a local LLM is, targeted to someone new. Provide concise explanation, maybe mention 
local LLM runs on local hardware, no internet, privacy, etc. Should be friendly. Should be Japanese. No other language. Should be 
3 points. Probably numbered 1,2,3. Ensure correct grammar. Provide explanation. Let's produce.
...done thinking.

## ローカルLLM(Local LLM)とは?

1. **自分のコンピュータで動くAIモデル**  
   - インターネットに接続せず、PCやサーバーなど自分のハードウェア上で動作します。  
   - 例えば、メモリが16GB〜32GBのGPUがあれば、ローカルでLLMを走らせることが可能です。

2. **プライバシーとデータの安全性が高い**  
   - すべての入力・出力が自分の端末内で完結するため、外部へ情報が漏れる心配がありません。  
   - 個人情報や機密データを扱う際に、第三者にデータを送信するリスクを回避できます。

3. **高速応答とカスタマイズ性**  
   - ネットワーク遅延がなく、応答速度が高速です。  
   - モデルを自分のニーズに合わせて微調整(ファインチューニング)したり、独自のデータセットで学習させたりすることができます。  

これらが「ローカルLLM」の主な特徴です。初めて使う際は、ハードウェアのスペックやモデルのサイズに合わせて設定を調整するとスムーズに利
用できます。

>>> /bye
suzuyu@macmini-m6 verification % 

Ollama での動作試験

日本語で技術的な質問をする

技術的な質問もしてみて回答内容を確認してみる

ollama run gpt-oss-20b-8k
日本語で回答してください。
VXLAN と EVPN の役割の違いを、ネットワークエンジニア向けに説明してください。
最後に、それぞれの役割を表で整理してください。
Ollama + gpt-oss 20B の回答全文
## VXLAN と EVPN の役割の違い

| 項目 | VXLAN | EVPN |
|------|-------|------|
| **定義** | オーバーレイのデータプレーン(イーサネットフレームをIPパケットにカプセル化する技術) | BGPをベースとした制御プレーン(
MAC/IPの到達情報を配布するプロトコル) |
| **主な役割** | ① **ネットワークの分離**:VNI(Virtual Network Identifier)で論理セグメントを区別<br>② **拡張性**:数千~数万のセ
グメントをサポート(最大24ビット)<br>③ **スイッチングのトラフィックをIPネットワーク経由で転送** | ① **MAC/IP到達情報の配布**(
BGP‑EVPN Path AttributeでMAC/IPを広告)<br>② **MAC学習の最適化**(ARP/NDプローブやMACアドバタイズでMAC floodingを防止)<br>③ 
**L2/L3 VPN・VXLANの統合制御**(EVPN‑VE、EVPN‑IP、EVPN‑MAC) |
| **制御プレーン** | なし(静的設定やMPLS/OSPF/LDPのような別途制御プレーンが必要) | あり(BGPを利用) |
| **トラフィックフロー** | データプレーンのみ | データプレーン+制御プレーン |
| **主要な課題** | ① MACアドレステーブルの拡張性が低い(MAC flooding)<br>② L3VPNのサポートが限定的 | ① BGPの複雑さ<br>② 適切な
BGPフィルタリング・ポリシー設定が必要 |
| **代表的なユースケース** | データセンターファブリックでの単純なL2オーバーレイ、VM間の通信 | データセンターファブリックにおける
L2/L3 VPN、マルチテナント環境、VTEP間の冗長性・ロードバランシング |
| **拡張性** | 1VNI=1セグメントのため、セグメント数が増えるとVTEPが増加し管理が難しくなる | BGPを使うことで大規模なトポロジーでも集
中管理が容易 |

---

## さらに掘り下げてみると

| 観点 | VXLAN | EVPN |
|------|-------|------|
| **カプセル化方式** | 1レイヤ2フレームをVXLANヘッダ+IPヘッダ+UDPヘッダで包む | 同上(VXLANヘッダが必要) |
| **VNI(Virtual Network Identifier)** | 24ビット | 同上 |
| **ルーティング/ブリッジング** | L2オーバーレイのみ | L2 + L3 ルーティングを同一インフラで実現 |
| **冗長性** | 物理リンクの冗長性は別途IP/TCP/UDPの冗長化で対応 | BGPが冗長性を提供(EVPN‑VEでVRRP的機能) |
| **スケーラビリティ** | VTEP 1つにつき1VNIで限界がある | BGPルーティングでVTEP間の情報を効率的に配布 |
| **オープンスタンダード** | RFC7348(VXLAN) | RFC7432(EVPN) |
| **ベンダーサポート** | ほぼ全ベンダーで実装 | 大規模ネットワーク機器で主に実装 |

---

## まとめ

- **VXLAN** は「データプレーン」だけを担い、オーバーレイネットワークを作るためのカプセル化技術です。セグメントの数が増えると管理が煩
雑になり、MAC flooding の問題も生じます。  
- **EVPN** は「制御プレーン」+「データプレーン」を統合したもので、BGP を利用して MAC/IP の到達情報を効率的に配布し、L2/L3 VPN の統
合管理を実現します。  
- 大規模なデータセンターファブリックやマルチテナント環境では **VXLAN + EVPN** の組み合わせが主流です。VXLAN がトラフィックをカプセル
化し、EVPN がそのカプセル化されたトラフィックを制御・最適化します。

| 役割 | VXLAN | EVPN |
|------|-------|------|
| データプレーン | オーバーレイのイーサネットフレームをIPネットワーク上で転送 | VXLAN ヘッダ付きデータフローを制御 |
| 制御プレーン | なし(外部制御が必要) | BGP で MAC/IP 受信情報を広告・学習 |
| 分離/セグメント化 | VNI により論理セグメントを作成 | 同一 VNI で L2/L3 VPN を提供 |
| 拡張性 | 物理リンクや VTEP でスケール制限 | BGP の多発で大規模スケール対応 |
| MAC 学習 | 物理VTEP 内でのテーブル管理 | BGP‑MAC アドバタイズで動的学習 |
| 主要ユースケース | L2 オーバーレイ、簡易拡張 | L2/L3 VPN、マルチテナント、冗長化 |

このように、**VXLAN** は「転送」だけを担い、**EVPN** は「情報配布」と「制御」を担うことで、現代のデータセンターネットワークを効率的
に構築・運用できるようになっています。

回答内容の評価

以下は GPT-6 Astra (高) による回答評価。技術的な指摘には照合先の RFC・公式資料が併記されている。実機での動作確認とは別の AI による評価であり、全項目を読み込んで個別に評価した結果を示すものではない

確認項目 結果
日本語として読みやすいか △:見出しと表で整理され、大意は読み取れる。ただし「BGP の多発」「MAC/IP 受信情報」など、不自然で意味が曖昧な表現がある
VXLAN と EVPN の役割を区別できているか △:冒頭では VXLAN をデータプレーン、EVPN を BGP ベースの制御プレーンと説明できている。一方、後半で EVPN 自体を「データプレーン+制御プレーン」とし、VXLAN と組み合わせた構成全体との区別が曖昧になっている
指定した表形式で回答できているか ○:比較表を出力し、まとめにも役割を整理した表がある。ただし掲載ログにはセル途中の改行があり、Markdown の表として表示する場合は整形が必要
説明に誤りや補足が必要な箇所はあるか あり:VTEP と VNI の関係、EVPN のカプセル化方式、制御プレーンの例、MAC/IP 広告と冗長化の用語などに修正が必要。技術説明としてそのまま採用するのは難しい

主な修正点は下記のとおり。上の回答ログは評価対象として残し、補足を分けて記載する

  • 「VTEP 1つにつき1VNI」は誤り:1つの VTEP で複数の VNI を扱えるため、VNI が増えると必ず VTEP も増えるという説明は不正確。Cisco の VXLAN 構成説明
  • EVPN に VXLAN ヘッダが必須という説明は誤り:EVPN は MPLS でも利用できる。今回の EVPN-VXLAN 構成では、VXLAN がデータのカプセル化・転送、BGP EVPN が到達情報の配布を担う、と説明する方が明確。RFC 7432、RFC 8365 §5
  • VXLAN の制御プレーンの例が不適切:OSPF はアンダーレイの IP 到達性を提供する用途で使えるが、EVPN と同じ MAC/IP 広告機能を担うものではない。VXLAN は EVPN なしの flood-and-learn 方式でも利用でき、「MPLS/OSPF/LDP が別途必要」という並べ方は役割を混同している。RFC 7348 §4
  • MAC/IP 広告と冗長化の用語に混乱がある:MAC/IP 情報は EVPN の MAC/IP Advertisement route(Route Type 2)で広告する。「EVPN-VEでVRRP的機能」という説明では、EVPN のマルチホームや DF 選出などの仕組みを正しく説明できていない。RFC 7432 §7.2、同 §8
  • フラッディングを防止できるという説明には条件が必要:制御プレーンでの MAC/IP 学習や ARP/ND 抑制により削減できるが、EVPN を使えばすべての BUM(Broadcast・Unknown Unicast・Multicast)転送がなくなるわけではない。RFC 7432 §10、RFC 8365 §8.3.3

今回の1回答では、日本語で応答し、指定した表形式を作ることは確認できた。
一方、もっともらしい用語や表の中に技術的な誤りが混在しており、ネットワーク設計や設定の判断に使うには一次資料との照合が必要となった。
この結果だけでモデル全体の品質や、API 試験での回答品質までは評価しているわけではない

GPU の利用状況を確認する

モデルがロードされている間に、別のターミナルで ollama ps を実行する

ollama ps
NAME                     ID              SIZE     PROCESSOR    CONTEXT    RUNNER      UNTIL
gpt-oss-20b-8k:latest    a1c927e2b35f    12 GB    100% GPU     8192       llamacpp    9 minutes from now

今回は PROCESSOR が 100% GPU となり、モデル全体が GPU にロードされていることを確認できた。これは GPU への配置を示す表示で、GPU 使用率が常時100%という意味ではない。GPU 利用状況の確認方法

一覧が空の場合は、ollama run gpt-oss-20b-8k で再度質問し、回答後すぐに ollama ps を実行する

メモリ使用量を確認する

macOS の「アクティビティモニタ」を開き、「メモリ」タブで使用状況を確認する

macmini-memory.png

ollama run gpt-oss-20b-8k を実行して質問を始めると、実行前よりメモリプレッシャーのグラフが上昇した。撮影時点では緑色の範囲に収まっており、Mac 全体の使用済みメモリは24.36GB、スワップ使用領域は0バイトだった

使用済みメモリには macOS やほかのアプリも含まれるため、Ollama 単体の使用量とは区別する

Ollama を API 経由で呼び出す

比較では両方のサーバが対応する /v1/chat/completions を使用し、curl でチャットの応答を確認する。Ollama の API 互換性

前項で作成した verification ディレクトリで、リクエストをファイルに保存する

cat > ollama-request.json <<'EOF'
{
  "model": "gpt-oss-20b-8k",
  "messages": [
    {
      "role": "user",
      "content": "日本語で回答してください。Kubernetes の Pod と Deployment の違いを3点、合計300字以内で説明してください。"
    }
  ],
  "reasoning_effort": "low",
  "temperature": 0.7,
  "max_tokens": 2048,
  "stream": false
}
EOF

curl --fail-with-body http://127.0.0.1:11434/v1/chat/completions \
  -H 'Content-Type: application/json' \
  --data-binary @ollama-request.json \
  -o ollama-response.json \
  -w 'HTTP=%{http_code} total=%{time_total}s\n'

cat ollama-response.json

HTTP 200で、choices[0].message.content に回答本文があることを確認する。思考出力と回答本文を区別し、finish_reason が length なら出力上限に達した結果として記録する

gpt-oss の思考強度は low とする。対応する値は /api/show でも確認できる。思考設定

curl --fail-with-body http://127.0.0.1:11434/api/show \
  -H 'Content-Type: application/json' \
  -d '{"model":"gpt-oss-20b-8k"}' \
  -o ollama-model-info.json

実環境での結果

HTTP 200で日本語の回答が返り、API 経由の応答を確認できた。今回の1回の完了時間は約7.68秒だった。ロードの有無を確認していないため、初期確認時の参考値として記録する

確認項目 結果
HTTP ステータス 200
応答完了までの時間 7.679472秒
終了理由 stop。出力上限による打ち切りではない
日本語・3点の指定 日本語で3点に整理して回答
合計300字以内 未達。回答本文は Markdown 記号・空白・改行を含め455文字
技術説明の正確さ 再起動とスケーリングの説明に補足が必要

回答本文とレスポンス全体は下記のログに記載する。内容には次の注意点があった (GPT-6 Astra での評価)

  • 再起動の説明:単独の Pod でも、内部のコンテナは restartPolicy に従って kubelet が再起動する。Pod 自体の再作成と、コンテナの再起動を区別する必要がある。Pod のライフサイクル
  • 自動スケールの説明:spec.replicas は希望する Pod 数の指定で、負荷に応じた自動増減には HPA などを使う。「spec.replicas で自動スケール」という表現は、この違いが曖昧になっている。Deployment のレプリカ数、HPA
Ollama のローカル API 応答全文
suzuyu@macmini-m6 verification % >....                                                                                                  
  "model": "gpt-oss-20b-8k",
  "messages": [
    {
      "role": "user",
      "content": "日本語で回答してください。Kubernetes の Pod と Deployment の違いを3点、合計300字以内で説明してください。"
    }
  ],
  "reasoning_effort": "low",
  "temperature": 0.7,
  "max_tokens": 2048,
  "stream": false
}
EOF

curl --fail-with-body http://127.0.0.1:11434/v1/chat/completions \
  -H 'Content-Type: application/json' \
  --data-binary @ollama-request.json \
  -o ollama-response.json \
  -w 'HTTP=%{http_code} total=%{time_total}s\n'

cat ollama-response.json
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1742  100  1403  100   339    182     44  0:00:07  0:00:07 --:--:--   385
HTTP=200 total=7.679472s
{"id":"chatcmpl-420","object":"chat.completion","created":1792305076,"model":"gpt-oss-20b-8k","system_fingerprint":"fp_ollama","choices":[{"index":0,"message":{"role":"assistant","content":"**Kubernetes の Pod と Deployment の違い(300字以内)**\n\n1. **単体 vs. 管理単位**  \n   - *Pod* は 1 つまたは複数のコンテナをまとめた最小実行単位。  \n   - *Deployment* は Pod の望ましい状態(レプリカ数、イメージバージョンなど)を宣言し、クラスタに自動で複製・更新を行う管理オブジェクト。\n\n2. **スケーリング・ローリングアップデート**  \n   - Pod は手動で作成・削除する必要があり、スケールアウトは手作業。  \n   - Deployment は `spec.replicas` で自動スケール、`spec.strategy` でローリングアップデートを実現。\n\n3. **自己回復機能**  \n   - Pod がクラッシュすると自動再起動は行われない。  \n   - Deployment は ReplicaSet を介し、失敗した Pod を検知して新規に再作成し、常に指定レプリカ数を維持する。","reasoning":"Need 3 points, 300 characters total? \"合計300字以内\" means total characters \u003c=300. Provide concise Japanese."},"finish_reason":"stop"}],"usage":{"prompt_tokens":95,"prompt_tokens_details":{"cached_tokens":0},"completion_tokens":289,"total_tokens":384}}
suzuyu@macmini-m6 verification % 
suzuyu@macmini-m6 verification % curl --fail-with-body http://127.0.0.1:11434/api/show \
  -H 'Content-Type: application/json' \
  -d '{"model":"gpt-oss-20b-8k"}' \
  -o ollama-model-info.json
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 40787    0 40761  100    26  10.5M   7082 --:--:-- --:--:-- --:--:-- 12.9M
suzuyu@macmini-m6 verification % 

Splash + Qwen3.8 のインストールと起動・応答確認

次に Splash と Qwen のインストールを実施する

前提条件

Splash は Apple M3 以降・macOS 26.4 以降が必要となる。OS を確認する

sw_vers

実行環境では下記の通り

% sw_vers
ProductName:            macOS
ProductVersion:         27.0.1
BuildVersion:           26A434

Splash のインストール

事前準備で導入した Homebrew を使う。調査時点の公式 tap は1.3.2を配布している。インストール前に配布バージョンを確認する

brew tap incoai/tap
brew info incoai/tap/splash

配布版が 1.3.2 であることを確認してからインストールする。通常の brew install は実行時点の formula を使うため、将来も1.3.2が入ることを保証するコマンドではない。配布版が変わっている場合は、1.3.2 の公式リリース の配布手順を確認する

brew install incoai/tap/splash
brew list --versions splash
splash --version
splash serve --help

実機では brew list --versions splash と splash --version の両方で1.3.2を確認できた。splash serve --help も正常に表示され、今回使用する --max-context と --language-only を確認できた

インストール先は /opt/homebrew/Cellar/splash/1.3.2、Homebrew が表示した本体の容量は250.7MBだった。モデルは後続の手順で取得する

インストール後の案内に表示される UD-Q4_K_M は起動例であり、この時点でモデルが取得されたわけではない。今回は次の手順で UD-IQ4_XS を指定する

比較期間中の brew upgrade による更新を防ぐため、確認済みの版を pin する。pin は導入済みの版を保持する操作で、特定の旧版をインストールする機能ではない。Homebrew の pin

brew pin incoai/tap/splash

比較終了後、更新を再開する場合は brew unpin incoai/tap/splash で解除する

Splash のインストールログとヘルプ全文
suzuyu@macmini-m6 verification % sw_vers
ProductName:            macOS
ProductVersion:         27.0.1
BuildVersion:           26A434
suzuyu@macmini-m6 verification % brew tap incoai/tap
==> Downloading Homebrew API data
✔︎ JSON API packages.arm64_golden_gate.jws.json                                                              Downloaded   15.8MB/ 15.8MB
==> Tapping incoai/tap
Cloning into '/opt/homebrew/Library/Taps/incoai/homebrew-tap'...
Tapped 1 formula (15 files, 33.8KB).
suzuyu@macmini-m6 verification % brew info incoai/tap/splash
==> incoai/tap/splash: stable 1.3.2 (bottled)
Local inference engine for Apple silicon, built around the model
https://github.com/incoai/splash
Not installed
From: https://github.com/incoai/homebrew-tap/blob/HEAD/Formula/splash.rb
Tap: incoai/tap
License: Apache-2.0
==> Requirements
Required: arm64 architecture ✔, macOS >= 26 ✔, Splashmacos ✔
==> Caveats
Serve a model:
  splash serve --model unsloth/Qwen3.8-27B-GGUF:UD-Q4_K_M
suzuyu@macmini-m6 verification % brew install incoai/tap/splash
==> Auto-updating Homebrew...
Adjust how often this is run with `$HOMEBREW_AUTO_UPDATE_SECS` or disable with
`$HOMEBREW_NO_AUTO_UPDATE=1`. Hide these hints with `$HOMEBREW_NO_ENV_HINTS=1` (see `man brew`).
==> Trusted formula incoai/tap/splash
==> Fetching downloads for: splash
✔︎ Bottle splash (1.3.2)                                                                                     Downloaded   74.5MB/ 74.5MB
==> Installing splash from incoai/tap
==> Pouring splash-1.3.2.arm64_tahoe.bottle.tar.gz
🍺  /opt/homebrew/Cellar/splash/1.3.2: 7,079 files, 250.7MB
==> Caveats
zsh completions have been installed to:
  /opt/homebrew/share/zsh/site-functions
==> splash
Serve a model:
  splash serve --model unsloth/Qwen3.8-27B-GGUF:UD-Q4_K_M
suzuyu@macmini-m6 verification % brew list --versions splash
splash 1.3.2
suzuyu@macmini-m6 verification % splash --version
Splash 1.3.2
suzuyu@macmini-m6 verification % splash serve --help
usage: splash serve [-h] --model OWNER/REPO[:VARIANT] [--revision REVISION] [--draft-model DRAFT] [--language-only] [--offline]
                    [--port PORT] [--host ADDRESS] [--allowed-host HOST] [--allowed-origin ORIGIN] [--api-key KEY] [--no-webui]
                    [--max-memory SIZE] [--max-context TOKENS] [--kv-format {int8,bf16}] [--state-format {fp32,bf16}]
                    [--idle-release DURATION] [--max-cache-disk SIZE] [--max-cache-disk-writes SIZE] [--persistent-cache]
                    [--cache-dir DIRECTORY] [--served-model-name NAME] [--announce-served-name] [--default-reasoning-effort EFFORT]
                    [--thinking-token-budget N] [--thinking-end-text TEXT] [--max-request-size SIZE] [--max-image-pixels PIXELS]
                    [--request-timeout DURATION] [--queue-size REQUESTS] [--decode-share SHARE] [--disable-ane] [--allow-idle-sleep]

Load an upstream model, automatically select its DFlash2 draft, and serve in the foreground.

options:
  -h, --help            show this help message and exit

model:
  --model OWNER/REPO[:VARIANT]
                        upstream Hugging Face model, with a GGUF variant after ':' (e.g. :UD-Q4_K_M)
  --revision REVISION   model branch, tag or commit (default: the repository's default branch)
  --draft-model DRAFT   DFlash2 draft repository or local directory to use instead of the automatically selected one
  --language-only       skip vision preparation and loading; image and PDF input is refused
  --offline             start the installed model without contacting the Hugging Face Hub (as HF_HUB_OFFLINE=1)

network and access:
  --port PORT           HTTP port (default: SPLASH_PORT or 8000)
  --host ADDRESS        HTTP bind address (default: 127.0.0.1; 0.0.0.0 for all IPv4 interfaces); clients use an IP address, localhost
                        or a name given with --allowed-host
  --allowed-host HOST   additional HTTP Host name to accept, e.g. mymac.local; does not change the bind address (repeatable)
  --allowed-origin ORIGIN
                        origin whose pages may call the API from a browser or webview, e.g. tauri://localhost; '*' for any
                        (repeatable)
  --api-key KEY         require this key as a bearer token or x-api-key on every route but the chat page, /health and /ready (default:
                        SPLASH_API_KEY)
  --no-webui            disable the chat page

memory and context:
  --max-memory SIZE     Metal memory budget, e.g. 28G; it can only lower the automatic budget (default: auto)
  --max-context TOKENS  context token limit, e.g. 100K (K = 1024 tokens); it can only lower the automatic limit, the model's native
                        window when memory holds it (default: auto)
  --kv-format {int8,bf16}
                        target KV cache storage (default: int8); bf16 uses more memory
  --state-format {fp32,bf16}
                        recurrent state storage (default: fp32); bf16 halves each state, so memory and the SSD cache hold about twice
                        as many
  --idle-release DURATION
                        time without a request before the engine unwires its memory and frees the weights: seconds, or with an s, m or
                        h suffix, e.g. 30m (default: 10m); off keeps both, though POST /v1/memory/release still frees the weights

SSD cache:
  --max-cache-disk SIZE
                        SSD quota for cached KV pages and states: auto follows the volume's free space up to 32G, 0 disables, or a
                        size such as 16G (default: auto)
  --max-cache-disk-writes SIZE
                        the most the SSD cache writes a day, e.g. 200G; the state a conversation resumes from and KV pages pass it
                        (default: 0, no limit)
  --persistent-cache    keep the SSD cache across restarts
  --cache-dir DIRECTORY
                        where --persistent-cache keeps its files; needs --persistent-cache (default: ~/Library/Caches/Splash/prefix-
                        cache)

API:
  --served-model-name NAME
                        additional API model name (repeatable); responses report the loaded model ID unless --announce-served-name
  --announce-served-name
                        report the first --served-model-name in API responses and list it first in /v1/models; /status keeps the
                        loaded model ID (needs --served-model-name)
  --default-reasoning-effort EFFORT
                        effort of Chat and Responses requests that set none: none, minimal, low, medium, high, xhigh or max (default:
                        SPLASH_DEFAULT_REASONING_EFFORT, else the model template's)
  --thinking-token-budget N
                        thinking tokens of a thinking request that sets no thinking_token_budget, after which its thinking closes
                        (default: SPLASH_THINKING_TOKEN_BUDGET, else no cap)
  --thinking-end-text TEXT
                        text that ends thinking where a thinking budget runs out, such as a sentence that turns to the answer, then
                        </think> as its last token; at most 64 tokens (default: SPLASH_THINKING_END_TEXT, else </think> alone)

requests:
  --max-request-size SIZE
                        maximum HTTP request body size, e.g. 128M (default: 128M); shared input budget is max(512M, twice this limit)
  --max-image-pixels PIXELS
                        maximum resized pixels per image, 65536–4194304 (default: 4194304); bounds the vision scratch one image needs
  --request-timeout DURATION
                        time before a queued or in-flight request expires with 504: seconds, or with an s, m or h suffix, e.g. 30m
                        (default: none)
  --queue-size REQUESTS
                        requests admitted at once, running or waiting; more get 503 (default: 32)
  --decode-share SHARE  decode time owed per unit of prefill time while other requests decode (default: 0.5; 0 alternates one command
                        each)
  --disable-ane         prefill on the GPU alone (default: a dense model's long prompts also use the Neural Engine when that is
                        faster)
  --allow-idle-sleep    let the Mac sleep automatically while requests run (default: it stays awake until they finish; the display may
                        still sleep)

Examples:
  splash serve --model unsloth/Qwen3.8-27B-GGUF:UD-Q4_K_M
  splash serve --model unsloth/Qwen3.6-35B-A3B-GGUF:UD-Q4_K_M --max-context 128K

SIZE is bytes, or a number with K, M or G (1024-based), such as 28G.
DURATION is seconds, or a number with s, m or h, such as 30m.

After Ready, open http://127.0.0.1:PORT in a browser on this Mac, or
connect an installed agent; both reach the server on loopback, which
the default --host and 0.0.0.0 include. The startup summary and
/status report the effective context limit; a client may impose a
smaller one. Keep this terminal open; Ctrl+C stops serving.
suzuyu@macmini-m6 verification % brew pin incoai/tap/splash
suzuyu@macmini-m6 verification % 

Ollama のモデルをアンロードする

ollama stop gpt-oss-20b-8k
ollama ps

一覧にモデルが残っていないことを確認する。他のモデルが表示される場合も、使用しているクライアントを止めてからアンロードする

Ollama のサービスは起動したまま、モデルをロードしない状態で Splash の試験へ進む

モデルの取得とサーバ起動

今回は起動ログを確認しやすく、比較後に Ctrl+C で停止できるよう、ターミナルで splash serve を実行する。Splash 1.3.2 の Homebrew formula にはサービス定義がないため、Ollama と同じ brew services start では起動しない。Homebrew formula

--model はリポジトリ名と量子化を指定する。下記では Qwen3.8-27B-UD-IQ4_XS.gguf が選択される

splash serve \
  --model unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS \
  --max-context 8K \
  --language-only

初回に対象モデルと対応するドラフトモデルを取得する。Ready が表示されたら、このターミナルを開いたまま別のターミナルで API を確認する。ここではテキスト用途のため --language-only を指定している

起動ログのモデル・ドラフトのリビジョン、利用可能なコンテキスト長、メモリ計画を記録する。再現時は記録した対象モデルのコミットを --revision に指定できる。モデル保存とリビジョン

今回の32GB環境では、指定した UD-IQ4_XS で Ready まで到達し、8K・テキスト専用でサーバを起動できた

Splash のモデル取得・起動ログ
suzuyu@macmini-m6 ~ % splash serve \
  --model unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS \
  --max-context 8K \
  --language-only
No GGUF is named for :UD-IQ4_XS alone; using Qwen3.8-27B-UD-IQ4_XS.gguf, the only one whose name ends in -UD-IQ4_XS.
Selected Qwen3.8-27B-UD-IQ4_XS.gguf from unsloth/Qwen3.8-27B-GGUF.
Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
config.json: 100%|████████████████████████████████████████████████████████████████████████████████| 1.24k/1.24k [00:00<00:00, 2.51MB/s]
Fetching 1 file(s), 3.85 GB, from incoai/Qwen3.8-27B-DFlash2@015e795645c7; cached files are reused.
Fetching 2 files: 100%|█████████████████████████████████████████████████████████████████████████████████| 2/2 [03:36<00:00, 108.15s/it]
Download complete: : ██████████████████████████████████████████████████████████████████████████████████████████████| 3.34GB, 10.9MB/s  Installing unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS as Qwen3.8-27B (gguf); draft incoai/Qwen3.8-27B-DFlash2; vision disabled..85GB, 33.5MB/s  
Fetching 1 file(s), 14.25 GB, from unsloth/Qwen3.8-27B-GGUF@4ca720788d1e; cached files are reused.
Fetching 1 files: 100%|█████████████████████████████████████████████████████████████████████████████████| 1/1 [15:45<00:00, 945.69s/it]
Download complete: : ██████████████████████████████████████████████████████████████████████████████████████████████| 14.2GB, 11.4MB/s  
Reconstruction complete: 100%|████████████████████████████████████████████████████████████████████████████| 14.3GB / 14.3GB, 29.9MB/s  
Reconstruction complete: 100%|████████████████████████████████████████████████████████████████████████████| 3.85GB / 3.85GB, 33.5MB/s  
Download complete: : ██████████████████████████████████████████████████████████████████████████████████████████████| 3.34GB, 10.9MB/s  
16:12:07 Loading · unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS
16:12:07 Chat template · patched to render later system messages in place (the original template rejects them), generation prompt 5-7 tokens
16:12:10 Weights loaded in 3.09 s.
16:12:10 Kernel policy for GPU family 11 with 12 cores.
16:12:10 Setting up the Neural Engine FFN split (splash serve --disable-ane keeps the FFN on the GPU).
16:13:17 Neural Engine FFN split at share 0.76 for chunks of 512 rows or more: 30.9 ms per 2048-row FFN layer against 63.5 on the GPU alone, 1.0% from the GPU alone on the Neural Engine's part (calibrated in 67.2 s).
16:13:17 SSD cache: up to 32.0 GiB of temporary files in /var/folders/vm/x0l5wqcx7njgdqpfvbpp20n00000gn/T/ (its automatic quota, while its volume has room beyond the 10.0 GiB it keeps free); KV pages of 1040 KiB and states of 152 MiB, a state's write staging through 152 MiB of the memory plan; --max-cache-disk 0 turns it off.
16:14:15 Ready · unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS · context 8K · language only · http://127.0.0.1:8000

確認項目 起動ログの結果
対象ファイル Qwen3.8-27B-UD-IQ4_XS.gguf
対象モデルのリビジョン 4ca720788d1e(ログの短縮表記)
ドラフトモデル incoai/Qwen3.8-27B-DFlash2、リビジョン 015e795645c7(短縮表記)
取得対象の容量 対象14.25GB + ドラフト3.85GB = 約18.10GB(ログ表示値。関連ファイルやキャッシュは別)
コンテキスト・入力 8K、language only
待ち受け先 http://127.0.0.1:8000
SSD キャッシュ 一時ファイルの上限32.0GiB。実際に32GiB使用したことを示す値ではない

冒頭の No GGUF is named... はファイル名の選択に関する案内で、その直後に目的の GGUF が選ばれている。HF Hub の未認証アクセスの警告も出たが、今回はダウンロードと起動が完了した。警告にある HF_TOKEN はモデル配布元への認証用で、Splash の API 認証キーとは別になる

重みのロードは3.09秒だったが、その後に Neural Engine の初期調整などが続いた。ダウンロード後の Loading(16:12:07)から Ready(16:14:15)までは約2分8秒で、3.09秒は起動全体の時間ではない

GPU 向け設定と Neural Engine の FFN 分担の初期調整もログで確認できた。回答中のメモリ使用状況と API 応答は、続く手順で確認する

日本語で技術的な質問をする

Ollama と同じ VXLAN / EVPN の質問を送り、日本語の読みやすさや説明の正確さ、表形式への対応を確認する

splash serve で Ready が表示されたら、Mac mini のブラウザで http://127.0.0.1:8000 を開く。サーバ用のターミナルは開いたままにする。Splash のチャット画面

macmini-splash-webchat.png

会話履歴のない状態で、下記の質問を入力する

日本語で回答してください。
VXLAN と EVPN の役割の違いを、ネットワークエンジニア向けに説明してください。
最後に、それぞれの役割を表で整理してください。

下記が Splash での回答

Splash + Qwen3.8 の回答全文
ネットワークエンジニア向けに整理すると、結論から言うと、

- **VXLAN:Overlay の「データ面・カプセル化」**  
- **EVPN:Ethernet サービスの「制御面・制御情報」**  

という違いです。  
現場では多くの場合、**EVPN over VXLAN** として一緒に使われ、**VXLANがデータ面、EVPNが制御面**を担います。

---

## 1. VXLAN の役割

VXLAN は、物理ネットワークの上に Overlay L2/L3 ネットワークを作るための **カプセル化プロトコル** です。

### 主な役割

- **VLANフレームを IP ネットワーク上で運ぶ**
  - 802.1Q フレームを VXLAN ヘッダ付きでカプセル化
  - 通常は UDP port 4789 を使う
- **Underlay の IP ネットワーク上を転送する**
  - Underlay は通常、IPルーティング
  - IGP / OSPF / BGP 等で VTEP 間経路を作る
- **VNI で Overlay を識別する**
  - VNI は 24 bit
  - 100万クラス以上の Overlay 空間を表現可能
  - VLAN ID を直接使わず、Overlay 側の識別子として VNI を使う
- **VTEP 間で Overlay 通信を可能にする**
  - VTEP は VXLAN のカプセル化/デカプセル化を行うポイント

### VXLAN 単体の特徴

VXLAN 自体には、強い制御面は備わっていません。

つまり、VXLAN 単体では次のような処理が必要になります。

- **BUM トラフィックの配布**
  - Broadcast / Unknown unicast / Multicast
- **マルチキャストや PIM、Static、Ingress Replication 等との連携**
- **MAC address learning**
  - Flooding して学習する場合
- **ARP / MAC 学習の最適化**

つまり、VXLAN は「フレームをどう運ぶか」のデータ面を提供しますが、  
「どの VTEP が見えるか」「どの MAC がどこにいるか」「BUM はどう送るか」を賢く制御するのは別レイヤです。

---

## 2. EVPN の役割

EVPN は、BGP を制御面に使った Ethernet VPN であり、  
**L2/L3 Ethernet サービスを制御するための制御面プロトコル**です。

### 主な役割

- **MAC 学習を制御面に委譲する**
  - flooding による学習ではなく、BGP EVPN の MAC route で情報を共有
- **VTEP の発見**
  - どの VTEP がどの Ethernet Segment / VNI に見えているか、BGP で制御
- **MAC / IP アドレスのルーティング制御**
  - MAC route
  - IP prefix route
- **Ethernet Segment 制御**
  - ESI: Ethernet Segment Identifier
  - 同一セグメント内の VTEP 群の識別
- **マルチテナント制御**
  - RT / RD / Community 等で VNI、VRF、テナント分離を制御
- **BUM トラフィックの最適化**
  - 従来は PIM マルチキャスト等に頼ることが多かったが
  - EVPN なら **Head-End Replication** 等により、単播で BUM を配送可能
- **MAC Mobility / Loop Prevention**
  - MAC が移動した際の制御
  - 循環や誤学習の抑制に寄与する

### EVPN が扱う主要な Route Type

- **Type 1: Ethernet Segment route**
  - ESI 情報
  - Ethernet Segment 内の VTEP 情報
- **Type 2: MAC/IP route**
  - MAC アドレスと IP アドレス
  - MAC 学習情報
- **Type 3: INSET route**
  - VNI 内でどの VTEP が見えているか
  - BUM 配送の制御
- **Type 4: EVPN Multicast route**
  - BUM を効率的に配送するための制御

---

## 3. VXLAN と EVPN の関係

実務で最も多いのが、**EVPN over VXLAN** です。

この場合の役割分担は、

- **VXLAN**
  - フレームをカプセル化して Underlay IP 上に運ぶ
  - VNI で Overlay を識別する
- **EVPN**
  - MAC、IP、VTEP、Ethernet Segment などの制御情報を BGP で配布する
  - BUM 配送を制御する
  - 大規模マルチテナント環境での制御面を提供する

つまり、

> **VXLAN は「車体」、EVPN は「ナビと交通管理」**  
> という比喩が近いと思います。

VXLAN 単体でも動作しますが、大規模クラウド、マルチテナント、データセンター Overlay では EVPN と組み合わせることで制御面の拡張性・安定性が上がります。

---

## 4. 具体例で見た違い

ある VNI 内で、未知宛てフレームやマルチキャストを配送する場合のイメージです。

### VXLAN 単体の場合

- BUM を配信するために、マルチキャストグループを使う
- PIM や static multicast と組み合わせる
- VTEP 間で flooding する必要がある場面もある

### EVPN over VXLAN の場合

- BGP EVPN で各 VTEP の存在を把握する
- Type 2 で MAC/IP 情報を交換する
- Type 3 で INSET を交換し、BUM を制御する
- Head-End Replication を使えば、PIM 不要で単播ベースにBUMを配送できる

したがって、

- **VXLAN:どのフレームをどうカプセル化してどこに運ぶか**
- **EVPN:どの VTEP が存在し、どの MAC/IP がどこにあり、BUMをどう制御するか**

という違いになります。

---

## 5. 役割の違いを表で整理

| 観点 | VXLAN | EVPN |
|---|---|---|
| 位置づけ | Overlay データ面プロトコル | Ethernet サービスの制御面プロトコル |
| 主な役割 | VLANフレームを IP ネットワーク上にカプセル化して転送 | MAC/IP/VTEP/Segment 情報を BGP で制御・配布 |
| 動作層 | L2/L3 Overlay のデータ面 | L2/L3 Ethernet サービスの制御面 |
| カプセル化 | 本体がカプセル化を行う | カプセル化自体は定義しない。VXLAN / MPLS / SRv6 等と組み合わせる |
| 識別子 | VNI で Overlay を識別 | RT / RD / VNI / ESI 等を用いてサービスやSegmentを制御 |
| Underlay 依存 | IPルーティングに依存。IGP/BGP 等で VTEP 間経路を作る | 制御面は BGP MP-BGP を使用。Underlay のルーティングとも連携 |
| MAC 学習 | flooding や ARP/ND の結果から学習する場合がある | BGP EVPN の MAC route で学習情報を共有 |
| BUM トラフィック | マルチキャスト、PIM、static、ingress replication 等と連携 | INSET / EVPN Multicast route / Head-End Replication で制御・最適化 |
| VTEP 発見 | 単体では static や multicast の制御に頼る場合がある | BGP で VTEP / INSET を動的に制御 |
| スケール | VNI 空間は広いが、制御面を外部に依存 | 大規模マルチテナント、DCI、データセンター Overlay に強い |
| 主な技術要素 | VTEP、VNI、VXLAN encapsulation、UDP 4789 | MP-BGP、Route Type 1–4、ESI、MAC/IP route、INSET |
| 相互関係 | EVPN のデータ面として使われることが多い | VXLAN の制御面として使われることが多い |
| 単体利用 | 可能。static / multicast 制御と組み合わせる | 可能。VXLAN / MPLS / SRv6 など複数のデータ面と組み合わせる |
| 典型利用例 | DC Overlay、マルチテナント L2/L3 延長 | EVPN over VXLAN、大規模マルチテナント、VXLAN の制御面として |

---

## 6. まとめ

一言でまとめると、

- **VXLAN** は、Overlay L2/L3 を物理ネットワーク上に作るための **データ面のカプセル化方式**
- **EVPN** は、その Overlay を制御するための **BGP ベースの制御面方式**

実務では、

> **EVPN over VXLAN**  
> = VXLAN がデータ面、EVPN が制御面

という構成が中心です。

つまり、

- **VXLAN:Overlay の“運搬手段”**
- **EVPN:Overlay の“制御と最適化”**

と捉えておくと分かりやすいです。

回答内容の評価

以下は GPT-6 Astra (高) による回答評価となる。技術的な指摘には照合先の RFC・公式資料を併記する。実機での動作確認とは別の AI による評価であり、著者が全項目を独立に検証した結果を示すものではない

確認項目 結果
日本語として読みやすいか ○:見出し・箇条書き・具体例で整理されており、流れを追いやすい。ただし、同じ結論の繰り返しが多く、やや冗長
VXLAN と EVPN の役割を区別できているか ○:EVPN-VXLAN 構成でのデータ面と制御面の役割分担を一貫して説明できている。詳細な経路種別の説明には誤りあり
指定した表形式で回答できているか ○:比較表で役割を整理できている。ただし、表の中にも誤った用語が含まれる
説明に誤りや補足が必要な箇所はあるか あり:Route Type 1・3・4 の名称と役割、VLAN タグの扱い、MAC 学習の説明などに修正が必要

特に Route Type の誤りは、ネットワークエンジニア向けの説明として見過ごせない。正しい対応は下記となる。RFC 7432 §7

Route Type 正しい名称 今回の回答の評価
1 Ethernet Auto-Discovery 「Ethernet Segment route」としており、Type 4 と混同
2 MAC/IP Advertisement MAC/IP 情報を広告するという説明は概ね正しい
3 Inclusive Multicast Ethernet Tag(IMET) 「INSET」は誤り。BUM 配送先の把握に使うという方向性は正しい
4 Ethernet Segment 「EVPN Multicast route」は誤り。同一 ES に接続する PE の発見や DF 選出に関わる

そのほか、次の点を補足する

  • VLAN タグ付きフレームに限定されない:VXLAN が運ぶのは Ethernet フレームであり、802.1Q タグが必須ではない。RFC 7348 では、別途設定がない限り、カプセル化時に内部の VLAN タグを除去する扱いとなる。RFC 7348 §6.1
  • MAC 学習がすべて BGP に置き換わるわけではない:ローカル接続先の MAC 学習と、リモートへの BGP 広告・学習を区別する必要がある。ESI も VTEP 群そのものではなく Ethernet Segment を識別する。RFC 7432 §9、同 §3
  • IP プレフィックス広告の説明には Type 5 を補足する:回答では IP prefix route に触れているが、対応する Route Type 5 の説明がない。RFC 9136 §3

今回の1回答では、Ollama + gpt-oss 20B より日本語の読みやすさと役割分担の説明は良好だった。一方で、専門用語と経路種別には明確な誤りがあり、そのまま設計・設定の根拠には使えない。モデル全体の優劣を、この1回答だけで判断するものではない

ここでは回答内容の動作確認を行う。Ollama の CLI と Splash のチャット画面では生成設定が揃っていないため、応答時間の比較は後述の共通 API 試験で行う

メモリ使用量を確認する

Ollama と同じく、アクティビティモニタの「メモリ」タブで確認する。Ollama のモデルはアンロードした状態とし、ほかのアプリの起動状況もなるべく揃える

macmini-memory-splash.png

Splash 実行時の撮影では、メモリプレッシャーは緑色の範囲に収まっていた。Mac 全体の使用済みメモリは25.50GB、スワップ使用領域は576KBだった

Ollama の撮影時と比べて使用済みメモリは約1.14GB多い。ただし、どちらも macOS やほかのアプリを含む Mac 全体の値であり、モデル単体の使用量の差ではない。この画像だけでは起動前からの増加量や、実行中のピークまでは分からない

API の動作確認

別のターミナルで作業ディレクトリに移動し、モデル一覧を確認する

cd ~/macmini-localllm/verification
curl --fail-with-body http://127.0.0.1:8000/v1/models

Ollama と同じ質問・出力上限を使う。今回は待ち時間を重視する設定として、Qwen 側は思考を無効にする

cat > splash-request.json <<'EOF'
{
  "model": "unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS",
  "messages": [
    {
      "role": "user",
      "content": "日本語で回答してください。Kubernetes の Pod と Deployment の違いを3点、合計300字以内で説明してください。"
    }
  ],
  "reasoning_effort": "none",
  "temperature": 0.7,
  "max_tokens": 2048,
  "stream": false
}
EOF

curl --fail-with-body http://127.0.0.1:8000/v1/chat/completions \
  -H 'Content-Type: application/json' \
  --data-binary @splash-request.json \
  -o splash-response.json \
  -w 'HTTP=%{http_code} total=%{time_total}s\n'

cat splash-response.json

choices[0].message.content と finish_reason を確認する。reasoning_effort: none の扱いは Splash の思考設定 を参照

/v1/models で対象の UD-IQ4_XS、コンテキスト8192、テキスト入力のみであることを確認できた。チャット API も HTTP 200で回答が返った

Splash のローカル API 応答全文
suzuyu@macmini-m6 verification % curl --fail-with-body http://127.0.0.1:8000/v1/models 
{"object":"list","data":[{"id":"unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS","object":"model","created":0,"owned_by":"splash","max_model_len":8192,"context_length":8192,"vision":false,"input_modalities":["text"]}],"models":[{"name":"unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS","description":"Splash resident model","release_date":""}]}%                                                                                         
suzuyu@macmini-m6 verification % 
suzuyu@macmini-m6 verification % cat > splash-request.json <<'EOF'
{
  "model": "unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS",
  "messages": [
    {
      "role": "user",
      "content": "日本語で回答してください。Kubernetes の Pod と Deployment の違いを3点、合計300字以内で説明してください。"
    }
  ],
  "reasoning_effort": "none",
  "temperature": 0.7,
  "max_tokens": 2048,
  "stream": false
}
EOF
suzuyu@macmini-m6 verification % curl --fail-with-body http://127.0.0.1:8000/v1/chat/completions \
  -H 'Content-Type: application/json' \
  --data-binary @splash-request.json \
  -o splash-response.json \
  -w 'HTTP=%{http_code} total=%{time_total}s\n'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1993  100  1633  100   360    219     48  0:00:07  0:00:07 --:--:--   477
HTTP=200 total=7.440603s
suzuyu@macmini-m6 verification % cat splash-response.json


{"id":"chatcmpl-68418aee3bad03571ba183fed54e670e","object":"chat.completion","created":1792308713,"model":"unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS","choices":[{"index":0,"message":{"role":"assistant","content":"Podはコンテナの最小実行単位で、同じストレージやネットワークを共有します。一方、DeploymentはPodの複製と更新を管理する上位リソースです。\n\n主な違いは以下の3点です。\n1. **管理対象**: Podは単一の実行体、Deploymentは複数のPodを管理します。\n2. **永続性**: Podは破損時に自動再作成されますが、DeploymentはPodの存在自体を保証します。\n3. **デプロイ機能**: Deploymentはロールバックや段階的更新などのライフサイクル管理が可能です。"},"finish_reason":"stop"}],"usage":{"prompt_tokens":39,"completion_tokens":121,"total_tokens":160,"prompt_tokens_details":{"cached_tokens":0},"completion_tokens_details":{"reasoning_tokens":0}},"metrics":{"prefill":{"tokens":39},"decode":{"tokens":120},"request_latency":{"start_to_first_token_ms":313.237,"first_token_to_done_ms":7120.87,"wall_ms":7434.472,"ttft_ms":313.60199999999986,"queue_to_start_ms":0.36499999999983856,"stream_tokens_per_second":16.8518734368132},"cache":{"status":"miss","matched_tokens":0,"deduplicated_tokens":0,"lane":0},"speculation":{"drafted_tokens":399,"accepted_draft_tokens":63,"cycles":57}},"timings":{"prompt_n":39,"prompt_ms":313.237,"prompt_per_second":124.50636419069266,"predicted_n":121,"predicted_ms":7120.87,"predicted_per_second":16.8518734368132,"cache_n":0,"draft_n":399,"draft_n_accepted":63}}%                       
suzuyu@macmini-m6 verification % 
確認項目 結果
HTTP ステータス 200
応答完了までの時間 7.440603秒
終了理由 stop。出力上限による打ち切りではない
日本語・3点の指定 日本語で3点に整理して回答
合計300字以内 遵守。回答本文は Markdown 記号・空白・改行を含め254文字
思考トークン reasoning_tokens: 0
技術説明の正確さ Pod の自動再作成に関する説明に誤りあり

回答は簡潔で、今回は文字数の指定にも対応できた。一方、2点目の「Podは破損時に自動再作成されます」は、単独の Pod の説明として誤り。Pod 自体が自動再作成するのではなく、Deployment 配下では ReplicaSet などのコントローラが代わりの Pod を作成する。コンテナの再起動とも区別が必要となる。Pod のライフサイクル

「DeploymentはPodの存在自体を保証します」も強すぎる表現で、希望するレプリカ数に近づけるよう管理する、と説明する方が正確。また、Deployment は1個の Pod を管理する構成にもできるため、単一か複数かだけが違いではない。Deployment のレプリカ数

レスポンスのサーバ側計測値は ttft_ms が約314ms、生成速度は約16.85 tokens/s、キャッシュは miss だった。今回は stream: false のため、この TTFT はクライアントで回答の表示が始まるまでの時間ではない

停止

Splash を起動したターミナルで Ctrl+C を押して終了する。

2構成の比較

今回は、Mac mini の32GB環境で起動し、日本語の質問とローカル API に応答できるかを初期確認した。ここまでの実行結果をまとめる

比較条件

項目 Ollama + gpt-oss 20B Splash + Qwen3.8
実行ツール Ollama 0.40.6 Splash 1.3.2
モデル・量子化 gpt-oss 20B / MXFP4 Qwen3.8-27B / UD-IQ4_XS
コンテキスト上限 8192 8192
VXLAN / EVPN の質問 CLI で入力 チャット画面で入力
Kubernetes の質問 ローカルの /v1/chat/completions 同左
API の生成設定 temperature: 0.7、max_tokens: 2048、stream: false 同左
API の思考設定 low none

2つのモデルは切り替えて実行し、API には同じ質問を1リクエストずつ送った。モデル・量子化・思考設定が異なるため、構成全体の使用感を確認するものとして扱う

初期確認の結果

確認項目 Ollama + gpt-oss 20B Splash + Qwen3.8
起動・日本語での応答 確認できた 確認できた
VXLAN / EVPN の回答 表形式には対応。役割の説明に混乱があり、技術的な誤りもあった 役割分担の説明は一貫していて読みやすいが、Route Type 1・3・4 に誤りがあった
Kubernetes の回答 日本語で3点に整理。再起動・スケーリングの説明に補足が必要 日本語で簡潔に3点に整理。Pod の自動再作成について誤りがあった
Kubernetes の300字以内の指定 未達:455文字 遵守:254文字
ローカル API HTTP 200、finish_reason: stop HTTP 200、finish_reason: stop
API 応答完了時間(各1回) 約7.68秒 約7.44秒
メモリプレッシャー(撮影時点) 緑色 緑色
Mac 全体の使用済みメモリ(撮影時点) 24.36GB 25.50GB
スワップ使用領域(撮影時点) 0バイト 576KB

文字数は回答本文の Markdown 記号・空白・改行を含めて数えた。技術的な誤りと補足は、それぞれの動作確認の節に記載している

API 応答完了時間は curl の total で、送信から応答受信完了までの値となる。各1回の測定で、生成された回答の長さや思考設定も異なるため、約0.24秒の差から速度の優劣は判断しない。初期ロードやキャッシュの条件を揃えた性能測定は今回は行っていない

メモリはアクティビティモニタを撮影した時点の Mac 全体の値で、実行中のピークやモデル単体の使用量ではない

使ってみた所感

今回の32GB環境では、両構成ともモデルの起動、日本語での回答、ローカル API からの応答まで確認できた。撮影時点のメモリプレッシャーはどちらも緑色で、まずローカル LLM を試す構成として動かすことができた

回答の印象では、Splash + Qwen3.8 は VXLAN / EVPN の役割分担を整理できており、Kubernetes の質問でも300字以内の指定を守れた。一方で、両構成とも専門用語や動作の説明に誤りがあった。技術的な相談のたたき台には使えるが、設計や設定に採用する内容は公式資料と照合する必要がある

今回はここまでを初期確認とする。繰り返し測定による応答時間の比較、長文・同時リクエスト・長時間運用、画像入力、JSON Schema・関数呼び出しの評価は、実際の用途に合わせて別途検討する

別PC・サーバからの API 応答確認

検証構成

同じ Mac 内での比較を終えてから、LAN 内の別端末から応答確認する。ローカルツールとの連携は別途検討し、ここでは curl を使う

項目 設定
API サーバ Mac mini:192.168.129.6
接続元 ROCKY01:Linux、192.168.129.12/24(ens192)
Ollama http://192.168.129.6:11434/v1/chat/completions
Splash http://192.168.129.6:8000/v1/chat/completions

Mac mini に 192.168.129.6 が割り当てられていることを、システム設定の「ネットワーク」で確認する。IP を維持する場合は DHCP 予約またはネットワークに合った固定設定を使う

事前準備のファイアウォール設定を済ませ、PF が有効で local.llm のルールが読み込まれていることを確認する。今回の許可元は 192.168.129.0/24。アプリケーションファイアウォールでも、実際に起動するサーバの受信接続を許可する。宛先 IP の指定だけでは接続元の制限にはならない

以下のクライアント用コマンドは macOS / Linux のシェルを想定する。Windows では WSL などの bash 環境で実行する。前節の ollama-request.json と splash-request.json を接続元にも作成またはコピーし、そのディレクトリで実行する

Ollama の待ち受け先を変更する(Mac mini 側)

Splash が起動中なら Ctrl+C で終了する。Ollama はサービス設定のコピーに LAN IP を指定して起動する。これらの操作は、サービスを登録した同じユーザーで行う。待ち受け先の設定

通常のサービスが起動している状態で設定をコピーし、コピー側に OLLAMA_HOST を追加する。Homebrew が設定した Flash Attention などの値も引き継ぐ

今回の環境では、brew services start ollama の起動ログに表示されたラベルは sh.brew.ollama だった。コピー元のファイル名は sh.brew.ollama.plist とする。Homebrew の版や既存の登録状態で名前が異なる場合は、brew services list の File 列を確認する

brew services list
brew services info ollama
ls -l "$HOME/Library/LaunchAgents/sh.brew.ollama.plist"

サービスが未登録・停止中の場合は、先の起動確認の手順で通常のサービスを起動する。コピー元の存在を確認してから下記へ進む

mkdir -p "$HOME/.config/ollama"
cp "$HOME/Library/LaunchAgents/sh.brew.ollama.plist" "$HOME/.config/ollama/ollama-lan.plist"
plutil -replace EnvironmentVariables.OLLAMA_HOST -string "192.168.129.6:11434" "$HOME/.config/ollama/ollama-lan.plist"
plutil -lint "$HOME/.config/ollama/ollama-lan.plist"

構文確認が OK になったら、通常のサービスを停止し、LAN 用の設定で起動する

brew services stop ollama
brew services run ollama --file="$HOME/.config/ollama/ollama-lan.plist"
brew services list

run はログイン時の自動起動を登録せずにサービスを起動する。今回は再起動後に PF を再適用する手順のため、LAN 待ち受けを自動起動させない。再起動後に LAN 試験を再開する場合も、先に PF の有効化とルールを確認する。services run と --file

Mac mini 側で待ち受け先を確認する

lsof -nP -iTCP:11434 -sTCP:LISTEN

待ち受けが 192.168.129.6:11434 となったことを確認する。この設定中は 127.0.0.1:11434 では接続できない。Mac 内の CLI でも接続先を明示する

OLLAMA_HOST=192.168.129.6:11434 ollama ps

実機では plist の構文確認が OK となり、brew services run で起動できた。lsof でも 192.168.129.6:11434 (LISTEN) を確認した

この時点の ollama ps はヘッダーのみだった。サーバ再起動後でモデルがロードされていない状態のため、そのまま次の API 応答確認へ進む。ここまでで確認できたのは Mac mini 側の待ち受けで、別端末からの接続は次の手順で確認する

この直接接続には認証がない。接続できる端末は推論以外の管理 API も呼び出せるため、192.168.129.0/24 内の端末を信頼できる検証環境で行う。認証を付けて常用する構成は別途検討する

Ollama の LAN 待ち受け設定ログ
suzuyu@macmini-m6 verification % brew services start ollama

==> Successfully started `ollama` (label: sh.brew.ollama)
suzuyu@macmini-m6 verification % ls -l "$HOME/Library/LaunchAgents/homebrew.mxcl.ollama.plist"
ls: /Users/suzuyu/Library/LaunchAgents/homebrew.mxcl.ollama.plist: No such file or directory
suzuyu@macmini-m6 verification % 
suzuyu@macmini-m6 verification % ls -l "$HOME/Library/LaunchAgents/sh.brew.ollama.plist"
-rw-r--r--  1 suzuyu  staff  1014 10月 18 17:33 /Users/suzuyu/Library/LaunchAgents/sh.brew.ollama.plist
suzuyu@macmini-m6 verification % 
suzuyu@macmini-m6 verification % 
suzuyu@macmini-m6 verification % 
suzuyu@macmini-m6 verification % 
suzuyu@macmini-m6 verification % brew services list

Name   Status  User   File
ollama started suzuyu ~/Library/LaunchAgents/sh.brew.ollama.plist
suzuyu@macmini-m6 verification % brew services info ollama

ollama (sh.brew.ollama)
Running: ✔
Loaded: ✔
Schedulable: ✘
User: suzuyu
PID: 8699
suzuyu@macmini-m6 verification % ls -l "$HOME/Library/LaunchAgents/sh.brew.ollama.plist"

-rw-r--r--  1 suzuyu  staff  1014 10月 18 17:33 /Users/suzuyu/Library/LaunchAgents/sh.brew.ollama.plist
suzuyu@macmini-m6 verification % mkdir -p "$HOME/.config/ollama"

suzuyu@macmini-m6 verification % cp "$HOME/Library/LaunchAgents/sh.brew.ollama.plist" "$HOME/.config/ollama/ollama-lan.plist"

suzuyu@macmini-m6 verification % plutil -replace EnvironmentVariables.OLLAMA_HOST -string "192.168.129.6:11434" "$HOME/.config/ollama/ollama-lan.plist"

suzuyu@macmini-m6 verification % plutil -lint "$HOME/.config/ollama/ollama-lan.plist"

/Users/suzuyu/.config/ollama/ollama-lan.plist: OK
suzuyu@macmini-m6 verification % 
suzuyu@macmini-m6 verification % brew services stop ollama
Stopping `ollama`... (might take a while)
==> Successfully stopped `ollama` (label: sh.brew.ollama)
suzuyu@macmini-m6 verification % brew services run ollama --file="$HOME/.config/ollama/ollama-lan.plist"

==> Successfully ran `ollama` (label: sh.brew.ollama)
suzuyu@macmini-m6 verification % brew services list

Name   Status  User File
ollama started      /opt/homebrew/opt/ollama/sh.brew.ollama.plist
suzuyu@macmini-m6 verification % lsof -nP -iTCP:11434 -sTCP:LISTEN

COMMAND  PID   USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
ollama  8998 suzuyu    3u  IPv4 0xc1fee6e3c24e661c      0t0  TCP 192.168.129.6:11434 (LISTEN)
suzuyu@macmini-m6 verification % OLLAMA_HOST=192.168.129.6:11434 ollama ps

NAME    ID    SIZE    PROCESSOR    CONTEXT    RUNNER    UNTIL 
suzuyu@macmini-m6 verification % 

Ollama を呼び出す(別PC・サーバ側)

まずモデル一覧を取得する

curl --fail-with-body --connect-timeout 5 \
  http://192.168.129.6:11434/v1/models

ROCKY01(192.168.129.12)から、gpt-oss-20b-8k:latest と gpt-oss:20b の一覧を取得できた

続いて、接続元の PC・サーバで、同一 Mac 内で使ったものと同じリクエストファイルを作成する。以下のファイル作成と curl は同じディレクトリで実行する

cat > ollama-request.json <<'EOF'
{
  "model": "gpt-oss-20b-8k",
  "messages": [
    {
      "role": "user",
      "content": "日本語で回答してください。Kubernetes の Pod と Deployment の違いを3点、合計300字以内で説明してください。"
    }
  ],
  "reasoning_effort": "low",
  "temperature": 0.7,
  "max_tokens": 2048,
  "stream": false
}
EOF

作成したファイルを使ってリクエストを送る

curl --fail-with-body --connect-timeout 5 \
  http://192.168.129.6:11434/v1/chat/completions \
  -H 'Content-Type: application/json' \
  --data-binary @ollama-request.json \
  -o ollama-lan-response.json \
  -w 'HTTP=%{http_code} total=%{time_total}s\n'

cat ollama-lan-response.json

HTTP 200と choices[0].message.content の回答を確認する。接続先は Mac mini の IP であり、接続元端末の localhost ではない

ROCKY01 から HTTP 200で日本語の回答を取得でき、finish_reason は stop だった

応答完了時間は12.547997秒だった。直前の ollama ps はモデル未ロードだったため、モデルのロード時間を含む可能性がある。ローカル試験の約7.68秒との差を、そのまま LAN の通信遅延とはみなさない。ここでは別端末から API で回答を取得できたことを確認結果とする

Ollama の LAN API 応答全文
ROCKY01 /home/suzuyu% ip addr show                               
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0c:29:d0:ec:55 brd ff:ff:ff:ff:ff:ff
    altname enp11s0
    inet 192.168.129.12/24 brd 192.168.129.255 scope global dynamic noprefixroute ens192
       valid_lft 84213sec preferred_lft 84213sec
    inet6 fe80::20c:29ff:fed0:ec55/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
ROCKY01 /home/suzuyu% curl --fail-with-body --connect-timeout 5 \
  http://192.168.129.6:11434/v1/models            
{"object":"list","data":[{"id":"gpt-oss-20b-8k:latest","object":"model","created":1792302933,"owned_by":"library"},{"id":"gpt-oss:20b","object":"model","created":1792302782,"owned_by":"library"}]}
ROCKY01 /home/suzuyu% cat > ollama-request.json <<'EOF'
{
  "model": "gpt-oss-20b-8k",
  "messages": [
    {
      "role": "user",
      "content": "日本語で回答してください。Kubernetes の Pod と Deployment の違いを3点、合計300字以内で説明してください。"
    }
  ],
  "reasoning_effort": "low",
  "temperature": 0.7,
  "max_tokens": 2048,
  "stream": false
}
EOF
ROCKY01 /home/suzuyu% curl --fail-with-body --connect-timeout 5 \
  http://192.168.129.6:11434/v1/chat/completions \
  -H 'Content-Type: application/json' \
  --data-binary @ollama-request.json \
  -o ollama-lan-response.json \
  -w 'HTTP=%{http_code} total=%{time_total}s\n'

cat ollama-lan-response.json
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1690  100  1351  100   339    107     27  0:00:12  0:00:12 --:--:--   381
HTTP=200 total=12.547997s
{"id":"chatcmpl-247","object":"chat.completion","created":1792313251,"model":"gpt-oss-20b-8k","system_fingerprint":"fp_ollama","choices":[{"index":0,"message":{"role":"assistant","content":"**1. 管理単位の違い**  \n- **Pod** は単一または複数コンテナをまとめた実行単位で、直接スケジューラに配置される。  \n- **Deployment** はPodのレプリカ数や更新戦略を管理するコントローラで、宣言的に希望状態を維持する。  \n\n**2. スケーリングとローリングアップデート**  \n- Pod は手動で生成・削除し、更新は新しいPodを作って古いPodを削除する形。  \n- Deployment は `kubectl scale` や `spec.strategy` で自動的にレプリカ数を調整し、ローリングアップデートを安全に実施できる。  \n\n**3. 冗長性と耐障害性**  \n- Pod だけだと障害時に単一障害点が残る。  \n- Deployment は複数のPodをクラスタに分散し、Podが失敗しても自動的に新しいPodを作成して可用性を保つ。","reasoning":"Need 3 points, 300 chars max, Japanese. 300 characters inclusive? So 300 characters. Provide concise. Let's craft ~280 chars."},"finish_reason":"stop"}],"usage":{"prompt_tokens":95,"prompt_tokens_details":{"cached_tokens":0},"completion_tokens":287,"total_tokens":382}}
ROCKY01 /home/suzuyu% 

Ollama をローカル待ち受けに戻す(Mac mini 側)

Splash の試験に移る前に、モデルをアンロードする

OLLAMA_HOST=192.168.129.6:11434 ollama stop gpt-oss-20b-8k

LAN 用のサービスを停止し、Homebrew 標準の設定で起動し直す。--file を付けず、ローカル待ち受けとログイン時の自動起動に戻す

brew services stop ollama
brew services start ollama

起動を待って、Mac mini 側で確認する

lsof -nP -iTCP:11434 -sTCP:LISTEN
ollama ps

待ち受けが 127.0.0.1:11434 に戻り、モデルがアンロードされていることを確認する

実機でも 127.0.0.1:11434 (LISTEN) への復帰を確認できた。ollama ps はヘッダーのみで、モデルがロードされていない状態となった。この状態で Splash の LAN 試験へ進む

Ollama のローカル待ち受け復帰ログ
suzuyu@macmini-m6 verification % OLLAMA_HOST=192.168.129.6:11434 ollama stop gpt-oss-20b-8k

suzuyu@macmini-m6 verification % brew services stop ollama

Stopping `ollama`... (might take a while)
==> Successfully stopped `ollama` (label: sh.brew.ollama)
suzuyu@macmini-m6 verification % brew services start ollama

==> Successfully started `ollama` (label: sh.brew.ollama)
suzuyu@macmini-m6 verification % lsof -nP -iTCP:11434 -sTCP:LISTEN

COMMAND  PID   USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
ollama  9250 suzuyu    3u  IPv4 0xcfa05c3eb2dfd786      0t0  TCP 127.0.0.1:11434 (LISTEN)
suzuyu@macmini-m6 verification % ollama ps

NAME    ID    SIZE    PROCESSOR    CONTEXT    RUNNER    UNTIL 
suzuyu@macmini-m6 verification % 

Splash を認証付きで起動する(Mac mini 側)

API キーをファイルに保存し、起動時に読み込む。初回のみ、32バイトの乱数を16進数64文字で生成する。-out などのオプションは最後のバイト数 32 より前に指定する。openssl rand

mkdir -p "$HOME/.config/splash"
(
  umask 077
  openssl rand -hex -out "$HOME/.config/splash/SPLASH_API_KEY" 32
)

新規ファイルは所有者のみ読み書きできる権限で作成される。再起動時は生成し直さず、保存済みのキーを読み込む。キーの生成コマンドを再実行するとファイルの値が変わるため、接続元にも同じキーを反映する

キーの読み込みに失敗した場合や内容が空の場合は、サーバを起動せずに終了する。下記は全体をまとめて実行する。丸括弧内のサブシェルで処理するため、エラー時も操作中のターミナルは終了しない。キー自体は画面に表示せず、記事・実行ログ・Git に含めない

(
  if ! SPLASH_API_KEY="$(cat "$HOME/.config/splash/SPLASH_API_KEY")" || [ -z "$SPLASH_API_KEY" ]; then
    printf '%s\n' 'API キーを読み込めないか、内容が空です。Splash は起動しません。' >&2
    exit 1
  fi
  export SPLASH_API_KEY

  splash serve \
    --model unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS \
    --host 192.168.129.6 \
    --port 8000 \
    --max-context 8K \
    --language-only \
    --no-webui
)

Ready の表示を待つ。SPLASH_API_KEY により API 認証が有効になる。今回は API のみ試すため、チャット画面は --no-webui で無効にする。/health や /ready などはキー認証の対象外となる。Splash のネットワーク・認証設定

Splash の認証付き起動ログ(変更前の実行記録)

以下はキー読み込みの失敗判定を追加する前の実行記録。起動時は上記の手順を使う

suzuyu@macmini-m6 verification % mkdir -p "$HOME/.config/splash"
(
  umask 077
  openssl rand -hex -out "$HOME/.config/splash/SPLASH_API_KEY" 32
)
suzuyu@macmini-m6 verification % export SPLASH_API_KEY="$(cat "$HOME/.config/splash/SPLASH_API_KEY")"
suzuyu@macmini-m6 verification % splash serve \
  --model unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS \
  --host 192.168.129.6 \
  --port 8000 \
  --max-context 8K \
  --language-only \
  --no-webui
Splash model unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS is already installed in /Users/suzuyu/Library/Application Support/Splash/models/.selections/834e3c6d41a9ee5bbe160f89275d30a18e1cca3874fb1159b752aa620232b8e1
17:58:43 Loading · unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS
17:58:44 Chat template · patched to render later system messages in place (the original template rejects them), generation prompt 5-7 tokens
17:58:49 Weights loaded in 4.99 s.
17:58:49 Kernel policy for GPU family 11 with 12 cores.
17:58:49 Setting up the Neural Engine FFN split (splash serve --disable-ane keeps the FFN on the GPU).
17:58:50 Neural Engine FFN split at share 0.76 for chunks of 512 rows or more, 1.0% from the GPU alone on the Neural Engine's part (set up as calibrated before in 1.1 s).
17:58:50 SSD cache: up to 32.0 GiB of temporary files in /var/folders/vm/x0l5wqcx7njgdqpfvbpp20n00000gn/T/ (its automatic quota, while its volume has room beyond the 10.0 GiB it keeps free); KV pages of 1040 KiB and states of 152 MiB, a state's write staging through 152 MiB of the memory plan; --max-cache-disk 0 turns it off.
17:58:57 Ready · unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS · context 8K · language only · http://192.168.129.6:8000

Splash を呼び出す(別PC・サーバ側)

Mac 側で生成したキーファイルを SFTP/SCP などで接続元の $HOME/.config/splash/SPLASH_API_KEY にコピーし、同じキーを読み込む。接続元で別のキーを生成しない。API キーは HTTP 通信を暗号化する仕組みではないため、この例は信頼できる検証用 LAN に限定する

chmod 600 "$HOME/.config/splash/SPLASH_API_KEY"
export SPLASH_API_KEY="$(cat "$HOME/.config/splash/SPLASH_API_KEY")"

curl --fail-with-body --connect-timeout 5 \
  http://192.168.129.6:8000/v1/models \
  -H "Authorization: Bearer $SPLASH_API_KEY"

実機では、Mac mini から SFTP でキーを転送し、ROCKY01 の所定の場所へ移動して権限を600に設定した。キー付きのリクエストで対象モデルの一覧を取得できた

チャット用のリクエストファイルも、接続元の ROCKY01 で作成する。ファイル作成と curl は同じディレクトリで実行する

cat > splash-request.json <<'EOF'
{
  "model": "unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS",
  "messages": [
    {
      "role": "user",
      "content": "日本語で回答してください。Kubernetes の Pod と Deployment の違いを3点、合計300字以内で説明してください。"
    }
  ],
  "reasoning_effort": "none",
  "temperature": 0.7,
  "max_tokens": 2048,
  "stream": false
}
EOF
curl --fail-with-body --connect-timeout 5 \
  http://192.168.129.6:8000/v1/chat/completions \
  -H "Authorization: Bearer $SPLASH_API_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @splash-request.json \
  -o splash-lan-response.json \
  -w 'HTTP=%{http_code} total=%{time_total}s\n'

cat splash-lan-response.json

ROCKY01 から HTTP 200で日本語の回答を取得できた。finish_reason は stop、応答完了時間は8.937652秒だった

認証ヘッダーなしのリクエストが拒否されることも確認する

curl --connect-timeout 5 \
  http://192.168.129.6:8000/v1/models \
  -w '\nHTTP=%{http_code}\n'

認証ヘッダーを付けない /v1/models へのリクエストは HTTP 401 となり、invalid or missing API key・authentication_error が返った。キー付きの応答取得と、キーなしのアクセス拒否を確認できた

試験後は Mac 側の Splash を Ctrl+C で終了し、Mac と接続元の両方で下記を実行する

unset SPLASH_API_KEY

unset は環境変数を解除する操作で、保存したキーファイルは残る

再びローカル利用する場合は、前節の --host・--no-webui を付けない起動コマンドを使う

Splash のキー転送・LAN API 応答・認証確認ログ
suzuyu@macmini-m6 ~ % sftp 192.168.129.12
suzuyu@192.168.129.12's password: 
Connected to 192.168.129.12.
sftp> put .config/splash/SPLASH_API_KEY
Uploading .config/splash/SPLASH_API_KEY to /home/suzuyu/SPLASH_API_KEY
SPLASH_API_KEY                                                                                        100%   65    18.8KB/s   00:00    
sftp> exit
suzuyu@macmini-m6 ~ % ssh 192.168.129.12
suzuyu@192.168.129.12's password: 
Last login: Sun Oct 18 17:44:49 2026 from 192.168.129.6
ROCKY01 /home/suzuyu% mkdir -p "$HOME/.config/splash"

ROCKY01 /home/suzuyu% mv SPLASH_API_KEY .config/splash/
ROCKY01 /home/suzuyu% chmod 600 "$HOME/.config/splash/SPLASH_API_KEY"
export SPLASH_API_KEY="$(cat "$HOME/.config/splash/SPLASH_API_KEY")"
ROCKY01 /home/suzuyu% curl --fail-with-body --connect-timeout 5 \
  http://192.168.129.6:8000/v1/models \
  -H "Authorization: Bearer $SPLASH_API_KEY"
{"object":"list","data":[{"id":"unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS","object":"model","created":0,"owned_by":"splash","max_model_len":8192,"context_length":8192,"vision":false,"input_modalities":["text"]}],"models":[{"name":"unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS","description":"Splash resident model","release_date":""}]}%                                                                                         
ROCKY01 /home/suzuyu% cat > splash-request.json <<'EOF'
{
  "model": "unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS",
  "messages": [
    {
      "role": "user",
      "content": "日本語で回答してください。Kubernetes の Pod と Deployment の違いを3点、合計300字以内で説明してください。"
    }
  ],
  "reasoning_effort": "none",
  "temperature": 0.7,
  "max_tokens": 2048,
  "stream": false
}
EOF
ROCKY01 /home/suzuyu% curl --fail-with-body --connect-timeout 5 \
  http://192.168.129.6:8000/v1/chat/completions \
  -H "Authorization: Bearer $SPLASH_API_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @splash-request.json \
  -o splash-lan-response.json \
  -w 'HTTP=%{http_code} total=%{time_total}s\n'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  2144  100  1784  100   360    199     40  0:00:09  0:00:08  0:00:01   453
HTTP=200 total=8.937652s
ROCKY01 /home/suzuyu% cat splash-lan-response.json
{"id":"chatcmpl-32eb3d15eeef9d5315da25daec8f5cb0","object":"chat.completion","created":1792314287,"model":"unsloth/Qwen3.8-27B-GGUF:UD-IQ4_XS","choices":[{"index":0,"message":{"role":"assistant","content":"Podはコンテナの最小実行単位であり、単一のグループ化されたコンテナ群を指します。一方、DeploymentはPodを管理・制御する抽象化オブジェクトです。\n\n1点目は役割の違いで、Podはアプリの実体ですが、Deploymentはそれらの複製や更新を統括する親です。2点目は持続性で、Podはノード故障時に失われますが、DeploymentはReplicaSetを介してPodを自動再作成し、可用性を維持します。3点目は運用性で、Deploymentはロールバックや段階的デプロイ(ロールリングアップデート)などの高度な更新戦略をネイティブにサポートし、安定した運用を可能にします。"},"finish_reason":"stop"}],"usage":{"prompt_tokens":39,"completion_tokens":147,"total_tokens":186,"prompt_tokens_details":{"cached_tokens":0},"completion_tokens_details":{"reasoning_tokens":0}},"metrics":{"prefill":{"tokens":39},"decode":{"tokens":146},"request_latency":{"start_to_first_token_ms":311.202,"first_token_to_done_ms":8607.3,"wall_ms":8922.352,"ttft_ms":315.0520000000015,"queue_to_start_ms":3.8500000000015007,"stream_tokens_per_second":16.96234591567623},"cache":{"status":"miss","matched_tokens":0,"deduplicated_tokens":0,"lane":0},"speculation":{"drafted_tokens":483,"accepted_draft_tokens":77,"cycles":69}},"timings":{"prompt_n":39,"prompt_ms":311.202,"prompt_per_second":125.32053135905296,"predicted_n":147,"predicted_ms":8607.3,"predicted_per_second":16.96234591567623,"cache_n":0,"draft_n":483,"draft_n_accepted":77}}%                                                                 
ROCKY01 /home/suzuyu% curl --connect-timeout 5 \
  http://192.168.129.6:8000/v1/models \
  -w '\nHTTP=%{http_code}\n'
{"error":{"message":"invalid or missing API key","type":"invalid_request_error","code":"authentication_error"}}
HTTP=401
ROCKY01 /home/suzuyu% 

LAN 経由の API 動作確認結果

確認項目 Ollama Splash
192.168.129.0/24 内からモデル一覧を取得 成功:Mac Mini の外のサーバ(ROCKY01(192.168.129.12))からモデル一覧を取得 成功:同じ ROCKY01 から API キー付きでモデル一覧を取得
チャットの HTTP ステータス・回答本文 HTTP 200、日本語の回答を取得、finish_reason: stop HTTP 200、日本語の回答を取得、finish_reason: stop
認証なしのリクエスト 認証なしの仕様 /v1/models で HTTP 401、authentication_error
API 完了時間 (秒) 12.547997(ロード時間を含む可能性あり) 8.937652

範囲外からの接続拒否試験は未実施。今回は PF ルールの読み込みと、許可範囲内からの API 応答を確認した

導入後のディスク容量と差分を確認する

一通りの動作確認が終わったら、Mac mini 側で測定する。Splash は Ctrl+C で終了し、Ollama のモデルも停止してから実行する。ダウンロードしたモデルは残した状態とする

ollama stop gpt-oss-20b-8k
LC_ALL=C df -kP /System/Volumes/Data > "$HOME/local-llm-measurements/disk-after.txt"
cat "$HOME/local-llm-measurements/disk-after.txt"

awk '
  NR == FNR { if (FNR == 2) before = $4; next }
  FNR == 2 {
    after = $4
    printf "導入前の空き容量: %.2f GiB\n", before / 1048576
    printf "導入後の空き容量: %.2f GiB\n", after / 1048576
    printf "空き容量の減少分(導入前 − 導入後): %+.2f GiB\n", (before - after) / 1048576
  }
' "$HOME/local-llm-measurements/disk-before.txt" "$HOME/local-llm-measurements/disk-after.txt"

df -kP の空き容量は KiB 単位のため、GiB に換算して表示する。差分が正なら空き容量が減少、負なら増加している

項目 測定結果
導入前の空き容量 400.48GiB
導入後の空き容量(測定時点) 366.45GiB
空き容量の減少分 34.03GiB
導入後のディスク容量と差分の測定ログ
Filesystem   1024-blocks     Used Available Capacity  Mounted on
/dev/disk3s5   482746452 71014400 384255016    16%    /System/Volumes/Data

導入前の空き容量: 400.48 GiB
導入後の空き容量: 366.45 GiB
空き容量の減少分(導入前 − 導入後): +34.03 GiB

差分には Homebrew・依存関係・モデル・ダウンロードファイルに加え、測定間に発生した他の書き込みや削除、スワップ、APFS のスナップショットなどの影響も含まれる。今回の作業前後で空き容量がどれだけ変化したかの目安として扱う

Splash の実行中 SSD キャッシュは終了時に解放されるため、この差分には実行中の最大使用量は表れない。実行中の容量も確認する場合は、Splash を動かしている間に別ターミナルで df -h /System/Volumes/Data を実行し、停止後の値とは分けて記録する

利用後の停止と設定の切り戻し

停止方法をまとめている。モデルの削除と PF 設定の解除は、必要になった場合の参考手順として記載する。(未実施)

モデル・API サーバを停止する

対象 操作 停止後の状態
Ollama のモデル ollama stop gpt-oss-20b-8k モデルをメモリからアンロードする。API サーバは動き続ける
Ollama の API サーバ brew services stop ollama サーバを停止し、ログイン時の自動起動も解除する
Splash 起動したターミナルで Ctrl+C サーバを終了し、モデルのメモリを解放する

Ollama が LAN 待ち受け中の場合、モデルの停止は OLLAMA_HOST=192.168.129.6:11434 ollama stop gpt-oss-20b-8k とする。停止後も同じ Mac から利用する場合は、LAN 試験の「Ollama をローカル待ち受けに戻す」の手順を参照する

停止だけではダウンロード済みモデルは削除されない。モデル自体が不要になった場合の削除方法は下記となる。Ollama CLI Reference

Ollama のダウンロード済みモデルを削除する(必要な場合のみ)
ollama rm gpt-oss-20b-8k
ollama rm gpt-oss:20b

Ollama の API サーバが起動し、CLI から接続できる状態で実行する。再び使用する場合は、モデルのダウンロードと8K設定のモデル作成を行う

PF の接続制限を解除する(必要な場合のみ)

今回追加した接続制限が不要になった場合にだけ実行する。LAN 経由での API 利用を続ける場合は設定を維持する

設定を戻す場合は、先に LAN 向けの Ollama / Splash を停止する。/etc/pf.conf から今回追加した anchor "local.llm" と load anchor "local.llm" ... の2行だけを削除する。初回バックアップは差分確認用に残し、後から追加された他の設定を上書きしない

sudo nano /etc/pf.conf
sudo pfctl -nf /etc/pf.conf

構文確認後に再読込し、専用 anchor のルールを空にする

sudo pfctl -f /etc/pf.conf
sudo pfctl -a local.llm -F rules

最後に、事前準備の sudo pfctl -E 実行時に表示された Token : 数値 を指定し、今回の PF 有効化要求を解除する。Token は有効化要求を識別する管理用の番号で、今回のログでは 923634586225060667 だった

下記の 123456789 は自分の実行時に表示された Token に置き換える。再起動後に有効化し直した場合は、そのときの Token を使う

sudo pfctl -X 123456789

-X は指定した有効化要求だけを解除する。フィルタルールの削除は前の手順で行う。ほかのサービスによる有効化要求を残すため、PF 全体を無効化する pfctl -d は使わない

おわりに

Mac Mini M6 で実際にローカル LLM を動かして動作確認まで試せた

AI エージェントやツールからの利用などは今後試す予定

参考

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?