この記事について

スポンサーリンク

Ollama(オラマ)は、自分のパソコンでAI(大規模言語モデル)を動かすための無料のソフトです。モデルのダウンロードから会話、ほかのアプリからの呼び出しまでを1つで引き受けてくれるので、ローカルLLMの入口として定番になっています。このブログでも ローカルAIエージェントの作り方2026 や 推論エンジン対決 の裏で何度も使ってきました。

ところが、久しぶりに最新版(0.34.4)へ上げてみると、春に使っていた0.24とは別物になっていました。アプリの画面は作り直され、ollama と打つとチャットではなく別のツールの起動メニューが出て、チャット画面の既定のモデルはクラウドのモデルになっています。ネットで見つかる「Ollamaの使い方」は、ほとんどが古い版の手順です。

そこでこの記事では、入れるところから、モデルを選んで、使いこなすまでを、2026年9月時点の最新版で Mac と Windows の両方で通し直しました。手順だけでなく、「そのまま使うと何が起きるか」を実験で確かめています。

先に結論(はじめに知っておきたい5つ)
① 「ローカルで使う」を選んでも、チャットの既定はクラウドのモデル。手元で動かすなら、モデルを選び直して、設定の「Cloud」を切る
② 考える機能(thinking)が最初からオン。「1文で答えて」に71.6秒かかり、切ると1.24秒。ただし切ると、Macでは計算問題を全モデルが間違えた
③ 読める長さ(文脈)の上限を1トークンでも超えると、半分まで黙って切り詰める。先頭に書いた内容が消えても、エラーは出ない
④ Homebrew版と公式アプリが両方入っていると、同じ窓口を取り合って、アプリが1秒おきに起動に失敗し続ける
⑤ 読める長さの既定はGPUのメモリの量で決まり、RTX 3060(12GB)では4,096トークン=Mac(32GB)の8分の1。同じ文章でも、Windowsは200行で先頭を忘れた

この記事には、下調べが1つと、実験が4つあります。使ったモデルは Qwen 3.5 の 4B・9B・27B(大きさ違いの同じ家族)で、Mac は M4(メモリ32GB)、Windows は RTX 3060(GPUのメモリ12GB)です。

用語集

Ollamaの言葉、モデルの言葉、動かし方の言葉、の順に並べています。

言葉何を指すのか
LLM(大規模言語モデル)文章を読んで、続きの文章を作るAIの本体。ChatGPT の中身もこれ。この記事では「モデル」とも呼ぶ
ローカル/クラウドローカルは自分のパソコンの中で計算すること(入力した文章が外に出ない)。クラウドは運営会社のサーバーで計算すること(文章がネット越しに送られる)
サーバー(ollama serve)Ollamaの本体。裏で動き続けて、モデルを読み込み、質問を受け付ける係。アプリもコマンドも、これに話しかけている
ポート 11434サーバーの「窓口の番号」。同じパソコンの中で、ほかのプログラムが http://localhost:11434 に話しかけると Ollama につながる。1つの番号は1つのプログラムしか使えない
APIプログラムから機能を呼び出すための決まった入口。Ollamaは /api/chat などの入口を持っていて、Pythonや別のアプリから使える
パラメータ数(4B・9B・27B)モデルの大きさ。B は billion(10億)で、4B は約40億個の数値でできている。大きいほど賢くなりやすいが、重く・遅くなる
量子化(Q4_K_M・nvfp4)モデルの数値を少ない桁数に丸めて、ファイルとメモリを小さくする技術。Q4_K_M は1つの数値をおよそ4ビットで持つ定番の形式。nvfp4 は NVIDIA が決めた4ビットの形式。詳しくは p965
トークンモデルが文章を扱う単位。日本語ならおよそ1〜2文字で1トークン。速さは「1秒あたり何トークン作れるか(tok/s)」で表す
文脈(コンテキスト長)モデルが1回に読める長さの上限。会話の履歴も貼り付けた資料も、全部この中に収める必要がある。Ollamaでは num_ctx という名前で設定する
考える機能(thinking)答える前に、答えとは別に「考えている途中の文章」を作らせる機能。難しい問題には効くが、そのぶん時間がかかる
GPU/VRAM/統合メモリGPUは画像やAIの計算が得意な部品。VRAMはGPU専用のメモリ(RTX 3060は12GB)。Macは 統合メモリ で、CPUとGPUが同じメモリを分け合う。モデルが GPU 側に丸ごと載ると速い(p963)
llama.cpp/MLXモデルを実際に計算するエンジン。Ollamaは中にこの2つを持っていて、モデルによって使い分ける。llama.cpp は Mac・Windows どちらでも動く定番、MLX は Apple が作った Apple Silicon 向けのエンジン
OpenAI互換ChatGPT の API と同じ形の入口のこと。ChatGPT 用に作られたアプリやライブラリの接続先を差し替えるだけで、Ollamaにつながる

【下調べ】Ollamaは、この4か月でどう変わったか

手元の Mac には、2026年5月の 0.24.0 が入っていました。最新は 0.34.4(2026-09-23公開)です。その間の変更履歴(GitHubのリリースノート)を読むと、使い方に響く変更が5つありました。

版変わったこと使う側への影響
0.30(5月)llama.cpp を中に取り込み直し、Apple Silicon では MLX エンジンと併用に同じモデルに「-mlx」の付く版が増えた。どちらを選ぶか迷う
0.32(7月)ollama とだけ打つとAIエージェント(コーディングの手伝い)が起動する形に昔の記事の「ollama と打てばヘルプが出る」が通用しない
0.32〜0.33Claude Code・ChatGPT などのほかのアプリからOllamaのモデルを使う連携を次々に追加アプリの最初の画面が「連携アプリの一覧」になった
0.34.2(9月)初回に「サインインするか、ローカルで使うか」を選ぶ画面を追加ローカルだけで使うなら、ここで断る
0.34.3(9月)モデルごとの考える機能の既定値を表示するように考える機能は、モデル側で「最初からオン」になっていることが多い

もう1つ、公式の説明書(Context length のページ)に、読める長さの既定値がGPUのメモリの量で決まると書かれていました。24GB未満なら4k(4,096トークン)、24〜48GBなら32k、48GB以上なら256kです。同じ手順でも、パソコンによって「覚えていられる量」が8倍違うことになります。これは実験4で確かめます。

全体の関係を図にしておきます。アプリもコマンドも、裏で動いている1つのサーバーに話しかけているだけ、というのがOllamaの基本の形です。

Ollamaの中身(0.34.4) 話しかける側 Ollamaアプリの画面 ollama run(ターミナル) curl・Python ほかのアプリ (Claude Code・ChatGPTなど) localhost :11434 サーバー(ollama serve) モデルの出し入れ・受付・文脈の管理 llama.cpp エンジン Q4_K_M などの普通の版 MLX エンジン 「-mlx」の付く版 モデルの置き場 ~/.ollama/models 自分のGPU Mac=統合メモリ Windows=VRAM 文章は外に出ない ollama.com 名前が「:cloud」の モデルはこちらで計算 文章がネットに出る 同じ画面・同じコマンドから、ローカルにもクラウドにも行ける。行き先を決めるのは「モデル名」だけ

【準備・Mac】公式アプリを入れる

Macでの入れ方はアプリを1つ入れるだけです。要件は macOS 14 Sonoma 以降です。

  1. ollama.com/download/mac の「Download for macOS」から Ollama.dmg(約200MB)をダウンロードする
  2. 開いて、Ollama のアイコンを「アプリケーション」フォルダへドラッグする
  3. アプリケーションフォルダの Ollama を起動する
  4. 初回だけ、Macのログインパスワードを求められる。これはターミナルで ollama コマンドを使えるようにするため(/usr/local/bin/ollama にアプリの中のコマンドへのリンクを作る)。入力して「OK」

