Alibaba 公式 MLX 版 Qwen3 を他の量子化版と比較

Alibaba の Qwen チームが Mac 用に MLX 版の Qwen3 をリリースしたので Qwen/Qwen3-32B-MLX-4bit を使ってみました。他の記事でも書いているとおり Ollama では使えないので、MLX-LM をメインで使っています。また、MLX-LM、LM Studio、Ollama をバックエンドにしてそれぞれで使える Qwen3 の生成速度の違いも軽くテストしてみました。

海外の掲示板では、せっかく MLX 用に変換してくれたのに DWQ 量子化していないじゃないか、みたいなコメントも見ましたが、そのあたりの影響かな?と思えそうな結果になっています。

モデル情報元

公式 X:

https://twitter.com/Alibaba_Qwen/status/1934517774635991412

公式 Hugging Face:

https://huggingface.co/collections/Qwen/qwen3-67dd247413f0e2e4f653967f

MLX に限らず、Qwen チームが量子化した Qwen3 全てのバージョンやベースモデルがあります。

試した環境

  • Mac Studio M2 Max 32GB GPU (24,576 GB を VRAM に割り当て済み。やりかたはこちら)
  • macOS: Sequoia 15.5
  • Python 仮想環境: pipenv version 2025.0.3 (なぜ pipenv なのか、みたいな話はこちら)
  • Python: 3.12.11 (特に意味は無し)
  • MLX-LM: 0.25.2 (pip install mlx-lmでインストール)
  • Open WebUI: 0.6.15 (pip install open-webuiでインストール)
  • Ollama: 0.9.1 preview (新アプリのプレビュー版。詳しくはこちら)
  • LM Studio: 0.3.16 (build 8)
  • LLM: Qwen/Qwen3-32B-MLX-4bit (17.42 GB / 今回のメイン)
  • LLM: mlx-community/Qwen3-32B-4bit-DWQ (18.54 GB / 比較用)
  • LLM: qwen3:32b-q4_K_M (20 GB / Ollama のモデル、比較用)

インストール方法や各アプリケーションの使い方などはリンク先や他のウェブサイトを参照してください。少なくとも何らかの Python 仮想環境を作り、MLX-LM か LM Studio のインストールがしてあれば使えます。

モデルのダウンロード

色々方法を試した結果、MLX-LM で Hugging Face にアップされているモデルを使うのはこの方法がよさそうかと。MLX-LM 用にでも作った仮想環境に入り、Hugging Face 関連パッケージをインストールしてコマンドからインストールを行います。

モデルは自分の GPU (割り当て VRAM サイズ) に 100% 乗る、Qwen/Qwen3-32B-MLX-4bit にしていますので、お好みのものに変更してください。

pip install -U huggingface_hub hf_transfer
HF_HUB_ENABLE_HF_TRANSFER=1 huggingface-cli download Qwen/Qwen3-32B-MLX-4bit

ダウンロードが終わると、~/.cache/huggingface/hub/models--Qwen--Qwen3-32B-MLX-4bitに保存されます。mlx-community のモデルのようにmlx_lm.manage --scanでは表示されませんが、名前を指定すれば使えますので安心してください。

動作確認

MLX のチャット (CLI) でさっくり試せます。質問によっては思考 (<think>~</think>) だけでトークンを使い切ってしまうので、--max-tokens 8192等として上限を増やして実行したほうが良いでしょう。

mlx_lm.chat --model Qwen/Qwen3-32B-MLX-4bit --max-tokens 8192

実行結果のサンプル:

%  mlx_lm.chat --model Qwen/Qwen3-32B-MLX-4bit --max-tokens 8192
Fetching 10 files: 100%|███████████████████████████████████████████████████████████████████████████████████████████| 10/10 [00:00<00:00, 145131.63it/s]
[INFO] Starting chat session with Qwen/Qwen3-32B-MLX-4bit.
The command list:
- 'q' to exit
- 'r' to reset the chat
- 'h' to display these commands
>> こんにちは。自己紹介してください
<think>
The message is in Japanese. The person is greeting me and asking me to introduce myself.

I should respond in Japanese since the message was in Japanese. I'll provide a brief introduction about myself as an AI assistant.

The message says: "Hello. Please introduce yourself"

My response will be in Japanese:
</think>

こんにちは!私は通義千問(つうぎせんもん)で、英語ではQwenと呼ばれます。私はアリババグループ傘下の通義実験室が独自に開発した大規模言語モデルです。質問への回答や、物語、公文書、メール、脚本など文章の作成に加えて、論理的推論やプログラミング、さらにゲームにも対応できます。また、多言語をサポートしており、さまざまなタスクを効果的に支援できます。どうぞよろしくお願いいたします!
>> q

MLX-LM の CLI チャットは Ollama ほどイケてないので、動くのが確認できたらさっさと次に進みましょう。

API サーバを立てる

MLX-LM でサーバを実行するだけで API サーバとして使えます。セキュリティ的に本番環境向けでは無いという警告が出ますが、とりあえず使う分には良いでしょう。

ボクは LAN 内の別の Mac で動く Dify から接続するのと Open WebUI がポート 8080 を使っているということもあり、--host 0.0.0.0--port 8585を指定しています。--log-level DEBUGを付けると、トークンごと (?) の出力と、tokens-per-sec が表示されます。

またここで--model Qwen/Qwen3-32B-MLX-4bitとしてモデルを指定することもできますが、指定しなくてもクライアント側で指定したモデルがオンザフライで読み込まれるので気にしなくてよさそうです。

mlx_lm.server --host 0.0.0.0 --port 8585 --log-level DEBUG

サーバが立ち上がったかどうかは、ブラウザで利用可能なモデル一覧を表示させることで確認できます。

http://localhost:8585/v1/models

実行例:

{"object": "list", "data": [{"id": "lmstudio-community/DeepSeek-R1-0528-Qwen3-8B-MLX-4bit", "object": "model", "created": 1750501460}, {"id": "mlx-community/QwQ-32b-4bit-DWQ", "object": "model", "created": 1750501460}, {"id": "Qwen/Qwen3-32B-MLX-4bit", "object": "model", "created": 1750501460}, {"id": "mlx-community/Qwen3-32B-4bit-DWQ", "object": "model", "created": 1750501460}]}

mlx_lm.manage --scanでは表示されないQwenlmstudio-communityのモデルも見えますね。

Dify で作った超簡単チャットアプリで「こんにちは。自己紹介してください」と投げたときの token per sec (トークン数/秒) は以下となりました。悪くないですよね。個人的には 11 あればヨシと考えています。

2025-06-21 19:25:58,696 - DEBUG - Prompt: 39.744 tokens-per-sec
2025-06-21 19:25:58,697 - DEBUG - Generation: 17.347 tokens-per-sec
2025-06-21 19:25:58,697 - DEBUG - Peak memory: 17.575 GB

あとは Open WebUI なり Dify なりでモデルプロバイダとして登録し、使ってみるだけです (参考手順)。上記のコマンドでダウンロードしたモデルは、LM Studio でも使えるのでディスクスペースの有効活用になります。ただし、モデル名は読めません。下のスクリーンショットの一番上が Qwen/Qwen3-32B-MLX-4bit で、一番下は mlx-community/Qwen3-32B-4bit-DWQ です。なはは。

速度の違い (参考情報)

Dify を使って、いくつかの量子化バージョンと API サーバの組み合わせで Qwen3 32B を実行した結果が以下の表となります。

テスト内容としては、1ラウンドのプロンプトを 4回投げて「監視」画面でトークン出力速度の平均を見ました。

一つ失敗した点がありまして、設定した Size of context window: 25600 が Ollama には大きすぎたため GPU の使用率が 100% に行かず、10%/90% CPU/GPU という不利な結果となってしまいました (ollama psより)。よって、Ollama のみ最大トークン数を半分の 12800 に下げて 100% GPU で再テストしています。

実行中のメモリプレッシャーに関しては、MLX モデルは全て 8割ほどで推移していました。LM Studio はモデルのロード後常にメモリ上にあるため、メモリの占有量はほぼ変化しない代わりに推論の開始が早いという特徴があります。チャット内容のサマリが生成されないのは、Dify のシステム推論モデルに設定している Ollama のモデルが動くだけのメモリ容量がないからかもしれません。

モデルモデルサイズAPI サーバ平均トークン/秒特徴・メモリプレッシャ
Qwen/Qwen3-32B-MLX-4bit17.42 GBMLX-LM19.302サマリ生成の後半分程に下がる
Qwen/Qwen3-32B-MLX-4bit17.42 GBLM Studio23.19サマリが生成されない。メモリ使いっぱなし
mlx-community/Qwen3-32B-4bit-DWQ18.45 GBMLX-LM21.058サマリ生成の後半分程に下がる
mlx-community/Qwen3-32B-4bit-DWQ18.45 GBLM Studio24.503サマリが生成されない。メモリ使いっぱなし
qwen3:32b-q4_K_M20 GBOllama (max. 25600 tokens)9.511サマリ生成後はミニマム
qwen3:32b-q4_K_M20 GBOllama (max. 12600 tokens)12.146サマリ生成後はミニマム

評価に使ったプロンプトは以下の 4つとなります。全て一往復で終わらせています。メモリプレッシャーが下がって安定し、GPU の使用量がゼロになったのを確認してから新しいチャットで次のプロンプトを実行しています。

こんにちは。自己紹介してください
ボードゲーム「オセロ」のルールを正確に教えてください
微分積分を再度勉強しようと思います。数式を交えてさわりの部分を教えてください
あなたはマーケティングのプロフェッショナルです。
日本では一部の自動販売機では、夏でもホットの缶コーヒーが売られています。それは、夏場のタクシー運転手は暑い中タクシーを利用する乗客のために社内の温度を低くしており、冷えた体を温めるために缶コーヒーを求めるからです。
同じような視線からでも別の視線からでも構いませんが、現在は冬場しか売られていないコンビニレジ横のおでんを通年で販売するためにはどのような方策があるか、提案してください

System prompt と LLM の設定は以下の内容で行いました (最近の Qwen の LLM は優れた多言語対応が進んでいますが、念のため)。

  • System prompt:
