【Tailscale+Ollama+Codex】Mac mini上のOSS LLM「Gemma 4」をWindows 11のVS Codeから利用する

TailScale

はじめに

Mac miniにOllamaとOSS LLMを導入しても、そのMacの前に座らなければ使えないのでは利便性が低い。

そこで今回は、次の環境を構築した。

  • LLMサーバー:Mac mini M4 Pro、メモリ48GB
  • LLM実行基盤:Ollama
  • OSS LLM:Gemma 4 12B/26B/31B、Qwen 3.6 Coding 27B
  • クライアント:Windows 11
  • 開発環境:VS Code+Codex拡張
  • ネットワーク:Tailscale

最終的な構成は次のとおりである。

Windows 11
  └─ VS Code
      └─ Codex拡張
          └─ Tailscale
              └─ Mac mini
                  └─ Ollama :11434
                      └─ Gemma 4 26B

本稿では、Tailscaleを使ってWindows 11からMac miniのOllamaへ接続し、Codex拡張のカスタムモデルとしてGemma 4を登録するまでの手順をまとめる。

なお、Tailscaleの100.x.x.x形式のアドレスは、同一Tailnet内で利用する専用アドレスであり、通常のインターネットへ直接公開されるアドレスではない。


1. 今回構築した環境

Mac mini側

Mac mini M4 Pro
メモリ:48GB
Ollama:インストール済み
Tailscale:インストール済み

Ollamaに登録されているモデルは次のとおり。

gemma4:31b
gemma4:26b
qwen3.6:27b-coding-mxfp8
gemma4:12b

Windows 11側

Windows 11
Tailscale
Visual Studio Code
Codex拡張
PowerShell

本稿ではセキュリティ上、Mac miniのTailscale IPを次のように表記する。

<MAC_MINI_TAILSCALE_IP>

実際には、自分のMac miniに割り当てられたTailscale IPへ置き換える。


2. TailscaleによるMac miniへの疎通確認

最初に、Windows PowerShellからMac miniへTailscale通信ができるか確認する。

tailscale ping ohidemac-mini

今回の環境では、次のように応答した。

pong from ohidemac-mini (...) in 49ms

これで、Windows 11とMac miniが同じTailnet内で通信できていることが分かる。


3. Ollamaのポート11434を確認する

Ollama APIは通常、TCPポート11434を使用する。

Windows PowerShellで次を実行する。

Test-NetConnection <MAC_MINI_TAILSCALE_IP> -Port 11434

成功時は、次のように表示される。

RemotePort       : 11434
InterfaceAlias   : Tailscale
TcpTestSucceeded : True

今回の環境では正常に成功した。

SSHポート22は必要か

試しにSSHポートも確認したところ、次の結果になった。

Test-NetConnection <MAC_MINI_TAILSCALE_IP> -Port 22
TcpTestSucceeded : False

しかし、今回の構成では問題ない。

11434:Ollama API接続に必要
22   :SSHまたはVS Code Remote SSHに必要

Windows側のローカルファイルをVS Codeで編集し、推論だけをMac miniのOllamaへ依頼する場合、SSH接続は不要である。


4. WindowsからOllamaのモデル一覧を取得する

次に、WindowsからMac mini上のOllama APIへ直接アクセスする。

(Invoke-RestMethod `
  -Uri "http://<MAC_MINI_TAILSCALE_IP>:11434/api/tags" `
  -TimeoutSec 15).models.name

今回の結果は次のとおりだった。

gemma4:31b
gemma4:26b
qwen3.6:27b-coding-mxfp8
gemma4:12b

ここまで成功すれば、次の通信経路は完成している。

Windows 11
  → Tailscale
    → Mac mini
      → Ollama API

5. OpenAI Responses API互換エンドポイントを確認する

Codexから利用するには、単にOllamaの標準APIへ接続できるだけでなく、OpenAI Responses API互換エンドポイントを利用できる必要がある。

OllamaはOpenAI互換APIの一部を提供しており、/v1/responsesにも対応している。ストリーミング、ツール呼び出し、Reasoning Summaryなども対応機能として案内されている。

