外部に出すのがはばかられる情報を扱う場合や、費用をかけたくない場合、再現性が必要な場合、rate limitを気にせず大量ループ処理したい場合に、ローカルLLMは魅力的である。ここでは Ollama(オラーマ)というツールを使ってコマンドラインまたはPythonからLLMを使う方法を説明する。Ollamaは llama.cpp から派生したたいへん使いやすいツールである。
ここではMacを使う。OllamaサイトよりバイナリのZIPファイルをダウンロードし、展開してOllama.appを /Applications(あるいは適当な場所)に移動し、起動すると /Applications/Ollama.app/Contents/Resources/ollama へのシンボリックリンクが /usr/local/bin にできる。また、バックグラウンドのプロセスが起動し、メニューバーにOllamaアイコンが常駐する。利用はターミナルからollamaコマンドを使って行うか、後述するようにPythonなどを使ってバックグラウンドのプロセスのAPIを呼び出す。
まずは適当なモデルをダウンロードして動かす。ここではGoogleの Gemma 3(ジェンマ・スリー)を使ってみる。
OllamaのGemma 3のページを参考にして、デフォルトの4ビット量子化(Q4_K_M)された4サイズのGemma 3のうち適当なものを選ぶ。メモリ8GのMacなら4B、メモリ16GのMacなら12B、それ以上なら27Bを使えばよかろう(Bはbillionつまり10億パラメータ)。例えば12Bモデルなら
ollama run gemma3:12b
と打ち込むと、初回はモデルのダウンロードが始まり、それが終われば入力を受け付ける状態になるので、何か質問すれば、答えが返る。
モデルは ~/.ollama
に入る。ドライブの容量が足りなければ、あとで外部ドライブにモデルを移動してシンボリックリンクを張ってもよい。
私はメモリ128GのMac Studioを買ったので、4ビットでなくても8ビットでも、おそらく16ビットでも大丈夫そうだが、8ビットあれば十分だ。ドロップダウンメニューの最後の「View all」から探すと、8ビット量子化の gemma3:27b-it-q8_0 が見つかる。これを実行してみよう。
ollama run gemma3:27b-it-q8_0
モデルの詳細は /show info
で調べられる:
>>> /show info Model architecture gemma3 parameters 27.4B context length 131072 embedding length 5376 quantization Q8_0 Capabilities completion vision Parameters top_p 0.95 stop "<end_of_turn>" temperature 1 top_k 64 License Gemma Terms of Use Last modified: February 21, 2024
8ビット27Bなら27Gバイト+αのメモリを使う。+αの量はコンテキスト長(num_ctx
)にも依存する。Gemma 3は128Kまで対応する(1Bモデルは32Kまで)。メモリ・CPUの使用率はMac標準のアクティビティモニタで表示できるが、GPUの使用率も含めてグラフィカルに表示できる mactop は便利である。
ちなみに、MacのメモリでGPUから使えるのはデフォルトでは3/4(64G以上のMac)または2/3(64G未満)に設定されているようだ(macOS でローカル LLM を使うときの VRAM 最適化設定参照)。例えば128GのMacなら sudo sysctl iogpu.wired_limit_mb=122880
と打ち込むと120Gまで使えるようになる。このあたりの事情は mlx.core.set_wired_limit の説明にも書かれている。
Ollamaは上記のようにコマンドラインで使うのが簡単だが、Pythonで使うこともできる。あらかじめ ollama-python というパッケージ pip install ollama
でインストールしておく。
簡単なプログラム例(温度0でランダムな文字列を出力させ、比較する。モデル名は置き換えてください):
from ollama import chat def ai(prompt): response = chat( model="hf.co/unsloth/gemma-3-27b-it-GGUF:Q8_0", messages=[{ 'role': 'user', 'content': prompt }], options={ "temperature": 0, "num_ctx": 512 } ) return response['message']['content'] ans1 = ai("Please output as many random tokens as possible.") ans2 = ai("Please output as many random tokens as possible.") print(ans1 == ans2)
まったく同じである(→ ランダムな文字列をLLMに出力させる)。
簡単なプログラム例(ファイルの各行に収めた文章を分類する):
from ollama import chat def ai(prompt): response = chat( model="hf.co/unsloth/gemma-3-27b-it-GGUF:Q8_0", messages=[{ 'role': 'user', 'content': prompt }], options={ "temperature": 0, "num_ctx": 2048 } ) return response['message']['content'] with open("list.txt") as f: lines = f.readlines() for line in lines: prompt = f"次の文章を○○○と×××に分類して、○○○なら「A」、×××なら「B」と答えてください。\n\n{line}" print(ai(prompt).strip())
このように、多数の文章を分類するような場合は、一つのプロンプトにまとめず、ループにする。
より高度なプログラム例(ストリーミング使用、会話を続ける):
from ollama import chat class Ollama: def __init__(self, model="hf.co/unsloth/gemma-3-27b-it-GGUF:Q8_0", messages=None, temperature=1, num_ctx=131072): self.model = model self.messages = messages or [] self.temperature = temperature self.num_ctx = num_ctx def chat(self, prompt, num_ctx=None, temperature=None): if len(self.messages) > 0 and self.messages[-1]["role"] == "user": self.messages.pop() self.messages.append({"role": "user", "content": prompt.strip()}) temperature = self.temperature if temperature is None else temperature num_ctx = self.num_ctx if num_ctx is None else num_ctx stream = chat( model=self.model, messages=self.messages, stream=True, options={ "temperature": temperature, "num_ctx": num_ctx } ) ans = "" for chunk in stream: content = chunk['message']['content'] print(content, end="") ans += content self.messages.append({"role": "assistant", "content": ans}) def get_messages(self): return self.messages ai = Ollama(temperature=1) ai.chat(''' 最初のプロンプト... ''') ai.chat(''' 次のプロンプト... ''')
コンテクスト長のデフォルトは環境変数でも設定できる。環境変数を変えたなら、Ollamaプロセスをいったん終了して、シェルから再起動(Macなら open -a Ollama
)する。
export OLLAMA_CONTEXT_LENGTH=131072