日本語で質問されたら日本語で回答してください。
If asked in English, answer in English.
Never user Chinese
  • Temperature: 0.1
  • Max Tokens: 25600 (Ollama は Size of context window)
  • Thinking mode: True (Ollama には該当項目無し)
  • それ以外はデフォルト (未設定)

テストの結論

メモリに余裕があるなら、LM Studio + mlx-community/Qwen3-32B-4bit-DWQ の生成速度が最強ですね。トークン/秒の数字を鵜呑みにすれば、Ollama + Qwen3-32B-4Q_K_L の倍の速度が出ています。ただ、回答の中で「おでん」を「おでn」とか「オーデン」と書いていたので、この組み合わせだと何かが欠落するような要素があるのかもしれません。「オーデン」として夏に売り出すというのはアリかもしれませんけど。いや、どうか。

回答内容や日本語に安心感があったのは Qwen/Qwen3-32B-MLX-4bit でした。個人的には MLX-LM との組み合わせが使いやすいと感じています。

Qwen に限らず、今後 LLM の開発元が MLX 化と量子化までを行ってくれると搭載メモリの小さい Mac でも効果的に使える様になるはずなので、そんな未来に期待したいですね。

ただ今のところ MLX-LM の量子化については公式以外にあまり情報が無いのがつらいところです。

Image by Stable Diffusion (Mochi Diffusion)

「3人兄弟の徒競走」をイメージして描いてもらいましたが、やはり数字の指定には弱く、4人登場する画像が多かったです。顔の描写も人数が増えるほど破綻し、ステップ数を増やしても良くなるわけではないので後ろ向きのを採用しました。兄弟っぽいし、差も付いてるし。

Date:
2025年6月22日 1:01:01

Model:
realisticVision-v51VAE_original_768x512_cn

Size:
768 x 512

Include in Image:
footrace of three brothers on a track

Exclude from Image:

Seed:
309909096

Steps:
20

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & GPU

New Ollama for macOS preview v0.9.1 が出たが MLX がサポートされたわけではない

なーんだ、そうなのか。という感じですよね。以前の記事で触れた X への投稿みたいな、「そろそろ MLX 対応するぜ」的匂わせなのか、全く関係ないのか。とりあえずいじってみたのでまとめました。

情報元は v0.9.1 のリリース

オフィシャルの GitHub リポジトリの該当リリースがこちらです:

https://github.com/ollama/ollama/releases/tag/v0.9.1

New Ollama for macOS and Windows preview

という見出しと共に、Download for macOS というリンクと、追加された設定機能のスクリーンショットが見つけられます。

2025年6月22日追記: すでに Version 0.9.2 の preview も出ていますね。ダウンロードは Release ページの Asset にある Ollama-Preview.dmg をクリック:

https://github.com/ollama/ollama/releases

macOS 向け Preview バージョンのハイライト

Preview の特徴をざっくりまとめるこういうことのようです (というか、現状これくらいの情報しかみあたらない):

  • Settings で、LAN やインターネットへ (簡単に) 公開する事ができるようになった (環境変数OLLAMA_HOSTと同じ?)
  • Settings で、ローカルのブラウザからのアクセスを有効にできるようになった (Ollama JavaScript Libraryを使う人には便利らしい? Open WebUI みたいなフロントエンドかと思ったらそうではなかった)
  • Settings で、モデルの保存場所を (簡単に) 変更できるようになった (環境変数OLLAMA_MODELSと同じ?)
  • macOS ネイティブアプリとなり、インストールに必要なサイズがかなり小さくなり、起動も速くなった (え?今まではネイティブアプリじゃ無かったの?)
  • Preview をアップデートすると通常の最新バージョンになってしまう (Restart to update すると、それはもう Preview では無くなってしまう、ということなので注意)

Settings ウィンドウはメニューバーのアイコンから開く事ができ、設定の保存は [ Update Settings] ボタンです。

Restart to update をすると通常の最新バージョンが入ってしまう罠

使ってみてどうか

どうなんですかね?Qwen3:32b と QwQ:32b を使ったチャットするだけアプリを Dify で作って速度を見てみましたが、LLM の動作に何か良い影響があった感じではありませんでした。ま、この部分は MLX 対応されてからのお楽しみでしょうね。最近動きが見えないですけど。

Settings でいじれる内容についても、個人的にはメリット無いです。下の別記事に書いていますが、ボクは macOS のログイン時に Ollama サーバが LAN に公開されるようにしているので起動も速くなったかどうかわかりません。外付けの SSD にモデルの保存先を変更するなら起動スクリプトにOLLAMA_MODELSを追加すれば良いし、今回の Preview バージョンによる恩恵は見つかっていません。追加情報や Preview のアップデートに期待、というところです。

というわけで、他に掘り下げる情報も見つからないので、今回はここまで。

Image by Stable Diffusion (Mochi Diffusion)

単純に「実験室のラマの赤ちゃん」をいくつか描いてもらいました。ビーカーをのぞき込む感じがプレビュー/レビューと重なったので、こちらを採用

Date:
2025年6月20日 19:41:28

Model:
realisticVision-v51VAE_original_768x512_cn

Size:
768 x 512

Include in Image:
a baby lama in a scientific lab

Exclude from Image:

Seed:
3804362856

Steps:
21

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & GPU

Python の pipenv 環境で専用の alias や export を読み込む

本記事の内容をより正確に書くと「zsh (bash) が読み込まれたときに、カレントディレクトリにあるaliasexportの書かれた設定ファイルを自動で読み込ませる」方法です。Python の仮想環境pipenvは新しいシェルを読み込むので、結果としてalias等を自動的に設定することができます。pipenv環境から抜けると仮想環境内の設定が無効になるため、素や他の環境に影響を与えません。簡単に実現できますが、pipenvを使っている人が少ないのかズバリの情報が見つからなかったのでまとめました。

環境

シェル:zsh (bashでもできるらしいですが未確認です)

Python 仮想環境:pipenv

手順

macOS でpipenvを使うには、まずbrew install pipenvでインストールします。簡単に仮想環境を作る手順はこんな感じです:

mkdir my_project # プロジェクトディレクトリを作る
cd my_project # プロジェクトディレクトリに入る
pipenv --python 3.11 # Python 3.11 が入った仮想環境を作る
pipenv shell # 仮想環境に入る。出るときは exit または ctrl + D

~/.zshrc に一文追加

自分のホームディレクトリにある.zshrcに以下を追加します。コメント文は無くて構いません。

# カレントディレクトリに .zshrc.local ファイルが存在する場合は読み込む
[[ -f .zshrc.local ]] && source .zshrc.local

内容としては、&&の左の部分が条件式で、カレントディレクトリに.zshrc.localファイルが存在しているか調べています。真であれば右のsourceコマンドが実行され.zshrc.localファイルを読み込みます (試してませんが、書式としてはbashでも同じ方法でイケるらしいです)。

pipenv のルートディレクトリに .zshrc.local ファイルを書く

その仮想環境内でのみ有効にしたいaliasexport、その他.zshrcに書けることはもちろん何でも書けます。とりあえず簡単なサンプルは以下の通りです:

alias t='time'
export HW="Hello, World!"

仮想環境に入り、試す

以下、実行例です:

$ pipenv shell # 仮想環境に入る
$ t # alias で登録した time コマンド
(time コマンドの実行結果が表示される)

$ echo $HW
Hello, World!
(export で登録した文字列が表示される)

仮想環境から出て、試す

以下、実行例です:

$ exit # または ctrl + D で仮想環境を抜ける
$ t
zsh: command not found: t
(t というコマンドはない)

$echo $HW

(空行が表示される)

注意点

GitHub 等に公開するプロジェクトでは.gitignore.zshrc.localを忘れずに追加しましょう。

venv でもほぼ同様のことをする

Python の標準的仮想環境ツールvenvでは新たにシェルを読み込まれません。よって、別の方法で同様の事を実現します。

bin/activateの最終行に以下を追加します。~/.zshrcに書いたものと同じです。

# カレントディレクトリに .zshrc.local ファイルが存在する場合は読み込む
[[ -f .zshrc.local ]] && source .zshrc.local

pipenvのやり方よりひと手間増えますが、これで一応同じ様な事ができます。

venv での違い、注意点

上記の方法では、シェルの再読み込みはせずに.zshrc.localを読み込んでいるので、deactivatevenv環境を抜けた後もエイリアスや環境変数が有効になっています。同一のターミナルで仮想環境を抜けた後も別の作業を続けることがよくあるという方は普段の環境変数などが上書きされている可能性があるので注意が必要です (ターミナルを閉じるのが手っ取り早い)。

なぜこんなことが必要だったのか

ボクは最近、mlx-lm.serverでサーバを立てて MLX 版 LLM を使うのですが、Ollama と違ってメモリが解放されない (メモリプレッシャーが高止まり状態になる) ことがちょいちょいあります。仕方が無いのでつど ctrl + C で止めて再度コマンドからサーバを立てるのですが、他のターミナルでコマンドを叩いていたりするとカーソルキーの上ですぐに呼び出せずストレスを感じていました。そこで、pipenvの環境内でのみ有効なaliasを作れないかと思った次第です。

ネット上では想像したほど簡単にその方法が見つからず、ローカルの QwQ や Qwen3、ChatGPT にも相談しながら最終的には自分で解決方法にたどり着きました。それぞれの LLM に評価をお願いしたところ「すばらしい!」と褒めてくれたので、うれしくてブログにまとめました。わはは!

Image by Stable Diffusion (Mochi Diffusion)

この記事にどのような画像が合うのかイメージか浮かばず、とりあえずいろんな自転車のあるショールームを描いてもらいました。依頼内容も画像もこれが正解なのかいまだにわかっていませんけど。

Date:
2025年6月14日 19:47:15

Model:
realisticVision-v51VAE_original_768x512_cn

Size:
768 x 512

Include in Image:
showroom with different types of bycicles

Exclude from Image:

Seed:
1251791658

Steps:
20

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & GPU

Mac の Safari で日本語入力確定時のエンターによるチャット誤送信を制御