Gemma 4 26Bをテストする

PowerShellで次を実行する。

$body = @{
    model = "gemma4:26b"
    input = "Reply only: OK"
} | ConvertTo-Json

$result = Invoke-RestMethod `
    -Method Post `
    -Uri "http://<MAC_MINI_TAILSCALE_IP>:11434/v1/responses" `
    -ContentType "application/json" `
    -Body $body

返却されたJSON全体を確認する。

$result | ConvertTo-Json -Depth 20

今回の結果には、次の2要素が含まれていた。

output[0] = reasoning
output[1] = message

最終回答は次の位置に格納されていた。

$result.output[1].content[0].text

実行結果:

OK

なぜoutput[0]では取得できなかったのか

最初に次を実行すると、エラーになった。

$result.output[0].content[0].text

理由は、output[0]が回答本文ではなくreasoningだったためである。

回答本文の位置を固定値で決めず、安全に抽出するには次のようにする。

$result.output |
    Where-Object { $_.type -eq "message" } |
    ForEach-Object { $_.content } |
    Where-Object { $_.type -eq "output_text" } |
    Select-Object -ExpandProperty text

6. Qwen 3.6 Codingでも確認する

コーディング用として導入していたQwenも同様に確認した。

$body = @{
    model  = "qwen3.6:27b-coding-mxfp8"
    input  = "Reply only: OK"
    stream = $false
} | ConvertTo-Json

$result = Invoke-RestMethod `
    -Method Post `
    -Uri "http://<MAC_MINI_TAILSCALE_IP>:11434/v1/responses" `
    -ContentType "application/json" `
    -Body $body

$result | ConvertTo-Json -Depth 20

Qwenでも、最終回答として次が返った。

OK

したがって、今回確認した2モデルは、どちらもWindowsからTailscale経由でResponses APIへアクセスできた。

モデルResponses API
Gemma 4 26B成功
Qwen 3.6 Coding 27B成功

7. Codexのconfig.tomlへGemma 4を登録する

Codex拡張は、VS Codeのエディターコンテキストを利用しながら、コードの確認や編集を行える。

Windowsのユーザー共通設定は、通常次の場所にある。

C:\Users\<ユーザー名>\.codex\config.toml

今回の場合は次のファイルである。

C:\Users\ohide\.codex\config.toml

変更前にバックアップする。

Copy-Item `
  "$HOME\.codex\config.toml" `
  "$HOME\.codex\config.toml.backup"

Gemma 4向けの主要設定

既存のプラグイン設定やプロジェクト設定は残し、先頭付近に次を設定した。

# Mac mini上のGemma 4 26Bを既定モデルにする
model = "gemma4:26b"
model_provider = "tailscale_ollama"

personality = "pragmatic"

[model_providers.tailscale_ollama]

name = “Mac mini Ollama via Tailscale” base_url = “http://<MAC_MINI_TAILSCALE_IP>:11434/v1” wire_api = “responses” requires_openai_auth = false supports_websockets = false request_max_retries = 4 stream_max_retries = 5 stream_idle_timeout_ms = 300000

重要なのは次の3項目である。

model = "gemma4:26b"
model_provider = "tailscale_ollama"
base_url = "http://<MAC_MINI_TAILSCALE_IP>:11434/v1"

tailscale_ollamaは、この設定内で使用する任意のプロバイダー識別名である。


8. プロジェクト内の.codex/config.tomlに注意する

今回の開発プロジェクトには、ユーザー共通設定とは別に次のファイルが存在していた。

プロジェクト
└─ .codex
   ├─ config.toml
   └─ config.toml.back.20270723

そのファイルには次の設定が残っていた。

model = "gpt-5.6-sol"
model_reasoning_effort = "medium"
approval_policy = "on-request"
sandbox_mode = "workspace-write"

このようなプロジェクト固有設定が残っていると、ユーザー共通のGemma設定と競合し、意図しないモデルが選ばれる可能性がある。

Gemmaの接続試験中は、プロジェクト側のファイルを一時的に退避した。

