- はじめに
- 1. 今回構築した環境
- 2. TailscaleによるMac miniへの疎通確認
- 3. Ollamaのポート11434を確認する
- 4. WindowsからOllamaのモデル一覧を取得する
- 5. OpenAI Responses API互換エンドポイントを確認する
- 6. Qwen 3.6 Codingでも確認する
- 7. Codexのconfig.tomlへGemma 4を登録する
- 8. プロジェクト内の.codex/config.tomlに注意する
- 9. VS Codeを再読み込みする
- 10. モデル選択画面の注意点
- 11. Codexが実際にGemma 4を呼び出したか確認する
- 12. Gemma 4 26Bの生成速度を測定する
- 13. 実測結果
- 14. Token/sとAIコーディング速度は同じではない
- 15. トラブルシューティング
- 16. 今回確認できたこと
- 17. この構成のメリットとデメリット
- まとめ
はじめに
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_providerやmodel_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へ戻す場合は、次を行う。
config.tomlがGemma設定のままか確認するDeveloper: Reload Windowを実行する- Codexで新しいチャットを作成する
- プロジェクト内の
.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 | 生成時間 | 全体時間 |
|---|---|---|---|---|---|
| 1 | 49 | 300 | 57.81 | 5.19秒 | 5.62秒 |
| 2 | 49 | 300 | 57.74 | 5.20秒 | 5.61秒 |
| 3 | 49 | 300 | 57.84 | 5.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コーディング環境の有力候補と評価できる。


コメント