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

MLX-LM で QwQ-32B-Preview を API ストリーミング (Ollama より速い)

Ollama のこちらの issue に、MLX バックエンドのサポートをおねだりするコメントがたくさんあり、中には llama.cpp (GGUF) より 20~40% も高速という書き込みがあったのでお気に入りの QwQ-32B-Preview MLX 版を試してみました。OpenAI の o1 の様に試行錯誤を繰り返して回答の精度を高めるオープンなモデルです。結論から言うと、確かに少し速いです。該当のコメントを書き込んだ人は M3 ということなので、M4 を含んだ新しめチップ搭載 Mac ならもっと違いが実感できるのかもしれません。せっかくなのでやり方を残しておきます。

ところで、↓ これって Ollama 公式の X ポストなんですかね? Ollama で正式に MLX バックエンドがサポートされる匂わせとも取られています。

MLX って?

ざっくり言うと、Apple 社純正の、Apple シリコン用機械学習フレームワークです。GPU だけではなく、CPU も使えるみたいです。性能は、100% というわけではなさそうなんですが、いろいろな実験をされた方々の記事を見てみると PyTorch で MPS を使用したときより速いというケースもあるらしいです。

MLX 公式 GitHub: https://ml-explore.github.io/mlx/build/html/index.html

詳細はブラウザの翻訳機能でも使って読んでください。Safari ならアドレスバーの右にあるこのアイコンで日本語に翻訳できます:

というわけで、「MLX 版 LLM」なんて呼んでいるのは、上記 MLX で動くように変換された、オープンな LLM のことです。

MLX-LM とは

MLX で動くように変換された LLM の実行環境です。言語モデルのみ対応しています。実行の他、Safetensor 形式 (HuggingFace にあるオリジナルのモデルはだいたいコレですね) を MLX 形式に変換したり、API サーバを実行する機能もあります。今回の記事では、API サーバとして利用する方法を紹介しています。

MLX-LM 公式 GitHub: https://github.com/ml-explore/mlx-examples/blob/main/llms/README.md

似たような実行環境に MLX-VLM というのもあり、こちらは Pixtral や Qwen2-VL 等のビジョンモデルに対応しています。

MLX-VLM 公式 GitHub: https://github.com/Blaizzy/mlx-vlm

上記二つの MLX-LM と MLX-VLM 両方の API サーバとして動作する FastMLX という Python パッケージもあり機能的には魅力的なのですが、ビジョンモデルは画像の URL かパスしか受け付けなかったり (Dify では使えない)、テキストのストリーミングがうまくいかず例外が出たりして結構工夫しないと使えなさそうなので、これまでのところくじけてます。興味がある方はどうぞ。

FastMLX 公式 GitHub: https://github.com/arcee-ai/fastmlx

LM Studio でも使えます

LM Studio は MLX モデルが使えるので、別に Dify 使わない・使っていないという方はこれ以上読み進めなくて大丈夫です。また、Dify には OpenAI API コンパチのモデルプロバイダとして LM Studio を登録して使うこともできますが、LLM からのレスポンスがなめらかにストリーミングされません。特に日本語の部分は結構な量がまとまって表示されるケースが多いです。なので、Dify から API で MLX の LLM を使うなら、MLX-ML のサーバ機能を利用するのが良いかと思います。

以下、内容はビジョンモデルですが、LM Studio の API を Dify から使う方法を別の記事にしてあるので参考にしてみてください。

MLX-LM で API サーバを立てる

インストール

MLX-LM を利用するには、まず仮想環境に MLX-LM をインストールしてください。今回使ったバージョンは、最新の0.20.4でした。

pip install mlx-lm

一度サーバを立てる

サーバを立てるにはmlx_lm.serverコマンドを使用します (実コマンドはインストール時のハイフンと違いアンダースコアなので注意)。Dify (や他の API クライアント) が別のホストで動いているとか、他のサーバがポートを使用している等という場合は、下の例のようにオプションを指定してあげます。ボクは Dify が別の Mac で動いていて、テキスト読み上げ (text-to-speech) サーバが動いていたりするので、それぞれを指定しています。オプションの詳細はmlx_lm --helpを見てください。--log-levelは付けなくても問題ありません。

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

こんな表示が出れば動いているはずです。