Rename-Item `
  ".\.codex\config.toml" `
  "config.toml.gpt56-backup"

または、.codexフォルダ全体を一時的に変更する。

Rename-Item ".codex" ".codex_old"

ただし、Git管理されている場合、削除や名前変更は変更ファイルとして検出される。

git status

そのため、完全に削除するよりも、一時的な名前変更のほうが安全である。

なお、カスタムプロバイダー定義はユーザー側の~/.codex/config.tomlに置くのが確実である。公式リポジトリでも、プロジェクトローカル設定のmodel_providermodel_providersが無視され、ユーザー設定へ移すよう表示される事例が報告されている。


9. VS Codeを再読み込みする

config.tomlを保存した後、VS Codeを再読み込みする。

Ctrl+Shift+P

コマンドパレットで次を実行する。

Developer: Reload Window

その後、Codexで新しいチャットを作成する。

Gemmaの設定が認識されたときは、モデル欄に一時的に次のような表示が確認できた。

Custom

ここで注意したいのは、Customがモデル名ではないことである。

Custom = 標準OpenAIモデル以外のカスタム接続設定

実際のモデル名はconfig.toml内の次の行で決まる。

model = "gemma4:26b"

10. モデル選択画面の注意点

Codexのモデルメニューには、次のようなOpenAIモデルが表示された。

GPT-5.6 Sol
GPT-5.6 Terra
GPT-5.6 Luna
GPT-5.5
GPT-5.4
GPT-5.4 Mini

ここでGPT-5.6 Solを選択すると、画面上のCustom表示が消えた。

つまり、このメニューでOpenAIモデルを選択すると、そのセッションはOpenAIモデル側へ切り替わる。

Gemmaへ戻す場合は、次を行う。

  1. config.tomlがGemma設定のままか確認する
  2. Developer: Reload Windowを実行する
  3. Codexで新しいチャットを作成する
  4. プロジェクト内の.codex/config.tomlに競合するモデル指定がないか確認する

ユーザー共通設定の確認コマンドは次のとおり。

Get-Content "$HOME\.codex\config.toml" |
    Select-String '^(model|model_provider)\s*='

正常なら、次の2行が表示される。

model = "gemma4:26b"
model_provider = "tailscale_ollama"

プロジェクト側も確認する。

Test-Path ".\.codex\config.toml"

Trueの場合:

Get-Content ".\.codex\config.toml" |
    Select-String '^(model|model_provider)\s*='

11. Codexが実際にGemma 4を呼び出したか確認する

Ollamaの/api/psは、インストール済みモデルの一覧ではなく、現在メモリへロードされているモデルを返す。

確認コマンドは次のとおり。

(Invoke-RestMethod `
  -Uri "http://<MAC_MINI_TAILSCALE_IP>:11434/api/ps").models |
  Select-Object name

ただし、モデルが動いていないタイミングでは何も表示されない。

そこで、Codexへプロンプトを送信しながら、別のPowerShellで監視する。

while ($true) {
    Clear-Host

    $models = (Invoke-RestMethod `
        -Uri "http://<MAC_MINI_TAILSCALE_IP>:11434/api/ps").models

    if ($models) {
        $models |
            Select-Object name, expires_at
    }
    else {
        Write-Host "現在ロード中のモデルなし"
    }

    Start-Sleep -Seconds 2
}

この状態でCodexへ、例えば次を送信する。

Create a Python file named gemma_test.py.
The file should print "Hello from Gemma 4 via Tailscale".
Do not modify any other files.

実行中に監視画面へ次が表示されれば、Gemma 4が実際に呼び出されている。

gemma4:26b

監視を終了するには次を押す。

Ctrl+C

12. Gemma 4 26Bの生成速度を測定する

次に、Mac mini上のGemma 4が何Token/sで動作しているか測定した。

Ollama APIは、出力トークン数を表すeval_countと、生成時間を表すeval_durationなどの利用統計を返す。時間の単位はナノ秒である。

生成速度は次の式で求められる。

Token/s
= eval_count ÷ (eval_duration ÷ 1,000,000,000)

PowerShell測定スクリプト