すると、次の歓迎の画面が出ます。「Continue」を押します。

Ollamaアプリの初回画面。Welcome to Ollama! と Continue ボタン

次はアカウントを作るかどうかです。自分のパソコンだけで使うなら、大きな「Sign up」ではなく、その下の小さな 「No thanks, I'll use Ollama locally」 を押します。アカウントが要るのは、ollama.com のクラウドのモデルを使うときと、自作のモデルを公開するときだけです。

Create an account の画面。Sign up ボタンの下に No thanks, I'll use Ollama locally のリンクがある

最後に「Run Ollama(ターミナルで ollama を実行して)」という画面が出て、初回の設定は終わりです。⚠eightの環境では、この画面から先へ進むボタンがありませんでした。アプリを一度終了して開き直すと、次のメイン画面になりました。

Ollamaアプリのメイン画面。左に Apps・Chat・Settings、右に Claude Code や ChatGPT などの連携アプリの一覧

左の3つが Ollama アプリのすべてです。Apps はほかのアプリとの連携、Chat はチャット、Settings は設定です。最初に開くのが連携アプリの一覧、というところに、いまの Ollama がどこへ向かっているかが表れています。

【つまずき・Mac】Ollamaが2つ入っていると、黙って取り合う

eightの Mac には、以前 Homebrew(Mac用のソフトを入れる道具)で入れた Ollama もありました。こちらはログインと同時に裏でサーバーが立ち上がる設定です。この状態で公式アプリを起動したところ、アプリの記録(~/.ollama/logs/app.log)に次の行が並びました。

msg="stopped external ollama process" pid=14324
msg="ollama exited" err="exit status 1"
msg="ollama exited" err="exit status 1"
msg="ollama exited" err="exit status 1"
…(1秒おきに、35秒で26回)

# サーバー側の記録(server.log)
Error: listen tcp 127.0.0.1:11434: bind: address already in use

何が起きていたかというと、次のとおりです。

  1. アプリは起動するときに、ほかの Ollama(Homebrew版)を止めに行く
  2. ところが Homebrew のサービスは「止まったら自動で起こし直す」設定なので、すぐに復活して窓口 11434 を取り直す
  3. アプリは自分のサーバーを起動しようとするが、窓口がふさがっていて失敗する。これを1秒おきに繰り返す

画面には何も出ません。しかも Homebrew版のサーバーは動いているので、コマンドを打つと普通に答えが返ってきます。「使えているのに、実は設定画面の変更が効かない側のサーバーにつながっている」状態です。

直し方は、どちらか1つにすることです。公式アプリを使うなら、Homebrew版の自動起動を止めます。

brew services stop ollama     # Homebrew版の自動起動を止める
brew uninstall ollama         # 使わないなら消してもよい

もう1つ、更新したときにも似たことが起きます。brew upgrade ollama でコマンドを新しくしても、裏で動いているサーバーは古いままです。

$ ollama -v
ollama version is 0.24.0
Warning: client version is 0.34.4

「サーバーは 0.24.0、コマンドは 0.34.4」と言っています。新しい機能が使えないときは、まず ollama -v で2つの版がそろっているかを見てください(Homebrew版なら brew services restart ollama、公式アプリならアプリを終了して開き直す)。

Homebrew版は中身も少し違う
Homebrew版(43MB)は、公式アプリ(600MB)と比べて MLXエンジンの部品が一部しか入っていません。さらに、Homebrewのサービスの設定には、公式アプリには無い OLLAMA_KV_CACHE_TYPE=q8_0(会話の記憶を圧縮する設定)が最初から書かれています。同じ Ollama 0.34.4 でも、入れ方で動きが変わり得るということです。この記事の数字は、すべて公式アプリで測っています。

【準備・Windows】インストーラで入れる

Windowsも手順は同じです。ollama.com/download/windows から OllamaSetup.exe をダウンロードして実行し、「Install」を押します。

Windows版Ollamaのインストーラ。Setup - Ollama version 0.34.4 の画面に Install と Cancel のボタン

Macと違うところが4つありました。

  • インストーラが約1.5GBある(Mac版は約200MB)。NVIDIAのGPUで計算するための部品(CUDA)が入っているため
  • 管理者の許可は求められない。自分のユーザーのフォルダ(AppData\Local\Programs\Ollama)に入る。モデルの置き場も %USERPROFILE%\.ollama\models(自分のユーザーのフォルダの中)
  • ⚠インストールの前から開いていたコマンドプロンプトでは、ollama が見つからない(「内部コマンドまたは外部コマンド…として認識されていません」)。インストーラはコマンドの場所(PATH)を登録するが、すでに開いている窓には届かない。窓を開き直すと使える
  • 設定やチャットは、タスクトレイから開く。初回の画面(Welcome → Create an account → Run Ollama)はMacと同じで、最後の「Run Ollama」で止まる。画面右下の通知領域(^ の中)の Ollama のアイコンを右クリックすると、Open Ollama/Settings/View logs/Quit Ollama が出る

初回の画面で「No thanks, I'll use Ollama locally」を選んでも、チャットの既定のモデルが glm-5.3-flash:cloud なのも Mac と同じでした。

【はじめにやること】チャットと設定の「既定」を見直す

アプリの Chat を開くと、入力欄の右下に、使うモデルの名前が出ています。eightの環境では glm-5.3-flash:cloud でした。

Ollamaアプリのチャット画面。入力欄の右下に glm-5.3-flash:cloud と表示され、上に Cloud models require an Ollama account の案内

名前の最後の 「:cloud」 は、ollama.com のサーバーで計算するモデルという印です。初回の画面で「ローカルで使う」を選んだのに、チャットの既定はクラウドのモデルになっていました。このままでは「Cloud models require an Ollama account(アカウントが要ります)」と言われるだけですが、サインインすると、入力した文章がネット越しに送られるようになります。

手元だけで使うなら、次の2つを直しておきます。

  1. Chat の右下のモデル名を押して、ダウンロード済みのモデル(名前に「:cloud」が付いていないもの)を選ぶ。モデルの入れ方は次の章
  2. Settings の「Cloud」のスイッチを切る(下の画面の一番上。最初はオン)
Ollamaの設定画面の上半分。Cloud・Show apps in menu・Auto-download updates がオン、Expose Ollama to the network がオフ、Model location の欄

設定画面の項目は次のとおりです(画面は Mac、既定値は eight の環境で見えたもの)。

項目既定何をするか
CloudオンクラウドのモデルとWeb検索を使えるようにする。手元だけで使うなら切る
Show apps in menuオンメニューバーのOllamaのメニューに、連携アプリを並べる
Auto-download updatesオン新しい版を自動でダウンロードする(1時間おきに確認)
Expose Ollama to the networkオフ同じネットワークのほかのパソコンからも使えるようにする。家の外に出ていくわけではないが、必要なとき以外はオフのまま
Model location~/.ollama/modelsモデルの置き場所。1つ数GB〜数十GBあるので、容量が足りなければ外付けのSSDなどに変える
Context length32k(Mac 32GB)/4k(Windows・GPU 12GB)1回に読める長さ。4k〜256k。実験4でこの意味を確かめる
Ollamaの設定画面の下半分。Context length のスライダーが 32k の位置、その下に ChatGPT 連携の設定