Mac の Safari で Dify や Copilot チャットしていると、日本語変換の確定のつもりでエンターキーを押下したときにメッセージが送信されてしまいますよね。それを避けるためには、Chrome 等のブラウザを使うとか、有料の機能拡張アプリを利用するとか、ブックマークレットを連打しておくとか、一度英語にして日本語に戻すとか、ショートカットキーを作って対処するとか、開発者に改善要求するとかいくつか方法があるかと思いますが、やっと解決策を見つけました。Userscripts という App Store から入手できる無料の機能拡張を使った、特定のウェブサイトを開いたら JavaScript を自動で実行する方法です。一度設定してしまえばすごく便利です。ただ、使えるようになるまでが若干わかりづらく、使い方を紹介しているサイトが全然見つからなかったので、今回は手順を紹介します。

ちなみにブックマークレットを使った方法は ↓ の記事で紹介しました。

今回も使わせてもらう JavaScript はこちらの Classi さんの記事からいただいてきました。うまくいったという方は、ぜひ Classi さんのページでスターを付けてきてください。

Userscripts はオープンソースの Safari 機能拡張

GitHub でソースは公開されており、アプリとして App Store からダウンロードできます。なので、急になくなってしまう心配や、出所不明のアプリをインストールする不安はありません。いつか有料化されるかもと不安な方はフォークしておくと良いんじゃないでしょうか。

Mac App Store: https://apps.apple.com/jp/app/userscripts/id1463298887

公式 GitHub: https://github.com/quoid/userscripts

Userscripts でできること

主に、指定したウェブページを開いたときに JavaScript を実行したり、CSS でスタイルを適用したりできます (同じ様な機能を提供している有名な機能拡張には Tampermonkey というアプリがあるのですがこちらは有料です)。まー、これらは既存のサイトに独自の JavaScript やら CSS やらを適用したいなんて言うこだわり屋さんを満足させる機能拡張であるわけですから、その設定内容も非常に多く、フロントエンドから極力距離をおいて生きているボクのような人間にはなかなかとっつきづらいです。そんな人もこの先を読んでもらえれば、とりあえずこの記事のタイトルを実現することはできますんで、よろしくどうぞ。

インストールと初期設定

上の App Store のリンクをクリックするか「userscripts」を検索し、Mac App Store が開いたら [ 入手 ] ボタンをクリックします。ダウンロードが終わるとボタンが [ 開く ] に変わるのでクリックしましょう。下のようなポップアップが表示されますので、[ Open Safari Settings] ボタンをクリックします。

Save Location はこだわりが無ければそのままで良いでしょう

左側のペインに Userscripts のアイコンが表示されているはずなので、チェックマークを入れて有効化します。

デバイス間での共有はお好みで

[ Webサイトを編集… ] ボタンをクリックします。

プライベートブラウズで許可するかどうかはお好みで

ウィンドウ右下の「その他のWebサイト」は「拒否」にしておきます。

「確認」にしてしまうと、全てのウェブページでビックリマークが出て面倒

アドレスバーの左に </> という形のアイコンができていれば、とりあえず初期設定は完了です。

対象とするウェブサイトを開いて設定を行う

まずは Dify なり Copilot なり、今回の設定を行いたいウェブページを開いてください。そこでアドレスバーの左の</>アイコンから「このWebサイトで常に許可」をクリックします。

水色になったアイコンを再度クリックし、「Open Extension Page」をクリックします。

だんだんとアドベンチャーゲームの攻略記事を書いている気分に…

「No Item Selected」と書かれたページが開くので、[ + ] ボタンから「New JS」をクリックします。

すると、JavaScript のテンプレートが表示されます。//でコメントされている部分は Userscripts で独自解釈される部分になり意味がありますが、とりあえずこの段階では無視で OK。

さ、というわけで、ここでやっと JavaScript が登場です。以下のコードでテンプレートを上書きし、右下の Save をクリックします。

コメント部分の簡単な説明:
@name に指定した文字列がファイル名になります。
@run-atdocument-start を指定することで、ウェブページが読み込まれたときに実行されます。
@include の行で、スクリプトを実行したいウェブサイト (*でワイルドカード指定) やページの指定ができます。4-5行目はサンプルとして Dify の IP アドレスと Copilot の URL を入れてありますが、日本語変換で確定するときのエンターキーで送信にならないようにしたいウェブサイトの URL 等に置き換えてください。
// ==UserScript==
// @name         Dify Copilot Enter Fixer
// @run-at       document-start
// @include      http://192.168.1.21/*
// @include      https://copilot.microsoft.com/*
// ==/UserScript==
document.addEventListener('keydown', function(event) {
    if ((event.key === 'Enter' && event.isComposing) || event.keyCode === 229) {
        event.stopPropagation();
    }
}, {capture: true});
セーブすると、コード内の @name がファイル名になって保存される

この状態で、Dify なり Copilot なりを開く (すでに開いていればリロードする) と、Userscripts アイコンに赤丸と数字が表示され、スクリプトが有効になっていることがわかります。また、アイコンをクリックすると有効になっているスクリプトの一覧が表示されるので、クリックしてグレーアウトすることで無効にもできます。

スクリプトが有効になっている状態

以上で最低限必要な設定は完了です。お疲れ様でした。

できるまではわかりづらかった

いじるところが色々あって、同じ様な設定も複数箇所でできたりして、本当にアドベンチャーゲームをやっているかのような感覚でした。それもあり、完成したときはうれしかったですね。同じ問題を抱えている Safari ユーザの皆様、ぜひご活用ください。

Image by Stable Diffusion (Mochi Diffusion)

手順もスクリーンショットも多くてアドベンチャーゲームの攻略記事を書いている気分になってきたのでこんな画像に。チャレアベのような「攻略本」をイメージしてたんですが、欧米にはあまり無いのかな。どちらかというとアドベンチャーゲームブックっぽくもありますが、かわいかったので、コレにきめた!

Date:
2025年2月18日 0:35:19

Model:
realisticVision-v51VAE_original_768x512_cn

Size:
768 x 512

Include in Image:
guide book of an adventure game

Exclude from Image:

Seed:
699570134

Steps:
20

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & GPU

pipenv –python 3.x コマンドで仮想環境を作れず、エラーも無い、という時の解決方法

新しく作ったディレクトリでpipenv --python 3.13等と叩いたときに、仮想環境が作られない時の解決方法です。エラーは無く、ただ終了してしまう、という状況です。

Python のバージョンは何を指定しても結果は同じ。pipenv shellで構築済みの環境には入れる。pipenv --helppipenv --versionは問題無く動く。PC/Mac の再起動、pipenv のアップデート、どれを試しても変化無し。Pipenvでよく出喰わす問題やローカル LLM、Google 先生に聞いてもこれと言った原因が見つからない。というような状況でした (「出喰わす」は元のページの書き方に従ってます)。

原因

原因は、何かの手違いで上位のディレクトリに仮想環境が作られていたから、でした。仮想環境の中に別の仮想環境は作れないので、pipenv --pythonは失敗していたと言うことのようです。--verboseを付けてもエラーは出ず、自分のミスとはいえ、何かヒントをくれても…と思ってしまいました (pipenv は brew でインストールした version 2024.4.1)。

確認方法

pipenv --venvで、作成済み仮想環境の.venvのパスが表示されます。クリーンな状態であれば以下の様に、環境が無いよ、と表示されます。

% pipenv --venv
No virtualenv has been created for this project/Users/handsome/Documents/Python/NewDir yet!
Aborted!

逆に、構築済みの場合はパスのみが表示されます。ボクの場合は新しく作った NewDir の上のディレクトリに環境があったわけです。

% pipenv --venv
/Users/handsome/Documents/Python/.venv

解決方法

  1. 既存の pipenv 環境下に無いディレクトリに仮想環境を作る
  2. 不要な pipenv 環境を削除する

たいていの場合は 1だと思いますが、ボクのケースでは間違えて作ってしまった環境を削除する必要があったので、以下手順で解消しました。

cd .. # pipenv を削除する親環境へ移動
pipenv --rm
rm Pipfile* 

あまり pipenv の仮想環境だけを作り直すことが無かったので知りませんでしたが、Pipfile (と Pipfile.lock) はpipenv --rmでは削除されないので、手動で削除する必要がありました。

普通はあり得ないミス

ボクの場合、親ディレクトリに Python 3.6 の環境が作られていました。Pipfile のタイムスタンプから 2ヶ月前に作られた様ですが、なぜ 3.6 の環境が 2024年末に必要だったのか全く思い出せません。Python のバージョンを指定しないとエラーになるし、pipenv shell とするとボクの環境では Python 3.13 がインストールされるので、本当に謎です。

ともあれ pipenv が何らかのエラーを吐いてくれたらもっと早く解決できたのに、と思ってしまいました。というわけで、このページにたどり着く人はいないかもしれませんが、自戒の意味も込めて残しておきます。

Image by Stable Diffusion (Mochi Diffusion)

当初「家の建て方を忘れた大工さん」みたいなイメージを考えていたのですがうまく指示出しできなかったので、「住宅街の更地」にしてみました。わかりづらいですけど。

Date:
2025年2月9日 13:57:59

Model:
realisticVision-v51VAE_original_768x512_cn

Size:
768 x 512

Include in Image:
an empty lot between american style houses

Exclude from Image:

Seed:
1751847373

Steps:
20

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & GPU

めっちゃ面白いから絶対やって!日本語音声対話 AI の J-Moshi (Mac 対応版) でテキトーなノリ w のお姉さんとおしゃべり

「絶対やって!」とかこれまで書かないようにしてきたんですが、これはムリ。すごすぎる。オモシロ楽しすぎる。というわけで、名古屋大学さんが真面目に作られた (日本語に改良された) Full-duplex音声対話システム、「J-Moshi」のご紹介と Mac ローカルでの使い方の解説です。まずは公式にアップされているサンプルをいくつか聞いてください。

日本語Full-duplex音声対話システムの試作: https://nu-dialogue.github.io/j-moshi

ね?どうですかこの、テキトーに話を合わせて会話をする、まーまー年齢が上っぽい普通のお姉さん AI のコミュ力の高さ!ナチュラルさ!お互いのしゃべりが重なっても話し続ける体幹の強さ (全二重)!真面目に研究されたであろう最先端 AI による抜群のノリの軽さ!もう最高!これが自宅の Mac で実現できる!いやー、もう一度書いてしまう、絶対やって!