$baseUrl = "http://<MAC_MINI_TAILSCALE_IP>:11434"
$model   = "gemma4:26b"

$prompt = @"
Create a Python function that reads a CSV file,
calculates the mean of every numeric column,
and handles missing values.
Return only the Python code.
"@

$body = @{
    model      = $model
    prompt     = $prompt
    stream     = $false
    keep_alive = "10m"

    options = @{
        temperature = 0
        num_predict = 300
    }
} | ConvertTo-Json -Depth 5

Write-Host "モデルをウォームアップしています..."

# 初回ロード時間を測定値から分離
$null = Invoke-RestMethod `
    -Method Post `
    -Uri "$baseUrl/api/generate" `
    -ContentType "application/json; charset=utf-8" `
    -Body $body

$results = 1..3 | ForEach-Object {

    $result = Invoke-RestMethod `
        -Method Post `
        -Uri "$baseUrl/api/generate" `
        -ContentType "application/json; charset=utf-8" `
        -Body $body

    $generationSeconds = $result.eval_duration / 1e9
    $promptSeconds     = $result.prompt_eval_duration / 1e9
    $totalSeconds      = $result.total_duration / 1e9
    $loadSeconds       = $result.load_duration / 1e9

    [PSCustomObject]@{
        Run                = $_
        Model              = $result.model
        InputTokens        = $result.prompt_eval_count
        OutputTokens       = $result.eval_count

        PromptTokensPerSec = if ($promptSeconds -gt 0) {
            [math]::Round(
                $result.prompt_eval_count / $promptSeconds,
                2
            )
        }
        else {
            0
        }

        OutputTokensPerSec = if ($generationSeconds -gt 0) {
            [math]::Round(
                $result.eval_count / $generationSeconds,
                2
            )
        }
        else {
            0
        }

        GenerationSeconds  = [math]::Round(
            $generationSeconds,
            2
        )

        TotalSeconds       = [math]::Round(
            $totalSeconds,
            2
        )

        ModelLoadSeconds   = [math]::Round(
            $loadSeconds,
            2
        )
    }
}

$results | Format-Table -AutoSize

$average = (
    $results |
    Measure-Object `
        -Property OutputTokensPerSec `
        -Average
).Average