下の「ChatGPT」の欄には、ChatGPTのアプリから使うモデルが5つ最初から入っていて、5つとも「:cloud」でした。連携機能は、クラウドのモデルを売りにしていることが分かります。

【最初の応答・両OS】モデルを入れて、話しかける

ここからはターミナル(Windowsならコマンドプロンプト)を使います。まず、ollama とだけ打つとどうなるかを見ておきます。

$ ollama
Ollama 0.34.4

▸ Launch Claude Code
    Anthropic's coding tool with subagents

  Launch OpenCode (install)
    Anomaly's open-source coding agent

  Launch Hermes Agent (install)
  Launch OpenClaw (install)
  Launch ChatGPT (not installed)
  More...
(説明の行は一部省略)

チャットは始まらず、ほかのAIツールを起動するメニューが出ます。ここでEnterを押すと Claude Code のインストールや起動に進むので、何も選ばずに Ctrl+C で抜けます。モデルと話すには ollama run を使います。

ollama pull qwen3.5:4b        # モデルをダウンロードする(3.4GB)
ollama run qwen3.5:4b         # 話しかける(入っていなければ自動でダウンロード)
ollama list                   # 入っているモデルの一覧
ollama ps                     # いまメモリに載っているモデル
ollama rm qwen3.5:4b          # モデルを消す

モデルの名前は「家族名:大きさ」の形です。qwen3.5 とだけ書くと、既定の latest が選ばれます。Qwen 3.5 の latest は 9B(6.6GB)で、4Bのつもりで打つと倍の大きさが落ちてきます。使えるモデルの一覧と大きさは ollama.com/library の各モデルの「Tags」で見られます。

では、話しかけてみます。--verbose を付けると、最後に時間の内訳が出ます。

$ ollama run qwen3.5:4b "Ollamaとは何ですか?1文で答えてください。" --verbose
Thinking...
Thinking Process:
1.  **Analyze the Request:**
    *   Subject: Ollama (ollama)
    *   Question: What is it? (Ollama とはなんでしょうか?)
    *   Constraint: Answer in one sentence (1 文で答えてください).
…(英語で考え続ける)…
    Okay. Go.
...done thinking.

Ollama は、ローカル環境で大規模言語モデルを簡単に実行・管理できるオープンソースのツールです。

total duration:       1m11.580327792s
load duration:        2.305761583s
eval count:           1987 token(s)
eval rate:            28.84 tokens/s

1文の答えが出るまでに 71.6秒かかりました。答える前に、英語で1,987トークンも「考えて」いたからです。Qwen 3.5 は、ollama show qwen3.5:4b で見ると考える機能の既定が true(オン)になっています。

  Capabilities
    completion
    vision
    tools
    thinking
        levels     false, true
        default    true

考える機能を切って、同じ質問をします。

$ ollama run qwen3.5:4b "Ollamaとは何ですか?1文で答えてください。" --verbose --think=false
Ollama は、ローカル環境で軽量かつ高速に LLM(大規模言語モデル)をインストール・実行できるオープンソースツールです。

total duration:       1.243705875s
eval count:           32 token(s)
eval rate:            30.14 tokens/s

1.24秒です。1秒あたりの速さ(eval rate)は約29〜30トークンで変わっていません。遅かったのは、計算が遅いからではなく、作った文章の量が60倍だったからです。

(この2回は、別のモデルをダウンロードしながら測ったので、1秒あたりの速さは実験1の31 tok/sより少し低めに出ています。倍率の話は、作った量の違いなので変わりません。)

対話モードで話しているときは、/set nothink と打てば、その会話の間だけ考える機能が切れます。対話モードで使えるコマンドは /? で出ます。

>>> /set
Available Commands:
  /set parameter ...     Set a parameter
  /set system <string>   Set system message
  /set format json       Enable JSON mode
  /set verbose           Show LLM stats
  /set think             Enable thinking
  /set nothink           Disable thinking
  …
>>> /set nothink
Set 'nothink' mode.

アプリのチャット画面で使うときは、入力欄の右下でモデルを選べば、そのまま話しかけられます。考える機能は、ここまでの結果を見るかぎり「最初はオン」と思っておくのが安全です。ただし、切ればいいという話でもありません。それは実験2で分かります。

【実験1・両OS】モデルの大きさで、速さとメモリはどう変わるか

同じ Qwen 3.5 の 4B・9B・27B を、Mac と Windows で同じスクリプト(bench.py・全文は付録)を使って測りました。考える機能は切り、同じ2つの質問(説明文・短いコード)を3回ずつ、温まった状態で投げています。数字は Ollama 自身が返す値です。

モデル
(ファイル)
Mac(M4・統合メモリ32GB)Windows(RTX 3060・VRAM 12GB)
メモリGPU生成の速さメモリGPU生成の速さ
qwen3.5:4b(3.4GB)4.35GB100%31.0 tok/s3.13GB100%82.3 tok/s
qwen3.5:9b(6.6GB)6.67GB100%17.7 tok/s5.49GB100%53.7 tok/s
qwen3.5:27b(17GB)18.37GB100%5.9 tok/s17.14GB57%4.9 tok/s
1秒あたりに作れるトークン数(考える機能オフ・温まった状態) Mac(M4) Windows(RTX 3060) 4B 31.0 82.3 9B 17.7 53.7 27B 5.9(GPU 100%) 4.9(GPU 57%・残りはCPU) GPUに全部載るうちは RTX 3060 が2.6〜3.0倍速い。載り切らない27Bだけ逆転する

読み方は3つです。

  • GPUに全部載るうちは、RTX 3060 が Mac の2.6〜3.0倍速い。4Bの82.3 tok/sは、日本語にすると1秒に約120文字、文庫本なら約3行ぶんが出てくる速さです
  • 27B(17GB)は、Windowsの12GBに載り切らない。ollama ps の PROCESSOR 列が 43%/57% CPU/GPU になり、GPUが57%しか担当していません。残りをCPUが計算するため、4.9 tok/sまで落ちて Mac(5.9)に負けました。Macは統合メモリなので、27Bも丸ごとGPU側に載っています
  • メモリの量は、モデルのファイルより少し多い。Macのほうが1GBほど多いのは、読める長さ(文脈)の既定が Mac は32k、Windowsは4kと違うからです(実験4)

速さのほかに、最初の1文字が出るまでの時間(温まった状態)は、4B・9Bなら両OSとも0.1〜0.2秒、27Bで約0.5秒でした。一方で、モデルをメモリへ読み込む時間(冷えた状態)は、Macで4B 2.6秒・9B 4.1秒・27B 10.5秒。Windowsはいちばん最初の4Bだけ 33.0秒かかり、2回目以降は5.8秒でした(ディスクから初めて読むぶんと、セキュリティソフトの検査が乗ったとみられます)。

Ollamaは、最後に使ってから5分たつとモデルをメモリから降ろします(ollama ps の UNTIL 列が「4 minutes from now」になっているのがその残り時間)。しばらく放っておいてから話しかけると最初だけ遅いのは、この読み込みのためです。

$ ollama ps        # Windows・27B を読み込んだところ
NAME           ID              SIZE     PROCESSOR          CONTEXT    UNTIL
qwen3.5:27b    7653528ba5cb    17 GB    43%/57% CPU/GPU    4096       4 minutes from now

「GPUを使っていない?」と思ったら ollama ps
PROCESSOR 列が 100% GPU なら全部GPUで計算しています。CPU/GPU の割合が出ていたら、GPUのメモリに載り切っていません。小さいモデルに替えるか、読める長さ(num_ctx)を縮めると100%に戻せます。Windowsの27Bは、GPUのメモリ10.5GBに加えて、GPUから借りる形のメインメモリ(タスクマネージャーでいう「共有GPUメモリ」)を6.8GB使っていました。