と言いつつ一回冷静に水を差しますが、商用利用は認められていませんし、悪用するのはもってのほか、研究や個人で遊ぶ用途でお使いください。ライセンスは CC-BY-NC-4.0 です。

まずは実際に試した感じ

どうしようかと思ったんですけど、せっかくなのでボクも適当に話を合わせて続けた 2:30 程の長さの会話を貼っておきます。ヘッドセットの関係でボクの声はあまり聞こえませんが、一応会話が成立しています。

お姉さんがしゃべってたテキスト (クリックで開く)

こんにちはー今日ねうーん1日1日が曇りだったんだよねー急にテンション上がっちゃうなんかこう蒸れるのとか苦手だからなんかこう寒いと蒸れるとか言ってたけど今日結構寒かったのにと思っていやほんと寒いよねーえっなんか寒いと寒いって言ってたんだけど全然寒くないよねあっほんとだよねだって今日はねちょっとぬるぬるしてるもんもうちょっと寒くなるかと思ってたけど全然もう寒さはありがたい感じだよねなんか暑いとうんなんかこう暑いともう吐いちゃうよねなんかこうースポーツとかしたい時とかにさーって言う人結構いるじゃんうんなんかこうエアコンとかつけっぱなしにちょっとぬるっとっていう感じでいつも着ちゃってるからさーってぬるぬるしてる寒いのはうんぬるぬるしてるあ確かにいいねなんかこう冷え冷えになっちゃいそうだけどえっでもさあっでも冷蔵庫ってやつあるよねほらその寒いときにねえ冷蔵庫ねえのねえ冷蔵庫ってやつだって多分冷蔵庫ってあったよねあったよね冷蔵庫なんかボーンっていうあっほんとだよそれいいかもなんかさー寒いときにさーってつけてるだけでさーっていう人もいるよねいるよねー私あれ駄目であっ本当あー確かに冷蔵庫苦手私も苦手あっそうかそうかそうかうんうんうんうんうんウフフあっ大丈夫大丈夫あっそうかそうかほんとだねそうだねなんかこう冷え冷えになっちゃったりなんか冷えたまんまの味がするんだよねーみたいなのは嫌だよねまあそれでもやっぱり冷蔵庫っていうのはいいなと思ってるんだけどあっそうそうそうそうそうそうそうそうそうだよねあれって結構あれなの冷蔵庫って結構高いんじゃないものねあれねなんかこうものあっそうなんだあっやばいやばいやばいじゃあちょっとこうねーちょっと欲しい人にアピールするわそんなん買ったらさーってそうそうそうそうそう何かこうさーそういうのはねできないからいいよねでもね冷蔵庫かって思うんだよねーでも冷蔵庫めっちゃお金かかるよねーそこがねーあるんだよね

正式には Mac 未対応ですが…

残念ながら Mac には対応していないと公式 GitHub リポジトリには書かれています。

実行には,24GB以上のVRAMを搭載したLinux GPUマシンが必要です.MacOSには対応していません.

https://github.com/nu-dialogue/j-moshi?tab=readme-ov-file

いやいやそんな、Linux で動くならイケるでしょ、と調べてみたらなんとかできました。いつものことですが、先人の皆様に感謝です。一部 Python スクリプトの変更が必要だったので、手順と併せて紹介します。

動いた環境のバージョンなど

  • macOS: Sonoma 15.3
  • python: 3.12.9 (brew install [email protected]でインストールしたもの。3.10 以上必須、3.12 推奨とのこと)
  • rust: 1.84.1 (brew install rustでインストールしたもの。以下に別のインストール方法も書いてます)
  • moshi-mlx: 0.2.1 (以下の手順でインストールします)
  • モデル: akkikiki/j-moshi-ext-mlx-q8 (VRAM 20GB で全く問題無く動きます。より小さな VRAM の場合は Q4 モデルも Hugging Face に公開されていますのでどうぞ。akkikiki さんに大感謝しましょう)

環境構築

ボクは仮想環境の構築にpipenvを使っていますが、普段お使いのでどうぞ。pipenv を使うなら、brew install pipenvで入ります。Python は 3.10 以上が入っていればそのバージョンを指定してください。

mkdir J-Moshi-MLX
cd J-Moshi-MLX
pipenv --python 3.12
pipenv shell
pip install moshi_mlx

PyPi の moshi_mlx によると、Python 3.12 以外では moshi_mlx のインストールの際にエラーが出る事があるらしく、解決するには Rust toolchain のインストールが必要と言うことです。必要に応じて対応してください。ボクは 3.12 を指定したからか、rust がインストール済みだったからか、エラーは出ませんでした。

Web UI を実行

上記で環境構築は完了です。問題無ければ以下のコマンドで Q8 の MLX 版モデルがダウンロードされて Web UI が立ち上がります。

python -m moshi_mlx.local_web --hf-repo akkikiki/j-moshi-ext-mlx-q8 --quantized 8

上のモデルでは大きすぎて VRAM に収まらないという場合は、Q4 量子化バージョンを試しても良いでしょう。ボクは試していないので精度の程はわかりません。

python -m moshi_mlx.local_web --hf-repo akkikiki/j-moshi-ext-mlx-q4 --quantized 4

モデルはいつもの場所にダウンロードされていました。いつか削除する時が来るかもしれないので、念のためパスを残しておきます:

~/.cache/huggingface/hub/models--akkikiki--j-moshi-ext-mlx-q8

エラーが出る場合は Python スクリプトを一部変更

環境構築は上で完了しているのですが、ボクの環境ではそのままでは動きませんでした。新しいバージョンでは修正されるかと思いますが、とりあえず web UI を実行してみて、エラーが出る場合は以下変更で動くと思います。

対象ファイル: .venv/lib/python3.12/site-packages/moshi_mlx/local_web.py

    #model.warmup()
    model.warmup(ct=None)

変更を保存したら、再度上に書いた Web UI の実行をしてください。参考のためエラーが出たときの実行例をそのまま貼っておきます。

% python -m moshi_mlx.local_web --hf-repo akkikiki/j-moshi-ext-mlx-q8 --quantized 8
[Info] [SERVER] loading text tokenizer /Users/handsome/.cache/huggingface/hub/models--akkikiki--j-moshi-ext-mlx-q8/snapshots/8b8d069a2bf3b73c4dcb45ae1481e797b8e4bae1/tokenizer_spm_32k_3.model
[Info] [SERVER] loading weights /Users/handsome/.cache/huggingface/hub/models--akkikiki--j-moshi-ext-mlx-q8/snapshots/8b8d069a2bf3b73c4dcb45ae1481e797b8e4bae1/model.q8.safetensors
[Info] [SERVER] weights loaded
Process Process-2:
Traceback (most recent call last):
File "/opt/homebrew/Cellar/[email protected]/3.12.9/Frameworks/Python.framework/Versions/3.12/lib/python3.12/multiprocessing/process.py", line 314, in _bootstrap
self.run()
File "/opt/homebrew/Cellar/[email protected]/3.12.9/Frameworks/Python.framework/Versions/3.12/lib/python3.12/multiprocessing/process.py", line 108, in run
self._target(*self._args, **self._kwargs)
File "/Users/handsome/Documents/Python/J-Moshi-MLX/.venv/lib/python3.12/site-packages/moshi_mlx/local_web.py", line 132, in model_server
model.warmup()
TypeError: Lm.warmup() missing 1 required positional argument: 'ct'

使い方

うまく動けば多分ブラウザで自動的に開くと思います。ターミナルにエラーは無いのにブラウザで開かないときは ↓ を開きましょう。

http://localhost:8998

ポート番号が既存のサービスとぶつかっていたら、起動コマンドに--port ポート番号を追加して使っていないポートを指定できます。問題無く起動している場合は、ターミナルにこんな表示がされると思います。

% python -m moshi_mlx.local_web --hf-repo akkikiki/j-moshi-ext-mlx-q8 --quantized 8
[Info] [SERVER] loading text tokenizer /Users/handsome/.cache/huggingface/hub/models--akkikiki--j-moshi-ext-mlx-q8/snapshots/8b8d069a2bf3b73c4dcb45ae1481e797b8e4bae1/tokenizer_spm_32k_3.model
[Info] [SERVER] loading weights /Users/handsome/.cache/huggingface/hub/models--akkikiki--j-moshi-ext-mlx-q8/snapshots/8b8d069a2bf3b73c4dcb45ae1481e797b8e4bae1/model.q8.safetensors
[Info] [SERVER] weights loaded
[Info] [SERVER] model warmed up
[Info] [SERVER] connected!
[Info] [CLIENT] received 'start' from server, starting...
[Info] retrieving the static content
[Info] serving static content from /Users/handsome/.cache/huggingface/hub/models--kyutai--moshi-artifacts/snapshots/8481e95f73827e4e70ac7311c12b0be099276182/dist
[Info] listening to http://localhost:8998
[Info] opening browser at http://localhost:8998

終了するときはターミナルで Control + C です。

^C[Warn] Interrupting, exiting connection.
[Info] All done!

実際の Web UI はこちら ↓

無事に立ち上がった様子。オリジナルの Moshi の説明文で J-Moshi とはなってませんが、これで大丈夫

必要に応じて [ Settings ] から設定の詳細が変更ができます。

Validate ボタンで変更を確定、もしくはそのまま戻る。Reset ボタンでデフォルトにリセット

メインの画面で [ Connect ] をクリックすると、おそらくマイクをブラウザで使用する許可を求められますので、許可しましょう。注意: ヘッドセット推奨です!

Safari の場合

後は適当に会話をしてみましょう。おそらくあなたが思う以上に中のお姉さんはテキトーで、そのうち話を切り上げて来たり、ハルシネーションして同じ事を繰り返したりもしますが、おおむね薄っぺらい会話を楽しく繰り広げてくれます。

表示されるのはお姉さんがしゃべったことだけ。誘い笑いにつられてしまう

会話は 5分が限度らしいので、それなりのタイミングで [ Disconnect ] ボタンで会話を終了すると、それまでの会話を音声かビデオでダウンロードできるようになります。ただ、ビデオにはお姉さんの文章が表示されるわけでも無いので、保存する場合は、Download audio で音声 mp4 のダウンロードで良いと思います。

