Run LLMs on macOS using llm-mlx and Apple’s MLX frameworkという記事で、mlxを使って、macOS上でllmを動かす方法が紹介されている。
mlxとはAppleが開発するApple Siliconのための機械学習フレームワーク。mlx-lmというllmをmlxで動作させるためのPythonライブラリも提供されている。
しばらく自身のMacBook Air上でllmを動かしていなかったので、自分のMacBookでmlxを利用したllmの実行を試してみることにした。
PCのスペック
MacBook Air M2 (2022)
- 16GB memory
- 512GB SSD
- Sequoia 15.3
LM Studioを使ってローカルでllmを実行
今回はLM Studioというオープンソースのデスクトップアプリを使ってllmを動かしてみる。
LM Studioは従来のllmのファイル形式であるggufだけでなく、mlxのファイル形式もサポートしている。(v0.3.4以降でmlxがサポートされた)
LM StudioのUIから、HuggingFaceが提供するmlx/ggufのモデルを検索し、ダウンロードして使うことができる。
Mistral 7Bをmlx/ggufで比較
16GBメモリのMacBook Airのため、巨大なモデルは動かすことはできない。今回はMistral 7B Instruct v0.3
の4GB程度のモデルファイルでmlx/ggufそれぞれの形式を比較してみる。
入力プロンプトは 「ツリーを巡回するPythonコードを簡潔に説明して」
TL;DR
先に結論だが、mlxとggufとで大差はなさそうだった。
- Modelサイズやメモリ使用量はmlxの方が少し小さい (-0.3GBほど)
- Token Speedはmlxの方がわずかに速い(+3tok/sec)
- 回答内容はどちらも良さそう
- どちらもちゃんとコードを生成できている
- ggufの方が回答内容が少し長いが、mlxの方が回答内容が簡潔である
- 日本語も漢字が中国語っぽい部分はあるが、どちらも十分理解できる内容である
あくまで今回の比較は、4GB程度のモデルでの比較であるため、より巨大なモデルでの比較を行うと、性能差がより顕著に出ると思われる。
次の表により詳細な結果をまとめた:
mlx | gguf | |
---|---|---|
Model Name | Mistral 7B Instruct v0.3 4bit | Mistral 7B Instruct v0.3 Q4_K_M |
File Size | 4.08 GB | 4.37 GB |
Memory Usage | 4.28 GB | 4.71 GB |
Token Speed | 20.81 tok/sec | 17.60 tok/sec |
Time to First Token | 0.63s | 0.52s |
Output Tokens | 531 | 639 |
実際の回答:
mlxの回答
Pythonでツリー(Tree)を輪遡させる(Recursively traverse)シンプルなコードは次のようになります:
def traverse(node, func): if node: # 現在のノードを処理 func(node) # 子ノード(左側と右側)について、同じ処理を繰り返してください traverse(node.left, func) traverse(node.right, func) # 木構造で表現されたデータを持ち、leftとrightに子ノードがあるnodeオブジェクト root = MyNode(1) # 例: Root Node root.left = MyNode(2) root.right = MyNode(3) root.left.left = MyNode(4) root.left.right = MyNode(5) # 木を輪遡させる処理 traverse(root, print) # 上記の構造に対して、各ノードを出力
上記のコードでは、
traverse()
関数に木構造を持ち、func
を呼び出すようになっています。このfunc
関数は処理を行うたびに、渡されるノードオブジェクトに対して何らかの処理を行います。一度呼び出されるたびに、現在のノードを処理してから子ノード(左側と右側)について、再帰的にこの処理を繰り返します。上記の例では、
print()
関数をfunc
として与えています。これにより、rootノードから、子ノードを辿ってrootノードから根拠した木構造の全てのノードが出力されます。
ggufの回答
以下は、Pythonで木(Tree)を深さ優先探索(Depth-First Search, DFS)または幅優先探索(Breadth-First Search, BFS)する方法の簡単な例です。
これらの例は、Python標準ライブラリに含まれる
collections.deque
を使用して、ノードを追跡したり、前へ戻れたりすることが可能です。# 深さ優先探索 (Depth-First Search, DFS) class Node: def __init__(self, val): self.val = val self.children = [] def dfs(node): print(node.val) for child in node.children: dfs(child) root = Node("root") root.children.append(Node("A")) root.children.append(Node("B")) root.children[0].children.append(Node("D")) root.children[1].children.append(Node("E")) root.children[1].children.append(Node("F")) root.children[0].children.append(Node("C")) root.children[2].children.append(Node("G")) dfs(root) # root, A, D, C, B, E, F, G の順に出力されます。 # 幅優先探索 (Breadth-First Search, BFS) from collections import deque def bfs(node): q = deque() q.append(node) while q: current_node = q.popleft() print(current_node.val) for child in current_node.children: q.append(child) root_bfs = Node("root") # ノードの設定は同じですが、DFSと異なり次第に子ノードを追加します。 root_bfs.children.append(Node("A")) root_bfs.children.append(Node("B")) root_bfs.children[0].children.append(Node("D")) root_bfs.children[1].children.append(Node("E")) root_bfs.children[1].children.append(Node("F")) root_bfs.children[0].children.append(Node("C")) root_bfs.children.append(Node("G")) bfs(root_bfs) # root, A, B, C, D, E, F, G の順に出力されます。
おまけ: CLIで実行する
冒頭の記事で紹介されているが、CLIツールLLM
でもmlxを使ってllmを動かすことができる。
まずuvを使ってLLM
をインストールする:
uv tool install llm --python 3.12
ここで--python 3.12
は、内部で利用されているsentencepiece
のwheelがpython3.12までしか提供されていないため指定している。
プラグインであるllm-mlx
をインストールする:
llm install llm-mlx
mlxモデルファイルは、CLIからダウンロードすることもできるが、せっかくなのでLM Studioでダウンロードしたファイルを使いまわしたい。
~/.lmstudio/models/mlx-community
にダウンロードしたmlxモデルが保存されている:
❯ ls -l ~/.lmstudio/models/mlx-community
total 0
drwxr-xr-x@ 9 kh03 staff 288 Feb 18 21:31 Mistral-7B-Instruct-v0.3-4bit
このモデルファイルをLLM
で使うためには、モデル定義を記述するjsonファイルを編集する必要がある。
以下でjsonを開ける:
❯ open "$(llm mlx models-file)"
jsonに以下のキーを追加する:
{
"~/.lmstudio/models/mlx-community/Mistral-7B-Instruct-v0.3-4bit": {
"aliases": ["lmstudio"]
}
}
キー自体は、LM Studioでダウンロードしたモデルのパスを指定し、aliases
にはCLIで利用するモデルの名前を自由に指定する。
(ここのモデル登録は少々面倒なので、コマンドを追加できないかIssue作成した)
あとはaliasを指定して、LLM
を実行する:
❯ llm -m lmstudio-mistral hi
まとめ
より大きなモデルをローカルで利用したい場合はMacBook Proの上位モデルが適しているかもしれないが、今回のような小規模なモデルであれば、MacBook Air M2でも十分動かすことができた。
何よりもローカルで動かせば無料だし、外部のサーバーに依存しないので、セキュリティ的にも安心である。色々と試すにはかなりお勧めできる。
またCLIを利用すれば、ほかのプログラムとPipeを通じて組み合わせることもできる。