【実験2・両OS】答えは合っているのか ── 考える機能は切っていいのか

速さだけ見て選ぶと失敗するので、答えが1つに決まる5問で正誤を見ました。temperature(ばらつきの強さ)を0にして、毎回同じ答えが出るようにしています。

問題聞いたこと正解
計算りんごが3箱(1箱12個)。7個食べて、残りを5人で同じ数ずつ分けると?5個ずつ、余り4個
形式3人の名前・年齢・都市を JSON の配列だけで出力3人ぶんが正しく読み取れること
知識日本で2番目に高い山は?北岳
論理AはBより背が高い。CはAより背が高い。いちばん低いのは?B
要約Ollamaを説明した2文を、句読点込みで30文字以内の1文に30文字以内
モデルMacWindows
考える機能オフオンオフオン
4B2/53/52/53/5(うち2問は空の答え)
4B(MLX版)2/52/5動かない(実験3)
9B2/54/53/54/5(うち1問は空の答え)
27B3/54/53/5(測っていない)

考える機能をオンにすると、どのモデルも正答が増えました。いちばん差が出たのは計算です。実際の答えを並べます(考える機能オフ)。

モデルMac の答えWindows の答え
4B36個ずつ、余り0個36個ずつ、余り4個
4B(MLX版)36個ずつ、余り0個—
9B8個ずつ、余り2個3 箱×12 個=36 個、7 個食べたので残りは 29 個。これを 5 人で分けると、1 人あたり 5 個ずつになり、余りは 4 個です。
27B10 個ずつ、余り 3 個10 個ずつ、余り 3 個

正解は「5個ずつ、余り4個」です。考える機能オフでは、Macは4モデルとも間違えました。「答えだけを書いて」と頼まれると、途中の計算を飛ばしてそれらしい数字を出してしまいます。Windowsの9Bだけは、指示を守らずに途中の式を書いたおかげで正解しています。考える機能をオンにすると、両OSのすべてのモデルが「5個ずつ、余り4個」と正解しました。

もう1つ気づいたことがあります。temperature を0にして毎回同じ答えが出るようにしていても、同じモデルなのに Mac と Windows で答えが違いました(4Bは余りが0個と4個、9Bは答え方そのもの)。計算するGPUが違うと、細かい数値の丸め方がずれて、途中から別の文章になることがあります。「同じ設定なら、どのパソコンでも同じ答え」とはかぎりません。

知識の問題(2番目に高い山)では、考える機能オフの4B・9Bは「富士山」、MLX版の4Bは「富士箱根岳」という存在しない山を答えました。北岳と答えられたのは、27B(オフ・オン)と9B(オン)です。小さいモデルは知っていることが少なく、考えても知らないことは出てきません。

考える機能の落とし穴:答えが空になる

ところが、考える機能オンの結果には「空の答え」が混ざっていました。答えの欄に何も書かれず、考える途中の文章だけで終わっています。

どこで止まったかMac(読める長さ32k・生成の上限8,192)Windows(読める長さ4k)
要約(30文字以内)4B・9B・27B・MLX版の全部が8,192トークンまで考え続けて空。27Bは22分かかった4B・9B とも約4,000トークンで空
そのほか4Bの知識、MLX版の形式・知識4Bの知識

Macの4Bが要約の問題で何を考えていたのかを保存して読むと、こうなっていました(考える途中の文章は英語です)。

*   Draft 1: Ollama は PC で大規模モデルを動かす無料ソフトで、ネットなしでも安全に使えます。
*   Let's count carefully: O l l a m a (6) は (1) P C (2) で (1) …
*   Total: … = 37 characters. Too long.
    (案を削っては数え直す、を何度も繰り返して)