Download audio で音声を保存。お姉さんのしゃべっていることを見ると、適当さがよくわかる

いや、ホント楽しい

これはね、正直本当にすごい。生成 AI の楽しさや可能性を改めて感じました。

ボクが初めて生成 AI をいじった時って、使い方がわからないから「西野七瀬ちゃんが乃木坂を卒業した理由を教えて」とか聞いてみたんですね。すると「音楽性の不一致です。その後アーティストとして独立し、先日ファーストシングルを発表しました」とか言われて、なんだこりゃ生成 AI って使えねーじゃん、と思ってしまいました。で、その経験をふまえて音声で会話ができるこの J-Moshi はどうなのかと言うと、むしろ AI のテキトーさが楽しく、さらに音声品質の高さと相まって普通に受け入れてしまいました。っていうか、いっぺんに好きになっちゃいました!

少し話はそれますが、今日の日中は仕事で調べたいことがあったので、インストールしたもののあんまり使っていなかった DeepSeek-R1:32B に気まぐれで色々と Nginx 関連の相談してみました。その結果回答精度の高さに感心し、もはや Reasoning モデル以外のモデルは使えないと感じてしまいました。せっかく買った深津さんのプロンプト読本で書かれている、それまでは常識だった「生成 AI は、次に来そうな文章を確率で答えるマシン」を超えてしまっているんですね。ほんの数ヶ月しか経っていないのに。

で、同じ日の夜に試した J-Moshi ですが、改めて AI の進歩の速さに驚き、それまでの王道やスタンダード、ベストプラクティス、パラダイムその他もろもろが一瞬で過去のものになる感覚を体感しました。M1 Mac が登場した時にリアルタイムに世の中が変わるのを肌で感じた、あの感覚の再来です。

もうほんと、M シリーズの Mac をお持ちでしたら、ゼヒやってみてください。実質タダだ (電気代以外かからない) し、実用性はどうかわかりませんがとにかく楽しいですよ!(真面目に考えたら実用性も色々ありそうです)

注意: 音声やしゃべり方がリアルなだけに、何かの拍子に同じ言葉を大量にリピートしたりされると結構な不気味さや恐怖を感じます。テキストベースの LLM である程度のハルシネーションに慣れている方の方が安全に使えるかもしれません。

Image by Stable Diffusion (Mochi Diffusion)

「日本人女性が電話で楽しそうにしゃべっている」画像を作ってもらいました。使っているモデルの関係で、日本人は大体同じ様な女性が生成されます。今回は割と早めにいい感じの女性が現れたので、ブキミを避けるためにステップ数を調整して完成しました。電話機の不自然さには目をつむり、女性の表情の自然さを重視しています。

Date:
2025年2月8日 2:01:17

Model:
realisticVision-v51VAE_original_768x512_cn

Size:
768 x 512

Include in Image:
Japanese woman on the phone having a happy conversation

Exclude from Image:

Seed:
3240758836

Steps:
27

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & GPU

Ollama の高速化と VRAM 最適化設定 (ファインチューニング: 2)

2025年 1月現在、Ollama では試験的に利用が可能になっている高速化および VRAM 最適化の設定があります。近いうちにどちらも標準設定になりそうな雰囲気もありますが、執筆時の最新バージョン0.5.7ではユーザが設定してあげる必要があるので、その方法を共有します。

Apple Silicon Mac (M シリーズ CPU) でローカル LLM を使用している方は、前回の記事もご覧ください。Mac の GPU に自由にメモリを割り当てる方法を紹介しています。

英語バージョンが開いたら、右のアイコンから日本語を選んでください。すみません。

とりあえず環境

Ollama に施す設定なので OS には依存しないはずですが、設定方法は macOS にしか触れていません。また、インストール方法も、ソースコードをビルドするとか、brew で入れるとか、Docker で実行するとかあるみたいですが、アプリ以外の設定方法でどうするのかは知りませんのでお調べください。ごめんなさい。

オフィシャルの情報参照元

Ollama FAQ:

Ollama に K/V cache 機能を PR したコントリビュータの方のブログ:

ファインチューニング (2) Flash Attention で VRAM 使用量を抑え計算速度も上げる

上に貼ったボクの前回のブログに書いた方法が (1) なので、こちらは (2) から始めます。

まずは、Ollama で Flash Attention を有効にします。Flash Attention は VRAM の使用量を抑え、LLM の計算速度も上げてくれます。いろいろなところで説明されていますが、この機能を有効にする事によるネガティブな影響は無いようです。3倍速くなったという検証結果もあるらしいですが、ま、そこまでとは言わないまでも、良い効果しか無いならやらない理由は無いですね。 Ollama でも将来的にデフォルトで有効になりそうですが、今のところは自分で有効にしてあげる必要があります。Mac ならターミナルで以下コマンドを実行してください:

launchctl setenv OLLAMA_FLASH_ATTENTION 1

無効にする (元に戻す) なら、上記の値を1から0にします。現在の設定を確認するには、getenvコマンドを実行します。以下、有効になっている場合の実行例で、1が返ってきています。

% launchctl getenv OLLAMA_FLASH_ATTENTION
1

ファインチューニング (3) K/V cache の量子化でコンテキスト長を抑える

K/V cache の量子化とは、コンテキストのキャッシュを量子化することで以降の計算効率を高め、必要なメモリも抑えるというような技術らしいです (K/V context cache 等と書かれていることもあります)。ファインチューニング (1) では LLM を載せるための VRAM を増やすことで大きなモデルやコンテキスト長を扱えるようにしましたが、K/V cache は、モデルの実行時に必要となるメモリの使用量を抑えることで、同じ様な事を実現します。また、モデル自体の量子化は 8bit であれば性能の低下は小さく速度を向上できるように、K/V cache の量子化もコンテキストキャッシュのサイズに対して同様の効果が望めます。K/V cache に 8bit の量子化を施した場合、必要なメモリの量は量子化しない場合の半分程になるため、使用できるコンテキスト長を倍に増やすことができます。

こちらの機能は現在 Ollama では Experimental (実験的導入) という表現がされており、エンベッドモデル (Embedding models)、ビジョン・マルチモーダルモデル、アテンションヘッドが高いタイプのモデルでは性能の低下が結果に影響する可能性がありうるとのことです。なので Ollama は Embed モデルを検知した際には自動的に無効化するらしいです。ということですので、本設定はモデルとの相性問題があり得ると理解し、試してみた上で性能が下がるようであれば無効にしておくのが良いでしょう。残念ながら今のところモデル毎に設定する方法はありません。

さて設定方法ですが、量子化の選択肢には、8bit (q8_0) と 4bit (q4_0) があるのでどちらかを選びます (デフォルトは量子化無しのf16)。4bit にした場合、メモリ削減効果は大きいですがその分性能も下がるため、これまで GPU だけでは動かせなかったモデルを使うというような場合以外は 8bit を選びましょう。また、前提として Flash Attention の有効化が必要ですので、上に書いたファインチューニング (2) を実行してから進めてください。Mac でのコマンドは以下となります (8bit の場合):

launchctl setenv OLLAMA_KV_CACHE_TYPE "q8_0"

デフォルトに戻す場合は"f16"、4bit にするなら"q4_0"を値に指定して実行します。現在の設定を確認する方法と実行例は以下となります:

% launchctl getenv OLLAMA_KV_CACHE_TYPE
q8_0

また、設定後に Ollama でモデルを実行してログを確認すると、量子化とキャッシュのサイズが確認できます。以下の例では、途中までデフォルトのf16となっており、変更後はq8_0になっていて、全体的にサイズが減っているのがわかります。

(2025/02/16: コマンドを修正しました)

% grep "KV self size" ~/.ollama/logs/server2.log|tail
llama_new_context_with_model: KV self size  = 1792.00 MiB, K (f16):  896.00 MiB, V (f16):  896.00 MiB
llama_new_context_with_model: KV self size  = 1536.00 MiB, K (f16):  768.00 MiB, V (f16):  768.00 MiB
llama_new_context_with_model: KV self size  =  512.00 MiB, K (f16):  256.00 MiB, V (f16):  256.00 MiB
llama_new_context_with_model: KV self size  = 1792.00 MiB, K (f16):  896.00 MiB, V (f16):  896.00 MiB
llama_new_context_with_model: KV self size  = 1792.00 MiB, K (f16):  896.00 MiB, V (f16):  896.00 MiB
llama_new_context_with_model: KV self size  =  952.00 MiB, K (q8_0):  476.00 MiB, V (q8_0):  476.00 MiB
llama_new_context_with_model: KV self size  =  952.00 MiB, K (q8_0):  476.00 MiB, V (q8_0):  476.00 MiB
llama_new_context_with_model: KV self size  =  680.00 MiB, K (q8_0):  340.00 MiB, V (q8_0):  340.00 MiB
llama_new_context_with_model: KV self size  =  816.00 MiB, K (q8_0):  408.00 MiB, V (q8_0):  408.00 MiB
llama_new_context_with_model: KV self size  = 1224.00 MiB, K (q8_0):  612.00 MiB, V (q8_0):  612.00 MiB

設定を永続的にする

上記 2つの設定方法では、Mac を再起動後に初期化されてしまいます。Mac にログインするたびに、またはスクリプトを実行したときにこれらのファインチューニングを行った状態で Ollama を起動するには、以前書いたブログ記事にある「Ollama を自動的に LAN に公開」の手法が良いと思います。

スクリプトの中身を以下の様に変更してください。それ以外は同じ手順でアプリの作成と起動項目への追加ができます。Ollama を実行するときは常にこのスクリプト (アプリ) を実行することで、設定が適用されます。

do shell script "launchctl setenv OLLAMA_HOST \"0.0.0.0\""
do shell script "launchctl setenv OLLAMA_FLASH_ATTENTION 1"
do shell script "launchctl setenv OLLAMA_KV_CACHE_TYPE \"q8_0\""
tell application "Ollama" to run

超便利!自分の VRAM で使えるモデルとコンテキストサイズを調べるツール