Write-Host ""
Write-Host `
    "平均生成速度: $([math]::Round($average, 2)) Token/s"

13. 実測結果

Mac mini M4 Pro・メモリ48GBでGemma 4 26Bを3回測定した結果は次のとおりだった。

Run入力Token出力Token出力Token/s生成時間全体時間
14930057.815.19秒5.62秒
24930057.745.20秒5.61秒
34930057.845.19秒5.62秒

平均値は次のとおり。

平均生成速度:57.80 Token/s

3回の測定差は約0.1 Token/sしかなく、非常に安定した結果だった。

入力プロンプトの処理速度は次の範囲だった。

約580~636 Token/s

300トークンのコード生成に必要な時間は、およそ5.2秒だった。


14. Token/sとAIコーディング速度は同じではない

今回測定した約57.8 Token/sは、純粋なモデル生成速度である。

Codex拡張を使った実際のAIコーディングでは、さらに次の処理が加わる。

・プロジェクト内のファイル探索
・既存コードの読み取り
・変更箇所の判断
・ツール呼び出し
・ファイルの書き込み
・Git差分の確認
・テストコマンドの実行
・エラー発生時の再試行

そのため、Token/sが高くても、エージェントとしての開発作業全体が必ず速いとは限らない。

AIコーディング用途では、次の項目を総合的に評価する必要がある。

評価項目内容
生成速度Token/s
コード品質正しいコードを生成できるか
ツール呼び出しファイル操作やコマンド実行が安定するか
コンテキスト理解複数ファイルを正しく把握できるか
修正能力テスト失敗後に修正できるか
指示追従性指定外ファイルを変更しないか

今後はGemma 4 26BとQwen 3.6 Coding 27Bで、同じコーディング課題を実行して比較する予定である。


15. トラブルシューティング

① /api/tagsは表示されるが/api/psが空

異常ではない。

/api/tags:インストール済みモデル
/api/ps  :現在ロードされているモデル

モデルを使用していないときは、/api/psが空になる。


② Port 22がFalseになる

Ollama APIだけを使う場合は問題ない。

Port 11434:必要
Port 22   :Remote SSHを使う場合だけ必要

③ Responses APIの回答が取得できない

次ではなく、

$result.output[0].content[0].text

type = "message"を探して取得する。

$result.output |
    Where-Object { $_.type -eq "message" } |
    ForEach-Object { $_.content } |
    Where-Object { $_.type -eq "output_text" } |
    Select-Object -ExpandProperty text

④ CodexでCustomが表示されない

次を確認する。

Get-Content "$HOME\.codex\config.toml" |
    Select-String '^(model|model_provider)\s*='

期待値:

model = "gemma4:26b"
model_provider = "tailscale_ollama"

続いて、プロジェクト側を確認する。

Test-Path ".\.codex\config.toml"

プロジェクト側にmodel = "gpt-5.6-sol"などが残っている場合は、一時退避する。

最後にVS Codeで次を実行する。

Developer: Reload Window

そして新規チャットを開始する。


⑤ OpenAIモデルを選んだらCustomが消えた

モデルメニューからGPT-5.6 Solなどを選ぶと、現在のチャットがOpenAIモデルへ切り替わる。

Gemmaへ戻すときは、Gemmaのconfig.tomlを確認後、VS Codeを再読み込みし、新しいチャットを開始する。


16. 今回確認できたこと

今回のPoCでは、以下を確認できた。

○ Windows 11からMac miniへTailscale接続
○ Ollamaのポート11434への接続
○ インストール済みモデル一覧の取得
○ Gemma 4 26BのResponses API呼び出し
○ Qwen 3.6 Coding 27BのResponses API呼び出し
○ Codexへのカスタムプロバイダー設定
○ Codex画面でCustomモデルの認識
○ Gemma 4 26BのToken/s測定

実測値は次のとおり。

Gemma 4 26B:約57.8 Token/s

一方で、Codex拡張からGemmaを使って、複数ファイル編集、コマンド実行、テスト修正まで安定して完了できるかは、今後さらに確認する必要がある。


17. この構成のメリットとデメリット

項目内容
メリット外出先や別PCからMac miniのLLMを利用できる
メリットAPIを一般公開せずTailnet内で利用できる
メリットOpenAI APIの使用量を消費せずに推論できる
メリット複数のWindows PCから同じLLMを利用できる
メリットモデルや量子化方式を自由に変更できる
デメリットCodexとのツール呼び出し互換性はモデル依存
デメリットMac miniが停止・スリープすると利用できない
デメリットGPT系モデルよりコード品質が下がる場合がある
デメリットconfig.tomlとプロジェクト設定が競合しやすい
デメリットモデル選択画面だけでは実モデルを判別しにくい

まとめ

今回、Tailscaleを利用して、Mac mini上のOllamaとWindows 11のVS Codeを接続した。

Windows 11+VS Code+Codex
        ↓ Tailscale
Mac mini+Ollama+Gemma 4

Gemma 4 26BとQwen 3.6 Coding 27Bについて、WindowsからOpenAI Responses API互換エンドポイントへの接続に成功した。

さらに、Gemma 4 26Bの生成速度を測定した結果、Mac mini M4 Pro・メモリ48GB環境で約57.8 Token/sという安定した性能を確認できた。

この構成により、Mac miniを自宅のOSS LLMサーバーとして動作させ、Windows側では普段使っているVS CodeからAIコーディングを行う基盤が整った。

次の検証項目は、Gemma 4 26BとQwen 3.6 Coding 27Bへ同一の開発課題を与え、以下を比較することである。

・コード生成品質
・複数ファイルの理解能力
・ツール呼び出しの安定性
・テスト失敗後の自己修正能力
・処理時間
・メモリ使用量

現時点では、Gemma 4 26Bは約57.8 Token/sと高速であり、ローカルAIコーディング環境の有力候補と評価できる。

コメント

タイトルとURLをコピーしました