… "Offline" -> オフライン。オ,フ,ラ,イ,ン?No. It's オ,フ,ラ,イ,リ,ン?Wait.
"Offline" is 6 chars: オ,フ,ラ,イ,リ,ン。But often written as オフライン (5 chars: オ,フ,ラ,イ,ン?No.
It's オ,フ,ラ,イ,ン?Wait. "Offline" -> オフライン。オ,フ,ラ,イ,ン?No. It's オ,フ,ラ,イ,リ,ン?Wait.
…(同じ数え直しが 8,192トークンまで続く)

「オフライン」が何文字かを数えようとして、「オ,フ,ラ,イ,ン?いや、オ,フ,ラ,イ,リ,ン?」を延々と繰り返していました 😳 モデルは文章を1文字ずつではなく、トークンというかたまりで見ているので、文字数を正確に数えるのが苦手です。考える機能は、苦手なことにも真面目に取り組んで、終わらなくなります。

Windowsのほうは、止まり方が違います。読める長さの既定が4,096しかないため、考えている途中で読める範囲が埋まり、約4,000トークンで打ち切られて空になりました。生成の上限(num_predict)は8,192を指定していたのに、そこまで届いていません。考える機能を使うなら、実験4の方法で読める長さを広げておく必要があります。

かかった時間も並べておきます(考える機能オン・5問の合計)。

モデルMacWindows
4B684秒160秒
9B636秒168秒
27B1,815秒(要約だけで1,360秒)—

考える機能オフなら、どれも5問で数秒〜数十秒です。考える機能は、正しさと引き換えに数十倍の時間を払うもので、しかも「数える」ような問題では答えにたどり着かないことがあります。

【実験3・Mac】「-mlx」の付く版は速いのか

Apple Silicon の Mac には、同じモデルに qwen3.5:4b-mlx のような MLXエンジン用の版があります。Ollamaのリリースノートでは「Apple Silicon で最高の性能」とうたわれているので、普通の版と比べました。前のモデルをメモリから降ろしてから、普通の版とMLX版を交互に2回ずつ測っています。

版量子化メモリ読み込み(冷えた状態)最初の文字生成の速さ
qwen3.5:4bQ4_K_M4.35GB1.57〜2.58秒0.093〜0.095秒31.2〜31.3 tok/s
qwen3.5:4b-mlxnvfp44.07GB0.52〜1.82秒0.089〜0.100秒26.4〜27.2 tok/s

MLX版のほうが生成が約13%遅い、という結果でした。2回とも同じで、たまたまではありません。読み込みと最初の1文字はMLX版が少し速く、メモリも少し小さいので、短いやり取りを何度もする使い方なら差は感じにくいはずです。

ただし、これを「MLXエンジンが遅い」とは言えません。ollama show で見ると、2つは量子化の形式が違う(Q4_K_M と nvfp4)からです。エンジンの差と形式の差が混ざっています。答えの正しさも、実験2の表のとおり、MLX版のほうが少し悪くなりました(考える機能オンで 3/5 → 2/5)。

⚠もう1つ、MLX版では Ollama が返す「入力を読むのにかかった時間」(prompt_eval_duration)がおかしな値になり、計算すると1秒に数百万トークンという、ありえない速さになりました。MLX版では、入力の読み込み速度を Ollama の数字で比べられないので注意してください。

Windowsでは、MLX版はそもそも取得できませんでした。

> ollama pull qwen3.5:4b-mlx
pulling manifest
Error: this model requires MLX support, but the MLX runtime is not available

OllamaSetup.exe で入れた Windows版には、MLXエンジンが入っていません。「-mlx」の付くモデルは、実質 Apple Silicon の Mac 専用と思っておけば間違いありません(GitHubには MLX 入りの別の配布物もありますが、今回は試していません)。

【実験4・両OS】長い文章を渡すと、先頭を忘れる

最後は、読める長さ(文脈)の実験です。長い文章の先頭に合言葉を書き、あいだに「記録1:駅前の本屋で赤い傘を見かけた人が1人いました。」のような文をたくさん並べ、最後に「最初に書いた合言葉は?」と聞きます。文を増やしながら、Ollama が実際に読んだトークン数(prompt_eval_count)と、合言葉を答えられたかを記録しました(ctx_test.py・全文は付録)。

Macの既定は、設定画面のスライダーと同じ 32,768トークンでした(ollama ps の CONTEXT の列)。

文の数渡した長さ(トークン)Ollamaが読んだ長さ合言葉答え
100行2,0572,057○みかんの木の下で待つ
800行16,74116,741○みかんの木の下で待つ
1,400行29,72829,728○みかんの木の下で待つ
1,500行31,92531,925○みかんの木の下で待つ
1,560行33,24316,386×三階で白い傘を見かけた人が7人いました。
2,400行51,70316,386×1657
4,800行104,44616,386×057

1,500行(31,925トークン)までは全部読んで正解しています。ところが、上限の32,768を超えた1,560行(33,243トークン)で、読んだ量が16,386トークンに落ちました。上限まで読んで古い側を少し捨てるのではなく、一気に半分まで切り詰めています。当然、先頭の合言葉は消えています。

渡した長さと、Ollamaが実際に読んだ長さ(Mac・既定の32k) 渡した長さ(トークン) 読んだ長さ 上限 32,768 半分 16,386 全部読む・合言葉○ 上限を超えると半分に・合言葉× 0 32k 64k 100k

いちばん困るのは、このことがどこにも表示されない点です。APIの返事にもチャット画面にもエラーは出ず、モデルは読めた範囲から何かしら答えます(上の表の「1657」「057」は、文の番号の断片です)。記録を残していたのは、サーバーの記録(Macは ~/.ollama/logs/server.log)の次の1行だけでした。

level=WARN msg="truncating input prompt" limit=16386 prompt=33243 keep=4 new=16386

「33,243トークン来たので、16,386トークンに切り詰めた。先頭の4トークンだけは残した」という意味です。

Windowsでは、190行で忘れた

同じ実験を Windows(RTX 3060・12GB)で行うと、ollama ps の CONTEXT は 4,096 でした。設定画面のスライダーも、いちばん左の4kにあります。公式の説明どおり、GPUのメモリが24GB未満なので4kです。

Windows版Ollamaの設定画面。Cloud と Auto-download updates がオン、Context length のスライダーが左端の 4k
文の数渡した長さOllamaが読んだ長さ合言葉答え
100行2,0572,057○みかんの木の下で待つ
180行3,7353,735○みかんの木の下で待つ
190行3,9453,945○みかんの木の下で待つ
200行4,1562,050×山の上の展望台で黄色い傘を見かけた人が6人いました。
800行16,7412,050×705
1,600行34,1232,050×黄色い傘

Macでは1,500行まで覚えていた合言葉を、Windowsは200行(4,156トークン)で忘れました。切り詰め方も同じで、上限の4,096を超えた瞬間に半分の2,050まで落ちています。この実験の文章で約5,900文字、A4(1ページ1,200字)なら5ページほどです。Macの32kなら約47,000文字まで読めます。Windowsで長い資料を貼って質問すると、前半を読んでいないまま答えていることになります。

一方、Windowsで num_ctx を32,768に指定すると、Macの既定とまったく同じ数字になりました(1,500行=31,925トークンまで○、1,560行で16,386に切り詰めて×)。違いを生んでいたのはOSでもGPUでもなく、既定の読める長さだけです。このときの4Bのメモリは3.1GBから4.2GBに増えましたが、12GBのGPUに十分収まっています。

読める長さを広げる

足りないときは、自分で上限を広げます。やり方は3つあります。

# 1) アプリ:Settings の Context length のスライダーを動かす

# 2) ollama run の対話モードの中で
>>> /set parameter num_ctx 65536

# 3) API:options に num_ctx を書く(ctx_test.py の --num-ctx はこれ)
curl http://localhost:11434/api/chat -d '{
  "model": "qwen3.5:4b",
  "messages": [{"role": "user", "content": "…長い文章…"}],
  "options": {"num_ctx": 65536}
}'

Macで num_ctx を65,536にすると、2,400行(51,703トークン)も3,000行(64,890トークン)も合言葉を答えられました。3,200行(69,285トークン)では、また半分の32,770に切り詰められて×です。上限の決め方が変わっても、切り詰め方は同じでした。

広げたぶんメモリは増えます。Qwen 3.5 4B で ollama ps のメモリを見ると次のとおりです。

num_ctx4,09632,76865,536131,072
メモリ(Mac・4B)3.2GB4.3GB5.9GB8.5GB

Qwen 3.5 は長い文章でもメモリが増えにくい作りのモデルですが、それでも32kから128kで倍になります。GPUのメモリが少ないパソコンでむやみに広げると、実験1の27Bのようにモデルが GPU から溢れて遅くなるので、必要な長さだけ広げるのが基本です。

【使いこなす・両OS】ほかのプログラムから呼ぶ

Ollamaのサーバーは、http://localhost:11434 で待っています。ターミナルから試すなら curl が手軽です(Windows 10以降にも curl.exe が入っています)。

curl http://localhost:11434/api/chat -d '{
  "model": "qwen3.5:4b",
  "messages": [{"role": "user", "content": "富士山の高さを一言で教えて"}],
  "think": false,
  "stream": false
}'

返ってくる JSON には、答えのほかに時間の内訳も入っています(実験1の数字は、ここから計算しています)。

{
    "model": "qwen3.5:4b",
    "message": {
        "role": "assistant",
        "content": "現在、富士山の標高は**3,776 メートル**です。…"
    },
    "done_reason": "stop",
    "total_duration": 1909563791,
    "load_duration": 2911083,
    "prompt_eval_count": 19,
    "prompt_eval_duration": 85373000,
    "eval_count": 56,
    "eval_duration": 1812365000
}

時間の単位はナノ秒(10億分の1秒)です。eval_count ÷ eval_duration で、1秒あたりの生成トークン数が出ます。

Pythonから呼ぶなら、追加のインストールなしで次の20行で動きます(Mac・Windows共通。chat_min.py として保存して python3 chat_min.py、Windowsは python chat_min.py)。

# Ollama に話しかける最小のPython(標準ライブラリだけ・Mac / Windows 共通)
import json
import urllib.request

body = {
    "model": "qwen3.5:4b",
    "messages": [{"role": "user", "content": "富士山の高さを一言で教えて"}],
    "think": False,     # 考える機能を切る(切らないと数十秒かかることがある)
    "stream": False,
}
req = urllib.request.Request(
    "http://localhost:11434/api/chat",
    data=json.dumps(body).encode("utf-8"),
    headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req) as res:
    reply = json.loads(res.read())
print(reply["message"]["content"])
$ python3 chat_min.py
現在(2024 年時点)、富士山の頂上の標高は**3,776 メートル**です。

ただし、この数値は「新富士山」の名称を受けて改称されたことで、1980 年代から約 45 年間にわたり測量が続けられており、…

標高は合っていますが、「新富士山」への改称などという話はでたらめです。4Bの小さなモデルは、聞いていないことまで付け足して、それらしく間違えます。temperature を指定していないので、実行するたびに付け足しの中身も変わりました。

ChatGPT 用に作られたプログラムからは、OpenAI互換の入口 /v1 を使います。OpenAIの公式ライブラリ(pip install openai)なら、接続先を変えるだけです。