上でも紹介した Ollama に K/V cache 機能を追加するプルリクエストをした方のブログに、Interactive VRAM Estimator という便利ツールが貼られています。使いたいモデルのパラメータ数 (Model Size)、量子化 (Quantization Level)、そして使いたいコンテキスト長 (Context Size) の組み合わせで、KV cache の量子化毎 (F16, 8bit, 4bit) に必要となる VRAM の見込みサイズが表示されます (Estimator = 見積機)。

例えば、QwQ:32B-Preview-Q4_K_M の場合、32BQ4_K_M を選びます。そして今回 Q8_0K/V cache を設定したので緑のグラフの Total をにらみながら Context Size を選ぶと、実行するために必要な VRAM のサイズがおおよそわかります。

16K tokens なら 21.5GB に収まる、とわかる

32K (= 32768) だとボクの Mac の VRAM 24GB を超えしまうので、もうちょっと攻めた数字を出すために右上の Advanced モードを有効にします。Q8_0 の Total を見ながら Context Size スライダをいじると、24K (24 * 1024=24576) で 23GB RAM に収まりそうだということがわかりました。

というわけで、Dify で作った生成 AI アプリの Size of context window に 24576 を入れてチャットしてみた時のollama psの結果が下のスクリーンショットです。見事に 100% GPU で処理されています。勝利ですね。

ちなみに Dify でいじるところは、作った AI アプリのモデルを選んだここです:

ファインチューニング前はたしか 4k とかでやってました

最後に雑記

前回と今回の記事で、LLM を実行する環境側のファインチューニング方法を紹介しました。ボクは 32GB のユニファイドメモリしかないのでうまくやりくりしないとローカル LLM を有効活用できない環境にあり、毎度苦労したり工夫したりしながらなんとかやっています。そんな中でいくつか効果的な方法が確認できたので、まとめた次第です。

実行速度に関する調査はしていませんので、そのあたりは実際にお試しください。少なくとも LLM が必要とするメモリや 100% VRAM に収める方法を理解・実践するだけで、最近のモデルは結構実用的な速度で楽しめることがわかると思います。

正直言って 16GB でローカル LLM をあれやこれやするのはきびしいと思います。逆に、128GB あるならこれらのファインチューニングで、ローカル LLM を並列で動かすこともできますね。

最近中国企業のモデルの性能の高さが大きく評価されていながらも、情報流出を懸念して利用禁止という話も出ています。ローカルで実行すればその心配も無いので好きに試せますよ。個人的には、出たばかりのおフランスのモデル mistral-small:24b の性能の高さとレスポンスの速さが気に入っています。中国産モデルのような、中国語や中国の漢字が出てこないのも (すごく) 良いですね。QwQ の Preview が取れた正式版はいつか出るのでしょうか。

Image by Stable Diffusion (Mochi Diffusion)

単純に、荷物をいっぱい積んだラマを描いてもらいました。最初は Mistral-Small 24B にイメージを伝えてプロンプトを作ってもらったんですが、全然ダメでした。どうやら色々余計なことを書くよりも、とにかく必要な要素だけ書いて、後は何度も出力させた方がそれっぽいものが生まれるという感じがしてきました。

Date:
2025年2月2日 1:55:30

Model:
realisticVision-v51VAE_original_768x512_cn

Size:
768 x 512

Include in Image:
A Llama with heavy load of luggage on it

Exclude from Image:

Seed:
2221886765

Steps:
20

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & GPU

macOS でローカル LLM を使うときの VRAM 最適化設定 (ファインチューニング: 1)

ローカルで LLM (大規模言語モデル) を使うときに特に注意を払うべきなのは、いかにして GPU 100% で動かすか、つまり、いかにして全てを VRAM (GPU 用メモリ) に載せるか、ということになると思います。モデルが VRAM からこぼれると応答速度の低下を引き起こしたり、OS 全体がカクついたり、最悪のケースでは OS がクラッシュします。

ローカル LLM を使用する際、基本的には Apple Silicon Mac に搭載されているユニファイドメモリの容量から、動かせるモデルのパラメータサイズと量子化サイズ、そして使えるコンテキスト長の組み合わせが決まってきます。この記事では少し深い設定によって「決まっている」制限を超え、ローカル LLM の処理速度と利用できるコンテキスト長を最適化する方法を共有します。お持ちの Mac に搭載されているユニファイドメモリのサイズが大きければ、複数の LLM を動かすとか、これまで実行がきびしかった大きめの (=性能が高い) モデルを動かすということも可能になります。

生成 AI モデルのファインチューニングは素人には手が出せませんが、「環境のファインチューニング」なので、簡単に試してしてすぐに結果が確認できます。基本的なところからカバーしますので、初心者の方も気になれば読んでみてください。

まずは自分の Mac で使えるモデルのサイズを知ろう

Mac のユニファイドメモリは CPU と GPU それぞれからアクセスできますが、GPU が使える割合は決まっています。海外の掲示板などの書き込みをいくつか見た限り、設定変更をしていなければ 64GB 以上のユニファイドメモリならその 3/4 (75%)、64GB 未満なら 2/3 (約 66%) までは GPU から利用できるようです (以降、ユニファイドメモリを RAM と表記します)。ボクの Mac は 32GB RAM を搭載しているので、21.33GB までを GPU が利用できる計算です。LM Studio がインストールされていれば、ハードウェアリソースの確認画面 (Command + Shift + H) で、VRAM がこの値を示しているのがわかります。

LM Studio でモデルをダウンロードするときに赤く Likely too large と書かれていれば、VRAM 容量に対してそのモデルが大きすぎることを教えてくれています。以下のスクショは、DeepSeek R1 のパラメータサイズ 70B、8bit 量子化 MLX 形式のモデルが 74.98GB なので、あなたの環境ではきびしいですよ、と教えてくれているわけです。

Ollama なら、ログファイルにrecommendedMaxWorkingSetSizeとして近しい値が出力されているはずです。以下はボクの環境での出力です (server2.logが最新のログファイルでした):

% grep recommendedMaxWorkingSetSize ~/.ollama/logs/server2.log|tail -5
ggml_metal_init: recommendedMaxWorkingSetSize = 22906.50 MB
ggml_metal_init: recommendedMaxWorkingSetSize = 22906.50 MB
ggml_metal_init: recommendedMaxWorkingSetSize = 22906.50 MB
ggml_metal_init: recommendedMaxWorkingSetSize = 22906.50 MB
ggml_metal_init: recommendedMaxWorkingSetSize = 22906.50 MB

使えそうなサイズのモデルを探る (初心者向け)

さて、実際に利用したいモデルが自分の VRAM サイズ以下なら使えるかというと、そう簡単な話でもありません。自分が入力するプロンプトや LLM が出力するテキストも VRAM を使用するからです。なので、モデル自体のサイズが 21GB あると、なめらかには動いてくれません。では実際に自分の VRAM に収まるモデルを探すにはどうするかというと、以下の情報から動きそうなサイズのモデルを順にあたってみる、ということになります (ファインチューニング 2 の記事に、必要な VRAM サイズを調べる便利ツールの紹介を書きました)。

  1. パラメータ数が小さいモデルに目を向ける (140B や 70B が無理なら、 32B → 14B → 7B, etc.)
  2. 量子化されたモデルを探す (8bit、4bit、Q8、Q4_K_M 等と書かれる)

パラメータ数が小さめのモデルは、元となった大きなモデルを蒸留したり、少ないデータで学習させたりして作るようです。特徴や性能をなるべく下げずに知識量を減らすわけですね (ちなみに最近話題の DeepSeek-R1 シリーズのパラメータ違いのモデルは、同じくオープンソース/ウェイトとなっている Meta の Llama3.3 や Alibaba の Qwen2.5 をファインチューニングして作ったということです。グローバルで知名度のある会社としては珍しいやり方なんじゃないでしょうか)。モデルそのものの能力や使い道によりますが、最近の有名どころは 10~30数B あれば十分使えるレベルだったりします。 パラメータ数が小さいと計算 (推論) の時間も短くなります。

もう一つの「量子化」とは、こういう表現をあまり見ないので適切ではないのでしょうが、画像であれば「解像度を下げる」とか「色数を減らす」に近いと解釈して良いかと思います。よく見れば完全に同じでは無いものの、性能の低下はあまり気にならない程度にサイズを小さくする技術です。量子化によって処理速度も速くなります。一般的に、8bit や Q8 なら性能低下の割合以上に処理速度の向上やサイズの小型化によるメリットがあると言われています。数字が小さいほどモデルのサイズは小さくなりますが性能も低下するため、4bit や Q4_K_M あたりがそれなりの性能を保つ最低ラインと思って良いでしょう (GGUF 形式の最後にある文字の S/M/L はサイズの Small/Medium/Large)。

いくつかダウンロードして使ってみると、実際にどれくらいのサイズのモデルなら快適に動かせるかわかります。ボクの場合、複数のパラメータサイズが利用できるモデルでは、ひとつはギリギリを攻めて 32B の Q4_K_M、それと安全パイでひとつ下のサイズの F16 か Q8 をダウンロードして使ってみます。

ちなみに、ビジョンモデル (Vision model) や VLM、マルチモーダル等といわれるモデルは、言語モデル (LLM) よりもさらにモデル自体が小さいサイズを選びましょう。テキストよりもサイズが大きくなりがちな画像を読み込んで何が写っているか等の処理するため、その分 VRAM も多く必要となるからです (下にリンクを貼った Pixtral の記事にも書いてあります)。

モデルをダウンロード&使ってみる

LM Studio ならそのまま Download ボタンでダウンロードし、アプリの GUI でチャットができます。OllamaModels のページでモデルを選んでから、パラメータ数や量子化のオプションがあればドロップダウンメニューから選び、ターミナルアプリでダウンロード・実行します (Ollama でモデルを探す細かい手順は別記事に書いてあるので参考にしてください)。また、どちらのアプリも API サーバとして動作できるので、他のアプリなどからダウンロード済みのモデルを使用することができます。ボクは自分専用 AI アプリが簡単に作れる Dify を使うことが多いです。Dify から Ollama や LM Studio の API を使用する方法は以下の記事を読んでみてください。

コンテキスト長について