% mlx_lm.server --host 0.0.0.0 --port 8585 --log-level INFO
/Users/handsome/Documents/Python/FastMLX/.venv/lib/python3.11/site-packages/mlx_lm/server.py:682: UserWarning: mlx_lm.server is not recommended for production as it only implements basic security checks.
  warnings.warn(
2024-12-15 21:33:25,338 - INFO - Starting httpd at 0.0.0.0 on port 8585...

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

ボクの場合 32GB の RAM で動いてほしいので、4bit 量子化されたもの (18.44GB) を選んでいます。

HuggingFace: https://huggingface.co/mlx-community/QwQ-32B-Preview-4bit

MLX-LM サーバが動いている状態で別のターミナルウィンドウを開き、以下の様な簡単なスクリプト書いて保存し、Python で実行してモデルをダウンロードします。

import requests

url = "http://localhost:8585/v1/models"
params = {
    "model_name": "mlx-community/QwQ-32B-Preview-4bit",
}

response = requests.post(url, params=params)
print(response.json())
python add_models.py

ダウンロードが完了したら Ctrl + C でサーバを一度終了します。あ、ちなみにこの方法でダウンロードしたモデルは、LM Studio からも読めますので、どちらも試す場合はコマンドで入れる方法が容量削減になります (ただしフォルダ名は人にきびしい)。

モデルを指定して API サーバを立ち上げる

モデルの保存場所は~/.cache/huggingface/hub/の中で、今回の例ではmodels--mlx-community--QwQ-32B-Preview-4bitというフォルダになります。サーバを立ち上げるコマンドに渡すパスはさらに深く、snapshotの中、config.jsonファイルが含まれるフォルダとなります。そちらを指定して API サーバを立ち上げるコマンドはこんな感じです:

mlx_lm.server --host 0.0.0.0 --port 8585 --model /Users/handsome/.cache/huggingface/hub/models--mlx-community--QwQ-32B-Preview-4bit/snapshots/e3bdc9322cb82a5f92c7277953f30764e8897f85

無事にサーバが上がると、ブラウザからダウンロード済みモデルが確認できます: http://localhost:8585/v1/models

{"object": "list", "data": [{"id": "mlx-community/QwQ-32B-Preview-4bit", "object": "model", "created": 1734266953}, {"id": "mlx-community/Qwen2-VL-7B-Instruct-8bit", "object": "model", "created": 1734266953}]}

Dify に登録する

OpenAI-API コンパチブルのモデルとして登録

Dify へは、OpenAI-API-compatible の LLM モデルとして登録します。モデル名は上に度々登場しているもので、URL にはポートと /v1 が必要、Delimiter には適当に \n\n を入れるくらいで大丈夫だと思います。参考にスクリーンショットを貼っておきます。

Chatbot を作る

上記で登録したモデルを指定し、Max Tokens は 4096 あたりにします。この値であれば 32GB RAM でも 100% GPU で動きます。QwQ からの回答に中国語が混じるのを防ぐには、下の System プロンプトを使ってみてください。100% ではありませんが、効果はあります。

中国語は絶対に使わないでください。日本語の質問は常に日本語で答えてください。
While answering, never ever use Chinese. Always answer in Japanese or English based on language used.
Ollama のモデルに比べて、OpenAI API コンパチは指定できるパラメータ数が少ない

さて、これで準備が整ったので、公開して実際に試してみましょう。

Dify では正確な数字での比較ができないが、体感は速い

さてこれで準備が整ったので、GGUF (ollama pull qwq:32b-preview-q4_K_M) と MLX を使った同じ条件のチャットボットを作り、同じ質問をいくつも投げて違いを見てみました。見た目は確かに MLX 版の方が文字の出力が速いです。ただ、Dify アプリのダッシュボードである「監視」での比較だと GGUF が 6トークン/秒くらいに対し、MLX が 18トークン/秒と 3倍速い結果になっていますが、実際にそこまでの違いはありません。個別のチャットのステータスが見れる「ログ&アナウンス」で確認できる経過時間とトークンの合計から算出すると、GGUF は大体 12トークン/秒くらいで、MLX は 35トークン/秒くらいとなるのですが、実際に出力されている文字数をざっと見比べてもそこまでの違いはありません。むしろ明らかに Ollama から多くの文字を受け取っているのに、MLX-LM から受け取ったトークン数の方が多くカウントされています。トークンの算出方法が違うのかもしれません。今回試しませんでしたが、英語でやると正確に調べられるかもしれません。また、単純に GGUF (llama.cpp) vs MLX-LM のガチンコ勝負をするなら、スクリプトを書いて比較するのが良いと思います。

追記: 本ブログの英訳記事を書く際に英語でテストを実施したところ、MLX の方が 30-50% 速い結果となりました。使ったプロンプトも載せてあります。

MLX-LM に乗り換えられるか?

QwQ や特定のモデル固定で構わないなら MLX-LM はアリだと思います。Mac の電源を切る習慣がある方は、別のブログ記事に書いたスクリプトエディタでアプリケーションを作る方法でログイン時に実行させれば勝手にモデルが使えるようにしてくれますし。でも、お手軽さと使いやすさでは Ollama の方が完全に上なので、複数のモデルをとっかえひっかえ使う方には向きません。上で触れた FastMLX はクライアント側からモデルの入れ替えができるようなので、本気で移行を考えるなら選択肢になり得るかと思います。ただ、Ollama 公式 X らしきポストを真に受けると、MLX 対応しそうな感じもあるので、どちらかと言えばそれ待ちですね。いずれにせよ、今回の GGUF vs MLX の趣旨とは外れますが、個人的に QwQ の出力速度はチャットベースであれば十分です。用途に応じて試してみてください。

Image by Stable Diffusion

「リンゴの上で走るロボット」をお願いしたのですが、big apple だと NYC になり、huge apple とすると、そんなものないとガン無視されました。というわけでこんな感じで手を打ちました。日本人の持つ「かわいいロボット」という感覚は西洋にはなさそうです

Date:
2024年12月16日 0:38:20

Model:
realisticVision-v51VAE_original_768x512_cn

Size:
768 x 512

Include in Image:
fancy illustration, comic style, smart robot running on a huge apple

Exclude from Image:

Seed:
2791567837

Steps:
26

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & GPU

Dify で Mac 専用 MLX 版 Qwen2-VL を使う (たぶん、LM Studio のバグ技)

前回 Pixtral (Mistral 社のビジョンモデル) の日本語コミュニケーション能力と画像認識の性能の高さに感動しつつも、画像の中の日本語は読めないことを知り傷心したボク。Ollama のバージョンアップで正式に利用できるようになった Meta 社の画像認識モデルである Llama3.2 Vision 11B こそが本命であるはずだ!と早速試したんですが、あまりのぱっとしなさに記事にすることもありませんでした。ローカルで「使える」オープンモデル・ウェイトのビジョンモデルの登場はまだ先なのか、または画像内の日本語が認識できる LLM なんて 32GB RAM の Mac には一生やってこないのか、っていうか OCR したいなら普通に macOS 標準のプレビューでいいじゃんか、ということなのか。なんて思っていたら、普通にありました。とっくに出てました。しかもボクが最近何かとメインで使っている Alibaba 社の Qwen ファミリーに。そう、Qwen2-VL です。

パラメータ数は 2B、7B、72B
はい、日本語正式サポートしてるぅ (上のサイトを Safari で日本語に翻訳してもらった一部のキャプチャ)
画像内の日本語認識が不要なら、Pixtral がオススメ

簡単に使うなら、やはり LM Studio 単体で

LM Studio の虫眼鏡アイコンで “qwen2-vl” と打ち込み、横のチェックボックスで MLX を選べば mlx-community にて変換された MLX 版の Qwen2-VL がずらずら出てきます。一番大きい 7B-Instruct-bf16 でも 16.60 GB なので、32GB RAM の Mac でも問題無く動きそうですが、そこはビジョンモデル、テキストオンリーのモデルと違ってメモリを喰います。BF16 はたまに CPU を使ったり頻繁にハルシネーションをおこしたりするヤンチャ坊主なので、オススメは 7B-Instruct-8bit です。というわけで、LM Studio で動けばそれでヨシ、という方はこれ以降読まなくて大丈夫です。ステキなビジョンモデルライフをエンジョイください。

bf16 のスクリーンショットですが、オススメは Qwen2-VL-7B-Instruct-8bit

Ollama では動かない

さて、普段はローカル LLM のモデルプロバイダとして使っている Ollama をなぜ使わないのかということを説明します。Ollama はバックエンドに llama.cpp を使っていますが、llama.cpp は (今のところ) ビジョンモデルに対応していません。Llama3.2 Vision は、Ollama の開発者が llama.cpp に依存せずに 頑張ってどうにか動くようにした (?) らしいです。が、Llama3.2 Vision 11B のパフォーマンスが 32GB RAM の M2 Max ではイマイチでして、ボクはすぐに使うのやめちゃいました。英語だけで OK の方は良いのかもしれないですね。

Safetensors は MPS で動かすこともできる

↓の Zenn に投稿された金のニワトリさんの記事に、MPS でサンプルコードを動かす方法が紹介されています。Llama3.2 Vision にガッカリしていたときに試したので、えらく感動しました。Mac の GPU で素のビジョンモデル Qwen2-VL が動いていたので。

https://zenn.dev/robustonian/articles/qwen2_vl_mac

さて、API で利用するにはどうするか?

macOS で Qwen2-VL を動かすには、元の Safetensors 版か、MXL 版のどちらか、ということがわかったわけですが、やっぱり Dify から使いたくなってしまいます (よね?)。本家の GitHub にあるワンライナーの API サーバ起動方法は vllm という Python のライブラリを使用しており、残念ながら Mac 未対応です。他の方法を探ると、言語モデルの MLX 用なら割といろんな API サーバライブラリがあるのですが、ビジョンモデルに対応したものとなるとかなり絞られました。さらにそこから実際に Qwen2-VL を使おうとすると、そもそも Dify に登録できなかったり、無理矢理登録できても例外しか発生しないという悲しい状況が 2週間ほど続きました。

同じように MLX のビジョンモデルである Pixtral は LM Studio が API サーバとなり Dify から使えるのに、Qwen2-VL を OpenAI コンパチモデルとして登録しようとすると、最初の ping (モデルが本当につながるかどうかのテスト) に画像が含まれないために Qwen2-VL がエラーを返してきて登録ができません。あらゆる方法を自分なりにいろいろと試しては失敗し、を繰り返していたある日、突然でたらめな組み合わせで動き始めました。詳細は以下に続きます。

成功した構成とバージョン

LM Studio または Dify の新しいバージョンではバグが潰されてこの方法が使えなくなるか、はたまた正式にサポートされて普通に動くようになるかわかりませんが、とりあえず ↓ の構成&バージョンで動作が確認できています。

  • Dify: 0.11.2 (0.11.1 でも動いた実績あり)
  • LM Studio: 0.3.5 (Build 2)
  • Pixtral: mlx-community/pixtral-12b-8bit
  • Qwen2-VL-7B: mlx-community/Qwen2-VL-7B-Instruct-8bit (FastMXL でダウンロードしたモデル)
  • Qwen2-VL-2B: mlx-community/Qwen2-VL-2B-Instruct-4bit (LM Studio でダウンロードしたモデル)
  • macOS: Sequoia 15.1.1 (上記アプリが動けば、多分何でも大丈夫)

Dify から LM Studio の Qwen2-VL を使う方法

やり方は、ほぼ前回の記事と同じです。異なっているのは、LM Studio には Qwen2-VL をロードしておくだけです。Dify のアプリで使うモデルは mlx-community/pixtral-12b-4bit のままで構いません。というか、上記バージョンでは Dify に LM Studio 上の Qwen2-VL を登録できないので、Dify は Pixtral を呼び出しているつもり、LM Studio は読み込み済みの Qwen2-VL を使って推論をする、という状況になります。本来 LM Studio は指定されたモデルを読み込むはずなので、バグだと思いますが、これで動きます。

実行サンプル

いくつか参考となりそうな画像を貼っておきます。

テキストの日本語が怪しいことは多いです
提灯の文字を読んで「雷門」と回答したわけでは無いらしい。っていうか「唐傘 (提灯)」って
文字だけなら手書きの日本語を読めます
結構がんばってる。Markdown の罠でインデックス番号が各行に振られているところと、写真一番下のテキストが斜めになってしまっているところは読めていないのが残念。ラウンドを繰り返すか、プロンプトを工夫すれば精度はあがるかも

感想

性能的には、画像認識の精度や知識は Llama3.2 Vision モデルよりは良いけど、Pixtral ほどじゃ無いです。日本語 OCR メインの用途なら macOS のクイックルックやプレビューの方が相当優れています。というわけでやはり、期待したほどでは無いな、というのが正直な感想です。大量の RAM を積んでいる方が 72B のモデルを動かすと、また違う感想になるかもしれません。ビジョンモデルの使いどころも定まっていないボクにとっては、「普通には動かせないものを動かす」というハッカー的欲求を満たせたのでとりあえず満足、という感じですかね。

ところで最近のローカル LLM はほとんど Qwen2.5 シリーズしか使ってません。日本語チャットは qwen2.5:14b-instruct-q8_0 (15GB) もしくは qwen2.5:32b-instruct-q4_K_M (19GB)、コーディングは qwen2.5-coder:14b-instruct-q8_0 (15GB) もしくは qwen2.5-coder:32b-instruct-q4_K_M (19GB) です。速度や大きなコンテキスト長を扱いたいとき (注) は 15GB サイズのモデル、精度が欲しいときは 19GB のモデルを使っています。欧米のモデルよりも日本語が得意というのもありますが、各種リーダーボード (日本語オープン、日本語総合コーディング) でも Qwen シリーズは高評価ですので、未体験の方はゼヒ。

ついでにですが、最近リリースされた推論 (reasoning) 重視モデルの QwQ-31B-Preview も普通に Ollama でダウンロードして使えます。32GB RAM の M2 Max での生成速度は速くない (7.5 TPS 程) ですが、性能というか、考え方の方向性がこれまでとかなり違い、ヤバいです。次元が違う感じ。

(注: 大きなサイズのモデルだとコンテキスト長を小さくしないと遅くなります。詳しくはこちらの記事をご覧ください:)

最後に、ビジョンモデルの活用法を一つ思いつきました。大量の素材用写真が無造作にフォルダに入っているみたいな状況があれば、それぞれの写真の特徴を macOS の「情報を見る」のコメント欄に書き込んでくれるスクリプトなんてよさそうですね。Spotlight で検索できるようになるし。Qwen2-VL はビデオの読み込みもできるらしいので、整理に困っている大量の画像・映像を持っている人は良いかもですね。

Image by Stable Diffusion (Mochi Diffusion)

Pixtral の記事のトップ絵に対抗した内容で、登場人物を alibaba にし、場所を Alibaba Cloud 社があるらしい中国の Yu Hang にしてみました。きっと本当はこんな町並みでは無いのでしょうが、ボクの想像と概ね近いものを選びました。

Date:
2024年11月27日 23:59:11

Model:
realisticVision-v51VAE_original_768x512_cn

Size:
768 x 512

Include in Image:
masterpiece, intricate, realistic, sharp focus, photograph, alibaba walking in Yu Hang, china

Exclude from Image:

Seed:
1426778725

Steps:
20

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & GPU

Mac のローカルオンリー環境で、画像認識 AI の Pixtral 12B MLX 版を使う (LM Studio 編)

フランスの生成 AI 企業のトップである Mistral 社が、画像認識にも対応したマルチモーダル モデルの Pixtral 12B を Apache 2.0 ライセンスでリリースしています。画像を読み込ませると、何が映っているかを答えてくれるタイプの AI ですね。こちらを、ボク得意の Mac ローカルオンリー環境で実行した方法を共有します。今回使ったのは、LM Studio と Dify です。Pixtral を実行するだけなら LM Studio の 0.3.5 以上で OK ですが、Dify から API 経由で LM Studio を利用してみたので、後半にはその設定方法を紹介してます。

環境について

Mac

ボクの M2 Max Mac Studio には 32GB しか RAM が積まれていないので、Dify だけは別の M1 Mac Mini で動かしています。これから Mac (M4 搭載 Mac Mini とか MacBook Pro とかいいですな〜) を買う方でローカル LLM も試したいと考えている方は、頑張って 64GB 以上の RAM (ユニファイドメモリ) を搭載した機種を買いましょう。それより少ない RAM 容量で LLM を楽しむのはいろんな工夫や妥協が必要で苦労します。本ブログにはそんな切ない記事があふれています。

RAM が少ないと、こんな感じのやりくりが必要になります

LM Studio

v0.3.0 で、インターフェイス含め大幅なアップデートがありました (v0.2 シリーズを使っている場合は、新規でインストールが必要)。その後も着々とバージョンアップが進められ、API 経由で他のアプリから LLM を利用できるようになり、Mac の GPU 用に最適化された MLX バージョンの LLM が動くようになり、v.0.3.5 で Pixtral に対応しました。また、同バージョンでは、メモリ喰い気味の GUI を閉じてサーバだけ動かす Headless mode にも対応したということで興奮したのですが、実際は LLM (pixtral-12B@4bit) がロードされると LM Studio Helper が 12~20GB ほどメモリを使用し続けるので、現状はまだ Ollama のような使い勝手は望めません。残念。

その他 LM Studio v0.3.5 の詳細はこちらをどうぞ: https://lmstudio.ai/blog/lmstudio-v0.3.5

Ollama

2024年 11月 4日現在、Ollama では Pixtral がサポートされていません。Llama 3.2 vision が動くらしい v0.4.0 は pre-release されているので、正式リリースや Pixtral のサポートも時間の問題かもしれません。

Ollama v0.4.0 に関してはこちらをどうぞ: https://github.com/ollama/ollama/releases/tag/v0.4.0-rc6

Dify

これから始める方は最新版になっちゃうんでしょうが、最近、リリース直後は多くのバグで悩むケースが散見されています。ボクはまだ v0.9.1-fix1 を使用しているため、v0.10 以降のファイル添付方法が変わったバージョンでは見た目や使い方が違う可能性があります。Dicord での情報収集が良いと思います。

LM Studio で MLX 版 Pixtral を動かす

LM Studio v0.3.5 以上のインストールは完了してる前提で

無料の Mac 用アプリがここからダウンロードできます。

Pixtral 12B 4-bit をインストール

32GB RAM の Mac では 4-bit (量子化) 版をオススメします。LM Studio 込みでオンメモリで動きます。もっと RAM を積んでいる方は、お好みで 8bit や bf16 版をどうぞ。

虫眼鏡アイコンの Discover → 検索窓に「pixtral」と入力 → MLX だけにチェックを入れる (GGUF を外す) → モデル名やファイルサイズを確認 → Download ボタンクリック

ボクのインターネット環境の問題かわかりませんが、ダウンロード中何度もタイムアウトしていました。左下のボタンでたまに確認し、下のような Timed-out が出ていたら再読み込みボタンをクリックしましょう。

他のエラーで止まることもありますが、再読込で進められます

Chat で試す

Dify で使う必要が無い方 (え?みんなそう?) は、そのまま LM Studio で Pixtral が楽しめます。

吹き出しアイコンの Chat → Select a model to load ドロップダウンから、ダウンロードした Pixtral を選択 → Load Model ボタンクリック

例として、以前没にしたトップ絵をドラッグアンドドロップして「この画像について教えてください」としたところが以下画像です。Pixtral は Mistral Nemo 12B をベースにしていると言うことで、上手な日本語でやりとりができます。ちなみに、Nejumi さんのリーダーボードでは、Mistral Nemo 12B は elyza/Llama-3-ELYZA-JP-8B より日本語性能が上らしいです。確かに。

LM Studio への日本語入力には難あり。エンターは確定+送信になり、最後の確定部分がチャット窓に残ります

他にもいくつか AI による生成画像を読み込ませてみたところ、大体 30 TPS (トークン/秒) で、正しく詳細な回答が得られました。個人的に画像認識 AI の正しい使い道は見つけられていないのですが、想像以上の性能です。

というわけで、Mac のローカル環境で画像認識マルチモーダル LLM を試したいだけ、という方はお疲れ様でした。いろいろとお試しください。

以降は Dify から API で LM Studio のモデルを使う方法になります。

LM Studio API サーバ設定

基本

Developer アイコンから設定します。全ての Off/On スイッチが表示できていないと思うので、まずは Developer Logs の境界線をつかんで下に押し広げましょう。Start ボタンで API サーバが利用できますが、確認しておいた方がよさそうな設定は以下となります。

  • Server Port: デフォルトで 1234
  • Server on Local Network: 他の Mac から API アクセスするなら、On
  • Just-in-Time Model Loading: Dify からモデルを選択したいなら、On
これで Start をクリックすれば、API サーバは起動します
LAN に公開した場合はこのポップアップが出るので、許可します

ヘッドレスモード (GUI 無し API サーバのみ)

右下のギアアイコンまたはコマンド+カンマで App Settings を開き、Local LLM Service (headless) を有効にします。これで、GUI を Quit しても API サーバは動き続けます。

GUI を開いたり、サーバを停止したりするには、メニューバーのアイコンを開きます。メモリを解放するなら、Unload All Models または、Quit LM Studio を選びましょう。

Dify から LM Studio API サーバに接続できるようにする

モデルプロバイダーにある、OpenAI-API-compatible に登録します。

MLX 版の Tanuki 8B も登録済みの図

設定内容はこんな感じです:

  • Model Name: モデル名 (例: mix-community/pixtral-12b-4bit)
  • API Key: 無し
  • API endpoint URL: 例 http://192.168.1.10:1234/v1 とか http://localhost:1234/v1 (ポート番号/v1 を忘れずに)
  • Completion mode: Chat
  • Model context size 2048
  • Upper bound for max tokens: 2048
  • Function calling: Not Support
  • Streaming function calling: Support
  • Vision Support: Support
  • Delimiter for streaming results: \n\n (区切り文字が入力必須ですが、とりあえず改行二つのこれで動きます)

Dify で動く超シンプル画像認識アプリ

DSL ファイルを下に貼っておきますが、チャットボットの Chatflow を作り、機能で「画像アップロード」を有効にして、モデルとして Pixtral を選んでいるくらいの単純なものです。

DSL を開く

app:
description: 32GB RAM オンメモリで動作するビジョンモデル。Pixtral は MLX 版で、LM Studio で実行中。事前に LM Studio
サーバを実行しておくこと
icon: eyes
icon_background: ‘#D5F5F6’
mode: advanced-chat
name: Pixtral 12B 4Bit (MLX)
use_icon_as_answer_icon: true
kind: app
version: 0.1.2
workflow:
conversation_variables: []
environment_variables: []
features:
file_upload:
image:
enabled: true
number_limits: 3
transfer_methods:
– local_file
– remote_url
opening_statement: ”
retriever_resource:
enabled: false
sensitive_word_avoidance:
enabled: false
speech_to_text:
enabled: false
suggested_questions: []
suggested_questions_after_answer:
enabled: false
text_to_speech:
enabled: false
language: ”
voice: ”
graph:
edges:
– data:
isInIteration: false
sourceType: start
targetType: llm
id: 1730552831409-source-llm-target
source: ‘1730552831409’
sourceHandle: source
target: llm
targetHandle: target
type: custom
zIndex: 0
– data:
isInIteration: false
sourceType: llm
targetType: answer
id: llm-source-answer-target
source: llm
sourceHandle: source
target: answer
targetHandle: target
type: custom
zIndex: 0
nodes:
– data:
desc: ”
selected: false
title: 開始
type: start
variables: []
height: 54
id: ‘1730552831409’
position:
x: 30
y: 253.5
positionAbsolute:
x: 30
y: 253.5
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
– data:
context:
enabled: false
variable_selector: []
desc: ”
memory:
query_prompt_template: ‘{{#sys.query#}}’
role_prefix:
assistant: ”
user: ”
window:
enabled: false
size: 10
model:
completion_params:
temperature: 0.7
mode: chat
name: mlx-community/pixtral-12b-4bit
provider: openai_api_compatible
prompt_template:
– id: 9011766a-9f0d-45d2-87bb-8f2665ad80db
role: system
text: ”
selected: true
title: Vision
type: llm
variables: []
vision:
configs:
detail: high
enabled: true
height: 98
id: llm
position:
x: 334
y: 253.5
positionAbsolute:
x: 334
y: 253.5
selected: true
sourcePosition: right
targetPosition: left
type: custom
width: 244
– data:
answer: ‘{{#llm.text#}}’
desc: ”
selected: false
title: 回答
type: answer
variables: []
height: 107
id: answer
position:
x: 638
y: 253.5
positionAbsolute:
x: 638
y: 253.5
selected: false
sourcePosition: right
targetPosition: left
type: custom
width: 244
viewport:
x: 49.4306562198135
y: 352.30093613089844
zoom: 0.7237754523423506

アプリを公開するとチャットウィンドウに画像をドラッグアンドドロップできるので、画像と質問を入力すれば回答が返ってきます (初回の LLM のロードが行われる時はタイムアウトするかもしれません。少し待って再度コメントを投げると返事が来ます)。Dify から使うと若干スピードが落ち、何度か試した後確認すると、20 TPS 以下でした。実行時のメモリプレッシャーはこんな感じ ↓ です。モデルがロードされた後のメモリの使用量が、ロード前ほど下がらないことがわかると思います。この時で LM Studio Helper のメモリ使用量は 12GB ほどでした。

以前の記事で使った画像を読ませたところ ↓

追加質問の余地がないほどの回答

Pixtral さんは画像の中の日本語は読めない (日本語 OCR としては使えない)

チャットでは流ちょうな日本語でやりとりできるのですっかりその気になっていたのですが、画像の中の日本語は読めないみたいです。ベーマガのペーパー・アドベンチャーをスキャンして遊びやすくしようと思っていたのですが、そういう用途には使えないようです (注意: 雑誌のスキャンを無許可で公開すると著作権侵害となります)。

なんかそれっぽいことが書いてあるけど、実際見比べると全然違う

雷門が歌舞伎町に見えてしまえば、文字だってそう読めてしまいます。

大提灯を指摘すれば気づいてくれるかも、との期待は打ち砕かれました

かわいい手書き文字でもテスト。うーむ、日本語 OCR として使うのは諦めた方がよさそうです。

念のため英語とフラ語でも聞いてみました。

ちょっと!誰よ、ひとみって!
朝起きたら、質問の前にあいさつをしよう

面白かったのでいくつも画像を貼ってしまいましたが、日本語 OCR 能力は無いという結論に変更はありません。アルファベットなら手書きもまーまーイケそうなので残念ですが、このパラメータ数 (12B) ですし日本語に特化しているわけでもないわけですから、欲しがりすぎてはいけませんね。

(おまけ) Mac で OCR するなら、写真の文字をなぞってコピーするだけ

話はそれますが、Mac なら画像や写真の文字部分をなぞればほぼコピーできちゃいます。素のままでできるはずですが、うまくいかないときはプレビューで開いて、ツールからテキスト選択選べばほぼイケるかと。

ペーパー・アドベンチャーの写真の一部を選択した状態

全文載せると著作権侵害になるので、冒頭部分のみ (関係者の方からご指摘あれば削除いたします)。適当に iPhone で撮影した写真でもこの認識力。改行位置の的確さもほぼ OK。これが OS レベルで実装されているのですごい。すごいありがたい macOS。

1 あなたは今、この町に入るための道に立っている。
北→7 西→26
2 君は必死に怪物と戦ったがあと一歩というところで殺されてしまった。
GAME OVER
3
君は逃げきれず、つかまってしまった。
GAME OVER
4
5
ここは森の入口だ、入れ。→109小さな木の箱があった。開けると、ダイヤ,サファイヤ、ルビーがあった。】
つだけ持っていってもよい。
ダイヤ→90 サファイヤ→39
ルビー→53
6
怪物半魚人が現れた。水中なので逃げられない。戦え。
体力が9以上→28 9未満→2
7怪物ゾンビーが現れた。戦うか,逃げ
るかさめよ。
戦う→37
逃げる→61
8
ここは武器屋だ。きみは金貨を50枚持っている。なにか買え。
剣,金貨50枚→16号,金貨40枚→69

Apple Inteligence もまもなく使えるようですし、AI スタートアップで収益上げるって難しそうだな、と思いました。

Image by Stable Diffusion (Mochi Diffusion)

おフランスをパリジェンヌが歩いているおしゃれな画像を期待して描いてもらいました。

Date:
2024年11月3日 15:13:32

Model:
realisticVision-v51VAE_original_768x512_cn

Size:
768 x 512

Include in Image:
masterpiece, intricate, realistic, sharp focus, photograph, woman walking in Paris

Exclude from Image:

Seed:
2526772693

Steps:
23

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & GPU

© Peddals.com