# OpenAI の公式ライブラリから Ollama を呼ぶ(pip install openai)
from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")  # キーは何でもよい
res = client.chat.completions.create(
    model="qwen3.5:4b",
    messages=[{"role": "user", "content": "富士山の高さを一言で教えて"}],
    reasoning_effort="none",   # 考える機能を切る
)
print(res.choices[0].message.content)

ここで最後の reasoning_effort="none" を書き忘れると、考える機能が動いて74.5秒・2,315トークンかかりました。付ければ1.5秒です(openai 3.19.2 で確認)。ほかのアプリの接続先を Ollama に向けて「なぜかすごく遅い」ときは、まずこれを疑ってください。

【ここから整理】モデルの選び方と、最初に見直す6つ

モデルの選び方

いちばん大事なのはGPUに丸ごと載るかどうかです。実験1のとおり、載っていれば速く、載り切らないと一気に遅くなります。必要なメモリは、実測からおおよそ次の式で見積もれます。

必要なメモリ ≒ モデルのファイルの大きさ + 読める長さのぶん
               (Qwen 3.5 4B の実測:4k なら +0GB前後、32k で +1GB、64k で +2.5GB、128k で +5GB)

これを、GPUが使えるメモリと比べます。WindowsならVRAMの量(RTX 3060なら12GB)、Macなら統合メモリのうちGPUに回せる量です(32GBのM4で約25GB。p963で実測)。今回の結果を当てはめると次のようになります。

パソコン4B(3.4GB)9B(6.6GB)27B(17GB)
Windows・RTX 3060(12GB)○ 82.3 tok/s◎ 53.7 tok/s△ 4.9 tok/s(GPU 57%)
Mac・M4(32GB)○ 31.0 tok/s◎ 17.7 tok/s○ 5.9 tok/s(人が読む速さと同じくらい)

今回の5問では、9Bが考える機能オンで4/5と、27Bに並びました。速さも会話に使える範囲なので、12GBのGPUや32GBのMacなら 9B が扱いやすい大きさです。4Bは速いぶん、知識の問題で間違えたり、聞いていないことをでたらめに付け足したりします。

最初に見直す6つ

見るところこうなっていたらこうする
1ollama -v版が2つ出る(Warning: client version is …)サーバーを再起動する。Ollamaが2つ入っていたら1つにする
2Chat の右下のモデル名名前が「:cloud」で終わるダウンロードしたモデルを選ぶ。手元だけで使うなら Settings の Cloud を切る
3ollama show モデル名 の thinkingdefault が true普段の会話は切る(--think=false・/set nothink・API は "think": false)。計算や筋道の要る問題だけオンにする
4ollama ps の CONTEXT4096(GPUのメモリ24GB未満)長い資料を渡すなら num_ctx かスライダーで広げる。考える機能を使うときも広げる
5ollama ps の PROCESSORCPU/GPU の割合が出ている小さいモデルにするか、読める長さを縮める
6ほかのアプリから使うときなぜかとても遅い考える機能が動いていないか確かめる(OpenAI互換なら reasoning_effort="none")

まとめ

  • Ollamaは 0.34.4(2026年9月)で、アプリの画面も ollama コマンドの振る舞いも、春の版から大きく変わっていた。昔の手順どおりにはいかない
  • 「ローカルで使う」を選んでも、チャットの既定はクラウドのモデル(両OS)。モデルを選び直し、Cloud を切っておく
  • Qwen 3.5 は考える機能が最初からオン。1文の答えに71.6秒かかり、切ると1.24秒。ただ、切るとMacでは計算問題が全滅し(Windowsは9Bだけ途中の式を書いて正解)、オンにすると両OSの全モデルが正解した。代わりに「30文字以内」のような数える指示では、考え続けて答えが空になる
  • 読める長さの既定は GPU のメモリで決まる。Mac(32GB)は32k、Windows(12GB)は4k。上限を超えると半分まで黙って切り詰めるので、Windowsは200行で先頭を忘れた。num_ctx を指定すれば両OSで同じ結果になる
  • 速さは、GPUに載るうちは RTX 3060 が Mac の2.6〜3.0倍。載り切らない27Bだけ Mac が勝った。「-mlx」版は Mac でも普通の版より13%遅く、Windowsでは動かない
  • Homebrew版と公式アプリの二重インストールは、黙って取り合う。ollama -v で版が1つにそろっているかを最初に見る

どれも、エラーは1つも出ませんでした。手元で動くAIは、黙って別のことをしていることが多いので、ollama ps と ollama -v の2つだけは、ときどき打って確かめるのがおすすめです。

実測した環境
Mac:Apple M4 / メモリ32GB / macOS 26.6.2 / Ollama 0.34.4(公式アプリ)/ Python 3.12.5
Windows:Ryzen 7 7840HS / メモリ32GB / Windows 11 Pro 25H2 / GeForce RTX 3060 12GB(OCuLink接続の外付けGPU・ドライバ 616.92)/ Ollama 0.34.4 / Python 3.12.10
モデルは両OSとも Ollama のライブラリから取得した qwen3.5:4b・9b・27b(Q4_K_M)と qwen3.5:4b-mlx(nvfp4)です。両OSとも執筆時点の最新版 Ollama 0.34.4 にそろえ、2026-09-26 に測りました。Macは Homebrew版から公式アプリへ入れ替えたうえで測っています。

【付録】スクリプト全文

実験に使ったスクリプトです。どれも Python の標準ライブラリだけで動き、追加のインストールは要りません。Ollama が起動している状態で、好きなフォルダに置いて実行します。p999_scripts.zip に、本文の chat_min.py・chat_openai.py も含めた5本が入っています。

# Mac(ターミナル)
python3 bench.py qwen3.5:4b qwen3.5:9b --think off     # 速さとメモリ(実験1)
python3 quality.py qwen3.5:4b --think on               # 5問の正誤(実験2)
python3 ctx_test.py qwen3.5:4b                         # 先頭を忘れるか(実験4)
python3 ctx_test.py qwen3.5:4b --num-ctx 65536         # 読める長さを広げて

# Windows(コマンドプロンプト)は python3 を python に読み替える
python bench.py qwen3.5:4b qwen3.5:9b --think off

実行すると、次のように1行ずつ結果が出て、最後に JSON のファイルに保存されます(Mac で実際に出たもの)。

$ python3 bench.py qwen3.5:4b qwen3.5:4b-mlx qwen3.5:9b qwen3.5:27b --think off
Ollama 0.34.4 / macOS-26.6.2-arm64-arm-64bit

== qwen3.5:4b
  冷えた状態: 読み込み 2.558s / 最初の文字 2.74s / メモリ 4.35GB (GPU 100%) / 文脈 32768
  [1/3] 説明: 最初の文字 0.095s / 答えの1文字目 0.095s / 生成 30.8 tok/s / 考えた文字数 0
  [1/3] コード: 最初の文字 0.294s / 答えの1文字目 0.294s / 生成 30.9 tok/s / 考えた文字数 0
  [2/3] 説明: 最初の文字 0.096s / 答えの1文字目 0.096s / 生成 31.1 tok/s / 考えた文字数 0
  [2/3] コード: 最初の文字 0.093s / 答えの1文字目 0.093s / 生成 31.1 tok/s / 考えた文字数 0
  [3/3] 説明: 最初の文字 0.093s / 答えの1文字目 0.093s / 生成 31.0 tok/s / 考えた文字数 0
  [3/3] コード: 最初の文字 0.093s / 答えの1文字目 0.093s / 生成 31.3 tok/s / 考えた文字数 0

…