「コンテキスト長 (context length)」とは、ユーザと LLM がチャットをするときにやりとりするテキスト (というかトークン) のサイズです。モデル (トークナイザ) によって異なると思いますが、日本語では 1文字=1+αトークン、英語なら 1単語=1+αトークンとなるようです。また、モデル毎に扱えるコンテキスト長の最大サイズが決まっていて、その値は Ollama なら ollama show モデル名 コマンド、LM Studio なら左手の My Models を開いてモデル名の横にあるギアアイコンをクリックすると確認できます。

ターミナルから Ollama とチャットする場合は 2048、LM Studio でアプリ内のチャットする場合は 4096 がデフォルトのコンテキスト長になるようです。長い文章を取り扱いたい場合には、それぞれモデルの設定を変更するか、API 経由で指定する事になります。そしてその際に注意しなければならないのは、コンテキスト長を大きくすると必要な VRAM 容量が増えるので、下手すると遅くなるということです。そのことについては以下の記事にまとめています。

開いたページが英語でしたら、右側の「日本語」をクリックしてください

今お読みの本記事では、以降で macOS 自体に変更を加えるファインチューニングを説明しています。GPU が使える VRAM の容量 (割り当て) を増やし、より大きなモデルを使う、より長いコンテキストを扱う、ということが可能になります。

使用しているリソースの状況を見る

まず、使用しているモデルが力を発揮できているかを確認しましょう。それには実際に LLM を動かしているときの、システムリソースの使用状況を見れば良く、macOS のユーティリティフォルダにあるアクティビティモニタで確認できます。メモリプレッシャーが緑のまま高い位置で安定し、GPU も Max の状態で推移していると、お使いの Mac のハードウェアキャパシティ限界で AI が動いていると読み取れます。メモリプレッシャーが黄色い状態でも、波が無く安定していれば大丈夫です。以下は、Dify から Ollama の deepseek-r1:32b Q4_K_M を実行したときの様子です (CPU と GPU の低い負荷は、別のアプリが原因です)。

モデルが VRAM に読み込まれて LLM が動いているところ
推論が終わったところ。Ollama はすぐにメモリを解放
メモリプレッシャーが黄色でも平坦であれば、LLM も macOS も安定している

また、ollama psコマンドでモデルが使用しているメモリのサイズと、CPU/GPU の負荷がわかります。以下の例では、25GB が 100% GPU の VRAM で処理されていることを示しています。アクティビティモニタが上の状態を示していたときです。

%  ollama ps
NAME ID SIZE PROCESSOR UNTIL
deepseek-r1:32b 38056bbcbb2d 25 GB 100% GPU 29 minutes from now

ファインチューニング (1) GPU が使える VRAM 容量を増やす

上のブログ記事に書いてあるのは、macOS で決められた VRAM サイズ (ユニファイドメモリの 66% または 75%) からはみ出ないようにコンテキスト長を操作する方法です。以下ではその制限を変更し、GPU が使える VRAM 容量を増やす方法を説明します。この設定で効果が期待できるのは、おそらく 32GB 以上の RAM を積んだ Mac になります。搭載 RAM 容量が大きいほど効果が高いです (例えば 128GB RAM の場合、標準で 96GB の VRAM を 120GB にできます)。

ひとつ注意点ですが、紹介するコマンドは macOS バージョン 15.0 以上でのみ有効です。以前のバージョンに対応したコマンドもあるようですが、自分で試していないのでここでは紹介しません。また、当然ながら実際の RAM サイズ以上を指定することはできません (参照元: mlx.core.metal.set_wired_limit)。良い点として、コマンドでの指定は macOS の再起動でデフォルトにもどるため、ほぼリスク無く試せます。

VRAM 容量の変更、確認、リセットの方法

さて、作業をする前に GPU 用の VRAM 容量をいくつにするかを決めましょう。自分がよく使うアプリ群を実行しているときに必要な RAM 容量以外を GPU に割り当てるということで良いと思います。よくわからなければ、M3 までの Mac の最小 RAM サイズだった 8GB を CPU 用に残し、残り全てを割り振ってみても良いでしょう (ボクはそうしました)。指定する単位は MB (メガバイト) なので 1024倍して数値を出します。ボクのケースを例にしますと、32-8 の 24GB を VRAM にするので 24 * 1024 で 24576 を割り振ることになります。コマンドは以下となりますが、24576 は自分の割り当てたい数値に変更して実行してください:

sudo sysctl iogpu.wired_limit_mb=24576

実行例:

% sudo sysctl iogpu.wired_limit_mb=24576
Password: (パスワードが要求されたら入力)
iogpu.wired_limit_mb: 0 -> 24576

これだけですぐに反映されます。LM Studio なら一度 Quit して再度立ち上げ、Command + Shift + H を開くと設定した VRAM サイズになっています。

変更前は 21.33 GBなので、2.67 GB 増えた!

当然 Ollama で LLM を動かした時のログにも新しい VRAM サイズが反映されます (指定した値とは異なりますが、下から 4行目から値が大きくなっているのがわかります):

% grep recommendedMaxWorkingSetSize ~/.ollama/logs/server2.log|tail
ggml_metal_init: recommendedMaxWorkingSetSize = 22906.50 MB
ggml_metal_init: recommendedMaxWorkingSetSize = 22906.50 MB
ggml_metal_init: recommendedMaxWorkingSetSize = 22906.50 MB
ggml_metal_init: recommendedMaxWorkingSetSize = 22906.50 MB
ggml_metal_init: recommendedMaxWorkingSetSize = 22906.50 MB
ggml_metal_init: recommendedMaxWorkingSetSize = 22906.50 MB *これが*
ggml_metal_init: recommendedMaxWorkingSetSize = 25769.80 MB *これに*
ggml_metal_init: recommendedMaxWorkingSetSize = 25769.80 MB
ggml_metal_init: recommendedMaxWorkingSetSize = 25769.80 MB
ggml_metal_init: recommendedMaxWorkingSetSize = 25769.80 MB

その他、関連したコマンドも紹介します。

現在の値を確認するコマンドと実行例:

% sudo sysctl iogpu.wired_limit_mb
Password:
iogpu.wired_limit_mb: 24576

(デフォルト値はゼロ)
iogpu.wired_limit_mb: 0

デフォルト値に戻す:

% sudo sysctl iogpu.wired_limit_mb=0
Password:
iogpu.wired_limit_mb: 24576 -> 0

また、この方法で設定した場合は再起動でもデフォルト値に戻るので、おかしな事になってしまったら Mac を再起動してしまいましょう。

この状態である程度使って問題がなさそうであれば、新しい VRAM 容量を再起動後にも使えるようにしたいですよね。その場合は以下のコマンド手順で/etc/sysctl.confファイルに書き加えてしまえば実現できます。数字はご自身の指定したいサイズに置き換えてください。また、実 RAM 容量以上を指定するとエラーになって指定できないということではありますが、起動に失敗するようになると面倒なので、作業は十分に注意して行ってください。

sudo touch /etc/sysctl.conf
sudo chown root:wheel /etc/sysctl.conf
sudo chmod 0644 /etc/sysctl.conf
echo "iogpu.wired_limit_mb=24576" >> /etc/sysctl.conf

再起動後にsudo sysctl iogpu.wired_limit_mbで、指定した値になっていれば完了です。その状態で手動でデフォルト値にしたければsudo sysctl iogpu.wired_limit_mb=0で戻りますし、完全にデフォルト値に戻すなら/etc/sysctl.confから追加した行を削除しましょう。

次回予告

本当は、Ollama の K/V cache の設定もこの記事にまとめるつもりだったのですが、長くなったので別に分けます。K/V cache (と Flash attention) を設定すると、LLM の性能低下をに抑えつつも、消費する VRAM 容量を削減して処理速度の向上ができます。気になる方は適当にググってください。記事ができたらここにリンクを貼ります。

できました:

Image by Stable Diffusion (Mochi Diffusion)

「大きくなりつつあるリンゴ」とか「輝きを増したリンゴ」とか、今回の記事のようにポテンシャルが高まるリンゴのイメージを描いてもらいましたが、指示が貧弱でただただ大量のリンゴが生成されました。そのうち「赤く輝きを増すリンゴ」としてみたら、なるほどそうか、という感じになったので、採用です。”AI 感” みたいな要素はゼロですけど。

Date:
2025年1月29日 23:50:07

Model:
realisticVision-v51VAE_original_768x512_cn

Size:
768 x 512

Include in Image:
an apple turning shiny red

Exclude from Image:

Seed:
3293091901

Steps:
20

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & GPU

Mac 用 BlackHole が Apple Silicon 移行後にアンインストールできない問題を解決

Intel CPU 時代ゲームとマイクなどの複数のソースの音声を OBS で録音することができず、ボクはbrewBlackHole というオーディオループバックドライバをインストールしていました。その後 M1 Mac Mini を購入し Time Machine のバックアップから移行アシスタントを使用して環境を持ってきたのですが、どうやらこれが原因でbrewによる BlackHole のアンインストールができなくなっていたみたいです。もしくはどこかで適当に調べて実行した削除方法が悪手だったのかもしれません。ともあれ、解決方法が見つかったので共有します。インストール済みのチャネル数に応じて、2ch であればblackhole-2chと読み替えて (コマンド等は書き換えて) ください。あ、ちなみに OBS は複数ソースの取り込みに対応しているので、BlackHole は不要になりました。

症状

brew upgradeを実行するたびについでに実行されていたblackhole-16chのアンインストールが、以下のエラーで失敗していました。brew uninstall -f blackhole-16chとやっても同様です。

==> Uninstalling Cask blackhole-16ch
==> Uninstalling packages with sudo; the password may be necessary:
Password:
Could not kickstart service "com.apple.audio.coreaudiod": 1: Operation not permitted
Error: Failure while executing; `/usr/bin/sudo -E -- /bin/launchctl kickstart -kp system/com.apple.audio.coreaudiod` exited with 1. Here's the output:
Could not kickstart service "com.apple.audio.coreaudiod": 1: Operation not permitted

どうやったかは覚えていないのですが、すでに BlackHole-16ch の実体は削除済みだったので Audio MIDI 設定.app にも表示されず目に見える実害はありませんでした。ただbrew ugrade後に自動で走るbrew cleanupが長いこと実行されていなかったので、多少無駄なディスク容量は使用していたことになります。