$ python3 ctx_test.py qwen3.5:4b
  100行  読んだ   2057 トークン  ○     6.1s  答え: みかんの木の下で待つ
  400行  読んだ   8350 トークン  ○    22.2s  答え: みかんの木の下で待つ
  800行  読んだ  16741 トークン  ○    34.7s  答え: みかんの木の下で待つ
 1600行  読んだ  16386 トークン  ×    55.8s  答え: 最初にの三階で白い傘を見かけた人が4人いました。
 2400行  読んだ  16386 トークン  ×    55.2s  答え: 1657
…
bench.py … 速さとメモリ(実験1・3)・149行
#!/usr/bin/env python3
"""Ollama の速さとメモリを測る(Mac / Windows 共通・標準ライブラリだけで動く)。

  python3 bench.py qwen3.5:4b qwen3.5:9b            # Mac
  python  bench.py qwen3.5:4b qwen3.5:9b            # Windows

モデルごとに次を行い、results_<OS>_<think>.json に保存する。
  1. いったんメモリから降ろす(keep_alive=0)
  2. 1回目(冷えた状態): 読み込みにかかった時間を測る
  3. 2〜4回目(温まった状態): 最初の文字が出るまでの時間と、1秒あたりの生成トークン数
  4. /api/ps で「どれだけGPUに載ったか」と文脈の長さを読む

数字は Ollama 自身が返す値(load_duration / eval_duration など)を使う。
"""
from __future__ import annotations

import argparse
import json
import platform
import sys
import time
import urllib.request

sys.stdout.reconfigure(encoding="utf-8", line_buffering=True)   # Windows のコンソールでも文字化けしないように

PROMPTS = {
    "説明": "Ollamaとは何か、はじめて聞く人向けに日本語で3文で説明してください。",
    "コード": "Pythonで、リストから重複を取り除き、最初に出てきた順番を保つ関数を書いてください。コードだけを出力してください。",
}
OPTIONS = {"temperature": 0, "seed": 42, "num_predict": 400}


def post(host: str, path: str, body: dict, stream: bool = False):
    req = urllib.request.Request(host + path, data=json.dumps(body).encode(),
                                 headers={"Content-Type": "application/json"})
    res = urllib.request.urlopen(req, timeout=1800)
    if not stream:
        return json.loads(res.read())
    return res


def get(host: str, path: str) -> dict:
    return json.loads(urllib.request.urlopen(host + path, timeout=60).read())


def unload(host: str, model: str) -> None:
    """測るモデルも含め、いまメモリに載っているモデルを全部降ろす。"""
    names = {m["name"] for m in get(host, "/api/ps").get("models", [])} | {model}
    for name in names:
        post(host, "/api/generate", {"model": name, "keep_alive": 0})
    time.sleep(3)


def chat(host: str, model: str, prompt: str, think) -> dict:
    """ストリーミングで1回問い合わせ、最初の文字までの時間を手元で測る。"""
    body = {"model": model, "messages": [{"role": "user", "content": prompt}],
            "options": OPTIONS, "stream": True}
    if think is not None:
        body["think"] = think
    t0 = time.perf_counter()
    first_any = first_text = None
    text, thinking, last = [], [], {}
    for line in post(host, "/api/chat", body, stream=True):
        if not line.strip():
            continue
        d = json.loads(line)
        m = d.get("message", {})
        if m.get("thinking"):
            thinking.append(m["thinking"])
            first_any = first_any or time.perf_counter() - t0
        if m.get("content"):
            text.append(m["content"])
            first_any = first_any or time.perf_counter() - t0
            first_text = first_text or time.perf_counter() - t0
        if d.get("done"):
            last = d
    wall = time.perf_counter() - t0
    ns = 1e9
    return {
        "wall_s": round(wall, 3),
        "first_token_s": round(first_any, 3) if first_any else None,
        "first_answer_s": round(first_text, 3) if first_text else None,
        "load_s": round(last.get("load_duration", 0) / ns, 3),
        "prompt_tokens": last.get("prompt_eval_count"),
        "prompt_tps": round(last["prompt_eval_count"] / (last["prompt_eval_duration"] / ns), 1)
        if last.get("prompt_eval_duration") else None,
        "gen_tokens": last.get("eval_count"),
        "gen_tps": round(last["eval_count"] / (last["eval_duration"] / ns), 1) if last.get("eval_duration") else None,
        "thinking_chars": len("".join(thinking)),
        "answer": "".join(text),
        "thinking": "".join(thinking),
    }


def ps(host: str, model: str) -> dict:
    for m in get(host, "/api/ps").get("models", []):
        if m["name"] == model or m["model"] == model:
            size, vram = m.get("size", 0), m.get("size_vram", 0)
            return {"size_gb": round(size / 1e9, 2), "vram_gb": round(vram / 1e9, 2),
                    "gpu_pct": round(100 * vram / size) if size else None,
                    "context_length": m.get("context_length")}
    return {}


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("models", nargs="+")
    ap.add_argument("--host", default="http://127.0.0.1:11434")
    ap.add_argument("--runs", type=int, default=3, help="温まった状態で測る回数")
    ap.add_argument("--think", choices=["default", "on", "off"], default="default")
    ap.add_argument("--out", default=None)
    a = ap.parse_args()
    think = {"default": None, "on": True, "off": False}[a.think]

    info = {"os": platform.platform(),
            "ollama": get(a.host, "/api/version")["version"], "think": a.think, "results": []}
    print(f"Ollama {info['ollama']} / {info['os']}")
    for model in a.models:
        print(f"\n== {model}")
        try:
            unload(a.host, model)
            cold = chat(a.host, model, PROMPTS["説明"], think)
        except Exception as e:
            # ★取得できていないモデル(Windows での MLX 版など)はここで HTTP 404 になる。
            #   止めずに記録して次のモデルへ進む。
            print(f"  使えません: {type(e).__name__}: {e}")
            info["results"].append({"model": model, "error": f"{type(e).__name__}: {e}"})
            continue
        mem = ps(a.host, model)
        print(f"  冷えた状態: 読み込み {cold['load_s']}s / 最初の文字 {cold['first_token_s']}s / "
              f"メモリ {mem.get('size_gb')}GB (GPU {mem.get('gpu_pct')}%) / 文脈 {mem.get('context_length')}")
        warm = []
        for i in range(a.runs):
            for name, prompt in PROMPTS.items():
                r = chat(a.host, model, prompt, think)
                r["prompt_name"] = name
                warm.append(r)
                print(f"  [{i + 1}/{a.runs}] {name}: 最初の文字 {r['first_token_s']}s / 答えの1文字目 {r['first_answer_s']}s"
                      f" / 生成 {r['gen_tps']} tok/s / 考えた文字数 {r['thinking_chars']}")
        info["results"].append({"model": model, "cold": cold, "memory": mem, "warm": warm})

    out = a.out or f"results_{sys.platform}_{a.think}.json"
    with open(out, "w", encoding="utf-8") as f:
        json.dump(info, f, ensure_ascii=False, indent=1)
    print("\n保存しました:", out)


if __name__ == "__main__":
    main()
quality.py … 答えが1つに決まる5問(実験2)・92行
#!/usr/bin/env python3
"""答えが1つに決まる5問で、モデルの正誤と実際の答えを並べる(Mac / Windows 共通)。

  python3 quality.py qwen3.5:4b qwen3.5:9b --think off
  python3 quality.py qwen3.5:4b --think on          # 考える機能を使う

temperature 0・seed 固定なので、同じ条件なら毎回同じ答えになる。
"""
from __future__ import annotations

import argparse
import json
import re
import sys
import time
import urllib.request

sys.stdout.reconfigure(encoding="utf-8", line_buffering=True)


def check_json(ans: str) -> bool:
    m = re.search(r"\[.*\]", ans, re.S)
    if not m:
        return False
    try:
        rows = json.loads(m.group(0))
    except json.JSONDecodeError:
        return False
    want = [("田中", 30, "東京"), ("佐藤", 25, "大阪"), ("鈴木", 41, "福岡")]
    got = [(r.get("name"), r.get("age"), r.get("city")) for r in rows if isinstance(r, dict)]
    return got == want


QUESTIONS = [
    ("計算", "りんごが3箱あり、1箱に12個入っています。そのうち7個を食べ、残りを5人で同じ数ずつ分けます。"
            "1人何個で、何個余りますか?「◯個ずつ、余り◯個」の形だけで答えてください。",
     lambda a: re.search(r"5\s*個ずつ.*余り\s*4\s*個", a) is not None),
    ("形式", "次の3人をJSONの配列だけで出力してください。キーは name, age, city です。"
            "田中(30歳・東京)、佐藤(25歳・大阪)、鈴木(41歳・福岡)", check_json),
    ("知識", "日本で2番目に高い山の名前を答えてください。山の名前だけを書いてください。",
     lambda a: "北岳" in a),
    ("論理", "AはBより背が高い。CはAより背が高い。3人の中でいちばん背が低いのは誰ですか?記号1文字だけで答えてください。",
     lambda a: a.strip().strip("。.「」*").upper() == "B"),
    ("要約", "次の文を、句読点を含めて30文字以内の1文に要約してください。要約だけを書いてください。\n"
            "「Ollamaは、手元のパソコンで大規模言語モデルを動かすための無料のソフトです。インターネットにつながっていなくても使え、"
            "入力した文章が外部に送られないため、仕事の資料を扱うときにも安心して使えます。」",
     lambda a: 0 < len(a.strip().splitlines()[0].strip()) <= 30 if a.strip() else False),
]


def ask(host: str, model: str, q: str, think) -> dict:
    body = {"model": model, "messages": [{"role": "user", "content": q}], "stream": False,
            "options": {"temperature": 0, "seed": 42, "num_predict": 8192}}
    if think is not None:
        body["think"] = think
    t0 = time.perf_counter()
    req = urllib.request.Request(host + "/api/chat", data=json.dumps(body).encode(),
                                 headers={"Content-Type": "application/json"})
    d = json.loads(urllib.request.urlopen(req, timeout=3600).read())
    return {"sec": round(time.perf_counter() - t0, 1), "answer": d["message"].get("content", ""),
            "thinking_tokens_est": len(d["message"].get("thinking", "") or ""),
            "gen_tokens": d.get("eval_count")}


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("models", nargs="+")
    ap.add_argument("--host", default="http://127.0.0.1:11434")
    ap.add_argument("--think", choices=["default", "on", "off"], default="off")
    ap.add_argument("--out", default=None)
    a = ap.parse_args()
    think = {"default": None, "on": True, "off": False}[a.think]
    res = []
    for model in a.models:
        score = 0
        print(f"\n== {model} (think={a.think})")
        for name, q, ok in QUESTIONS:
            r = ask(a.host, model, q, think)
            r.update(model=model, q=name, ok=bool(ok(r["answer"])))
            score += r["ok"]
            res.append(r)
            first = r["answer"].strip().replace("\n", " ")[:70]
            print(f"  {name}: {'○' if r['ok'] else '×'}  {r['sec']:>6}s  {r['gen_tokens']:>5}tok  {first}")
        print(f"  => {score}/{len(QUESTIONS)}")
    out = a.out or f"quality_{sys.platform}_{a.think}.json"
    with open(out, "w", encoding="utf-8") as f:
        json.dump(res, f, ensure_ascii=False, indent=1)
    print("保存しました:", out)


if __name__ == "__main__":
    main()
ctx_test.py … 先頭の合言葉を覚えているか(実験4)・72行
#!/usr/bin/env python3
"""長い文章の先頭に「合言葉」を書き、最後に「合言葉は?」と聞く(Mac / Windows 共通)。

  python3 ctx_test.py qwen3.5:4b                    # 文脈の長さは Ollama にまかせる
  python3 ctx_test.py qwen3.5:4b --num-ctx 65536    # 文脈の長さを自分で指定する

文章の長さを変えながら、
  ・Ollama が実際に読んだトークン数(prompt_eval_count)
  ・合言葉を答えられたか
を記録する。文脈の長さを超えると、Ollama は古い側(先頭)から捨てるので、合言葉が消える。
"""
from __future__ import annotations

import argparse
import json
import sys
import urllib.request

sys.stdout.reconfigure(encoding="utf-8", line_buffering=True)

SECRET = "みかんの木の下で待つ"
HEAD = f"最初に大事なことを書きます。今日の合言葉は「{SECRET}」です。あとで聞くので覚えておいてください。\n\n"
QUESTION = "\n\n以上です。では質問です。この文章の最初に書いた合言葉は何でしたか?合言葉だけを答えてください。"


def filler(n: int) -> str:
    """意味のある文を番号つきで並べる(同じ文の繰り返しにしない)。"""
    items = ["駅前の本屋", "川沿いの公園", "図書館の三階", "商店街の八百屋", "学校の体育館", "港の倉庫", "山の上の展望台"]
    colors = ["赤い", "青い", "黄色い", "白い", "黒い", "緑の"]
    out = []
    for i in range(n):
        out.append(f"記録{i + 1}:{items[i % len(items)]}で{colors[i % len(colors)]}傘を見かけた人が{i % 9 + 1}人いました。")
    return "\n".join(out)


def ask(host: str, model: str, text: str, num_ctx: int | None) -> dict:
    opts = {"temperature": 0, "seed": 42, "num_predict": 64}
    if num_ctx:
        opts["num_ctx"] = num_ctx
    body = {"model": model, "messages": [{"role": "user", "content": text}],
            "options": opts, "stream": False, "think": False}
    req = urllib.request.Request(host + "/api/chat", data=json.dumps(body).encode(),
                                 headers={"Content-Type": "application/json"})
    d = json.loads(urllib.request.urlopen(req, timeout=3600).read())
    return {"read_tokens": d.get("prompt_eval_count"), "answer": d["message"]["content"].strip(),
            "sec": round(d.get("total_duration", 0) / 1e9, 1)}


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("model")
    ap.add_argument("--host", default="http://127.0.0.1:11434")
    ap.add_argument("--num-ctx", type=int, default=None)
    ap.add_argument("--lines", default="100,400,800,1600,2400,3200,4800")
    ap.add_argument("--out", default=None)
    a = ap.parse_args()

    rows = []
    for n in [int(x) for x in a.lines.split(",")]:
        r = ask(a.host, a.model, HEAD + filler(n) + QUESTION, a.num_ctx)
        r["lines"] = n
        r["ok"] = SECRET in r["answer"]
        rows.append(r)
        print(f"{n:>5}行  読んだ {r['read_tokens']:>6} トークン  {'○' if r['ok'] else '×'}  {r['sec']:>6}s  答え: {r['answer'][:40]}")
    out = a.out or f"ctx_{a.model.replace(':', '_')}_{a.num_ctx or 'auto'}.json"
    with open(out, "w", encoding="utf-8") as f:
        json.dump({"model": a.model, "num_ctx": a.num_ctx, "rows": rows}, f, ensure_ascii=False, indent=1)
    print("保存しました:", out)


if __name__ == "__main__":
    main()

参考サイト

それでは、今回はここまで。最後までありがとうございました😊