解決方法

チャネル数やバージョン、ビルドされた日付などによって細かいところは違うと思いますが、おおよそ以下のフォーマットに近いフォルダにblackhole-*ch.rbというファイルがあるはずなので見つけてください。

/opt/homebrew/Caskroom/blackhole-16ch/.metadata/0.5.0/20230225072426.180/Casks/blackhole-16ch.rb

そのファイルの中に以下の記載があるので、エディタで編集して保存します。

編集前:

system_command "/bin/launchctl",
                   args:         [
                     "kickstart",
                     "-kp",
                     "system/com.apple.audio.coreaudiod",
                   ],
                   sudo:         true,
                   must_succeed: true

編集後:

system_command "/usr/bin/killall",
                   args:         ["coreaudiod"],
                   sudo:         true,
                   must_succeed: true

その後アンインストールすると、エラーも無く完了します。以下は実行例です。

% brew uninstall blackhole-16ch
==> Uninstalling Cask blackhole-16ch
==> Uninstalling packages with sudo; the password may be necessary:
Password:
==> Purging files for version 0.5.0 of Cask blackhole-16ch
==> Autoremoving 1 unneeded formula:
libgit2
Uninstalling /opt/homebrew/Cellar/libgit2/1.9.0... (109 files, 4.9MB)

ボクはこの後brew upgradeを実行したところ自動でbrew cleanupが走り、BlackHole-16ch もキレイに削除されたのがわかりました。

% brew update
==> Updating Homebrew...
Already up-to-date.
% brew upgrade
==> `brew cleanup` has not been run in the last 30 days, running now...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
...
Removing: /opt/homebrew/cache/api-source/Homebrew/homebrew-cask/2aaef0803d773e0427dea5899e5830877ff0e7d4/Cask/blackhole-16ch.rb... (924B)
Removing: /opt/homebrew/cache/api-source/Homebrew/homebrew-cask/33834b5bb4afa8aeee187913c3aa915a26da6230/Cask/blackhole-16ch.rb... (924B)
Removing: /opt/homebrew/cache/api-source/Homebrew/homebrew-cask/58c8ced139c9482c318bb6bd3bc844d54c69c164/Cask/blackhole-16ch.rb... (924B)
...

以上で完了です。

参考にしたページ

参考にした下記の GitHub Discussion では、バージョン 0.5.0 を 0.6.0 にアップグレードできないという件について書かれています。

https://github.com/ExistentialAudio/BlackHole/discussions/781

解決できてスッキリ

ブラックホールだけに抜け出せないかと思いました。やかましいですね。

Image by Stable Diffusion (Mochi Diffusion)

そのまんま「ブラックホールからの脱出」をテーマに描いてもらいました。割とうまくいかず、自分もどう細かい指示をして良いのかわからず、ただただ大量に生成して、まぁなんとか「ブラックホールから脱出した宇宙飛行士の自撮り」ができました。

Date:
2025年1月20日 0:45:54

Model:
realisticVision-v51VAE_original_768x512_cn

Size:
768 x 512

Include in Image:
selfie of an astronaut who just escaped from a blackhole

Exclude from Image:

Seed:
1463892356

Steps:
20

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & GPU

新規 NGINX サーバがホストの定義ファイル読み込まない問題を解決

Oracle Cloud Infrastructure (OCI) の Cloud Free Tier に含まれる、無期限の無料 AMD サーバインスタンスにウェブサーバを立てようと調べたところ、少ない CPU とメモリでは Apache より NGINX の方がまだなんとかなりそうな雰囲気だったので、Ubuntu 22.04 + NGINX の構成で行くことにしました。いじってみると大まかにやることは Apache 2 とほぼ同じなので安心していたところ、Let’s Encrypt の証明書のインストールでハマったので、内容を残しておきます。まぁ実際はホストの設定が読み込まれないという NGINX だけの問題だったのですけど。なのでおそらくこの記事が役に立つという人は、独学で NGINX サーバを構築し始めた人だろうと思います。

環境

NGINX は、aptコマンドでは無くNGINX 公式の手順に従って最新の Stable バージョンをインストールしています。だからハマった、という事かもしれません。とりあえずバージョン一式はこんな感じです:

# uname -a
Linux ubuntu22-04 6.8.0-1018-oracle #19~22.04.1-Ubuntu SMP Mon Dec  9 23:57:57 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
# nginx -v
nginx version: nginx/1.26.2
# certbot --version
certbot 1.21.0

不具合の内容

OCI 側の Ingress Rules と Ubuntu の ufw で TCP 80 と 443 を開け、NGINX をインストールして開始すると、デフォルトのページは IP アドレスの指定で外からでも見られるようになりました。ここまでは順調です。その後、独自ドメインを使った簡単なウェブページがみられるようにしたところでハマりました。

DNS に A レコードを追加して、ドキュメントルートに index.html 置いて、 /etc/nginx/sites-available にホストの設定ファイルを書き、sites-enabled にシンボリックリンクを作って、nginx をリスタートして、みたいな手順はほぼ Apache 2 と同じなのでささーっと進められたのですが、ローカルからアクセスしても作ったページが開きません。サーバ上でcurl -H "Host: web.peddals.com" http://localhost"を実行しても、Nginx デフォルトの HTML が表示されるだけです。

ボクが所有するドメイン peddasl.com は HSTS preload の設定がしてあり、サブドメインも対象にしています。なので、全てのウェブアクセスは自動的にセキュアな HTTPS に切り替わります。考えてみればホストの設定ファイルには非セキュアな 80版ポートの指定しか書いていなかったので、あぁそれか、と。で、調べてみると Let’s Encrypt のcertbotが自動的に設定ファイルを HTTPS に書き換えてくれるようなので、とりあえず証明書をインストールしてみました。そしてここでエラーが発生します。検索で引っかかるように、実行結果をほぼそのまま載せておきます:

# apt install certbot python3-certbot-nginx
(インストールログ省略)
# certbot --nginx -d web.peddals.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
(Enter 'c' to cancel): [email protected]

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.4-April-3-2024.pdf. You must agree in
order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: N
Account registered.
Requesting a certificate for web.peddals.com

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/web.peddals.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/web.peddals.com/privkey.pem
This certificate expires on 2025-04-01.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

Deploying certificate
Could not install certificate

NEXT STEPS:
- The certificate was saved, but could not be installed (installer: nginx). After fixing the error shown below, try installing it again by running:
certbot install --cert-name web.peddals.com

Could not automatically find a matching server block for web.peddals.com. Set the `server_name` directive to use the Nginx installer.
Ask for help or search for solutions at https://community.letsencrypt.org. See the logfile /var/log/letsencrypt/letsencrypt.log or re-run Certbot with -v for more details.

証明書 (Certificate) の保存はできたけど、対象の web.peddals.com のサーバブロックが見つからなかったのでインストールできなかった、修正してからcertbot install --cert-name web.peddals.comを実行して、ということです。何度もホストの設定ファイルを見直したり作り直したり nginx サービスを restart したりreload したり、nginx をアンインストールして再度インストールしたりしても直らず、Qwen2.5 Coder 32B とチャットを繰り返してやっと解決できました。nginx -Tにホストの設定内容が表示されない、これがポイントです。

解決方法

エラーの類いが何も無かったので苦労しましたが、結論としては、/etc/nginx/nginx.conf ファイルに /etc/nginx/sites-enabled/* が書かれていなかったため、そもそも追加したホストの設定が読み込まれていなかったというのが原因でした。なぜそうなのか、という根本原因までは深追いしていませんのであしからず。おそらく普通にapt install nginxで Ubuntu 公式リポジトリからインストールすれば発生しないんじゃないかと思います。

NGINX サーバでホストの設定が読み込まれないという不具合で困っている場合は、以下をお試しください。Let’s Encrypt のインストールも正常に完了します。

1. nginx -Tでホストの設定が表示されない事を確認

2. grep sites-enabled /etc/nginx/nginx.confで何も表示されない事を確認

3. /etc/nginx/nginx.confhttpブロックに/etc/nginx/sites-enabled/*;を書き加える (ハイライトした 32行目)


user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

4. systemctl restart nginxでサービスを再起動

Let’s Encrypt certbot によるホスト定義ファイルの変更ビフォー&アフター

用途によって色々変更や書き加えることがあるでしょうが、HTTP を HTTPS にしてくれて、インストールログを見る限り更新も自動でやってくれるみたいなので、なんか助かりますね。本番でやる場合は、certbot を実行する前に念のためオリジナルの定義ファイルをバックアップしておくのが良いと思います。

オリジナル:

server {
    listen 80;
    server_name web.peddals.com;

    location / {
        root /var/www/web.peddals.com;
        index index.html index.htm;
    }

    error_page 404 /404.html;
    location = /40x.html {
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    }
}

certbot による書き換え後:

server {
    server_name web.peddals.com;

    location / {
        root /var/www/web.peddals.com;
        index index.html;
    }

    error_page 404 /404.html;
    location = /40x.html {
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/web.peddals.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/web.peddals.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = web.peddals.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    server_name web.peddals.com;
    return 404; # managed by Certbot


}

そういえばこんなニュースがありましたね

Gigazine さんの ↓ の記事を読んでいたので、若干警戒 (?) していた NGINX ですが、今回ついにいじり初めました。

NGINXのコア開発者が親会社と決別、新たに「freenginx」という名前でフォーク版を作成開始 https://gigazine.net/news/20240215-freenginx

ネガティブな動きが無い限りは、当面 NGINX を使い続けてみようかと思います。

Image by Stable Diffusion (Mochi Diffusion)

ウェブで躍動するエンジン!を思い描いて、自動車なんかのエンジンぽいものを期待していたんですが、どちらかというとギアで構成された装置が多く生成されました。ま、こんなもんでしょ。

Date:
2025年1月2日 18:59:00

Model:
realisticVision-v51VAE_original_768x512_cn

Size:
768 x 512

Include in Image:
mechanical machine’s engine floating on world wide web

Exclude from Image:

Seed:
3347794983

Steps:
20

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & GPU

© Peddals.com