学習メモ — ローカルLLM(Ollama)¶
lectures/local_models/ の写経で得た理解を1枚に圧縮したメモ。混乱はほぼ「用語のすれ違い」と「どこまでがローカル固有か」の取り違えで起きるので、後半の用語集を最初に押さえると速い。
1行で言うと¶
ローカルLLM = 自分のマシンで立てた「OpenAI 互換の HTTP サーバ」。 クラウド API との違いは宛先(localhost:11434 か api.openai.com か)と課金の有無だけで、コードの形は同じ。base_url を差し替えればモデルは交換可能な部品になる。
モデルをローカルに「持つ」とは(直感)¶
最初にいちばん詰まるのがここ。「モデル」と聞くと頭脳やアプリのような“動いている何か”を想像しがちだが、ローカルに持つモデルの実体は、ただのデカいファイル1個。
モデルをローカルに持つ = 映画ファイルを1個ダウンロードして HDD に置くのと同じ。 再生する代わりに「質問を投げると答えが返る巨大な計算表」が手に入る。
- モデル = 学習で決まった数字(重み, weights)の塊。
llama3.2:3bの3bは「30億個の数字」の意味。数字1個を約0.5〜2バイトで保存 → 約2GBのファイルになる(4bit 量子化込み)。 - このファイルにコードは1行も無い。賢さは「if 文のルール」ではなく、30億個の数字の配置そのものに染み込んでいる(電卓に手順が無く回路の配線で答えが出るのと同じ)。
- 実物は
~/.ollama/models/blobs/に置かれる巨大 blob。ollama listでllama3.2:3b 2.0GBと登録され、実体はsha256-...(約1.9G)のファイル。
「持つ」と「動かす」は別もの(混乱の元)¶
持つ(ollama pull) |
動かす(ollama serve + 質問) |
|
|---|---|---|
| 何をする | ディスクに2GBファイルを置く | そのファイルをメモリに展開し、毎回30億回の掛け算 |
| 例えると | 映画を「DL済み」で寝かせてる | 映画を「再生中」 |
| 食う資源 | ディスク容量 | メモリ(理想は GPU)+電気 |
| 電源切ると | 残る | 消える(ファイルは残る) |
ローカルの登場人物は4人(1台のPC内で同居)¶
ローカルが分かりにくいのは、クラウドでは OpenAI が持っていた役割を、自分のPC内の別々のものが分担するから。
| 役者 | 正体 | クラウドでは誰が持っていた |
|---|---|---|
| ① コード | 自分が書く Python(ex01〜06) | (同じ) |
| ② ランタイム/サーバ | ollama serve(:11434 で待ち受ける常駐プロセス) |
OpenAI の API サーバ |
| ③ 重みファイル | ~/.ollama/models/ の約2GB blob(数字の塊) |
OpenAI が秘匿する重み |
| ④ GPU/CPU+メモリ | 自分の Mac のチップ | データセンタの GPU 群 |
→ 「重み(③)を、サーバ(②)が、自分のチップ(④)に載せて計算する」。これがローカル推論の全部。クラウドは②③④を全部 OpenAI が代行していただけ。
1回の質問が流れる様子(時系列)¶
sequenceDiagram
participant Code as ① Python (ex01)
participant Srv as ② ollama serve (:11434)
participant Wt as ③ 重みファイル (~/.ollama)
participant HW as ④ GPU/CPU
Code->>Srv: POST /api/chat {model, messages}
Srv->>Wt: 重みをメモリにロード(初回のみ)
Wt-->>HW: 30億個の数字を展開
loop トークンの数だけ繰り返す
Srv->>HW: 次の1トークンを予測(行列計算)
HW-->>Srv: 「こ」「ん」「に」… を1個ずつ
end
Srv-->>Code: 返答 JSON {message.content}
ポイントは loop の部分: LLM は「次の1トークン予測」を何百回もループしているだけ。stream=True(ex03)にすると、この1語ずつをリアルタイムで受け取れる=タイプライター表示の正体。
Ollama と LM Studio の関係(別倉庫・窓口だけ共通)¶
複数のローカルランタイムを併用すると必ず混乱する点。Ollama と LM Studio は別アプリ・別倉庫で、モデルを共有しない。
| Ollama | LM Studio | |
|---|---|---|
| モデル入手 | ollama pull |
アプリ内検索 → Download |
| 保存場所 | ~/.ollama/models/(独自 blob 形式) |
~/.lmstudio/models/(GGUF ファイル) |
| 待ち受けポート | :11434 |
:1234 |
| 倉庫の共有 | しない(完全に別) | しない |
ollama pullしたモデルは LM Studio には現れない(逆も同様)。倉庫もファイル形式も別。- LM Studio 内でも「ダウンロード ≠ ロード」。DL は倉庫に置くだけ、別途 "Load"(メモリ展開)が要る(または初回リクエストで JIT ロード)。
- 共通なのは「OpenAI 互換の窓口」だけ。だからコード側は
OLLAMA_HOSTを:11434(Ollama)↔:1234(LM Studio)に変えるだけで乗り換えられる(_client.pyの設計)。アプリ・倉庫は別物でも、窓口の形が揃っているから差し替え1行で済む。
倉庫を共有して二重ダウンロードを避ける(検証済み手順)¶
倉庫は別でも、Ollama の重み blob は中身が GGUF(LM Studio が読む形式)なので、再ダウンロードせず共有できる。要点は方向の非対称:
- Ollama → LM Studio = ハードリンクで共有(ディスク増分ゼロ)。
- LM Studio → Ollama =
ollama create(Modelfile のFROM x.gguf)でインポート(=コピー)。
Ollama を主にして LM Studio へ配るのが無駄が無い。現行の正解は LM Studio 公式 CLI の lms import:
# Ollama の重み blob を LM Studio へハードリンク取り込み(実体は1個を共有)
lms import <blobパスを .gguf 名にしたリンク> --hard-link --user-repo ollama/<名前> -y
# → ~/.lmstudio/models/ollama/<名前>/<...>.gguf にハードリンク生成
# 検証: stat -f '%i' で両者の inode が一致すれば実体共有成功(links=2 になる)
注意点:
- gollama は使えない: v2.0.1 で LM Studio 連携を削除済み(「LM Studio の仕様変更が頻繁で symlink 維持が割に合わない」)。フォルダに symlink を手置きする方法も同じ理由で不安定。公式の
lms importが確実。 - ハードリンクの安心:
ollama rmしてももう片方が実体を保持(参照カウントが減るだけ)。同一ボリューム(~配下)必須なので Mac 内なら常に可。 - gollama 自体は Ollama モデル管理(一覧
-l、VRAM 見積もり-vram)には有用なので残してよい。
実行ログ(実測 — 自分のマシンの実力値)¶
ex01(生 HTTP で /api/chat を叩く)を llama3.2:3b で実行した実出力:
--- 生レスポンス(要所だけ)---
{ "model": "llama3.2:3b", "done": true, "done_reason": "stop" }
--- 本文(message.content)---
1. 人性化されたアクセス:ローカルLLMは、日常の会話を理解し、適応できる。
2. 文脈のある答え:地元の知識を活用し、より正確な回答が得られる。
3. Dataの制約緩和:ローカルデータセットを利用して、制限られたデータで精度高いモデルを作成できる。
生成速度: 20.0 tok/s(94 tokens)
観察できたこと:
20.0 tok/sがこのマシンの実力値。eval_count(94) /eval_duration(ナノ秒) から算出。クラウドと違い「自分のチップの速さ」がそのまま出る。- 小型モデルの粗さが実際に出る: 「Dataの制約緩和」「制限られた」など日英混じり・不自然な日本語。3B は軽い代わりに品質はクラウドのフロンティアモデルに劣る、というトレードオフが体感できる(改善するなら
qwen2.5:7b等の大きめモデルへ)。 done_reason: "stop"= モデルが自然に生成を終えた(長さ上限で切れたわけではない)。
ex02(ドロップイン)— 動いた仕組みと、出た幻覚¶
base_url 差し替えだけで OpenAI SDK がローカルモデルを呼べた(choices[0].message.content / usage の形はクラウドと完全同型、[tokens] prompt=57 completion=65、課金ゼロ)。一方で「RAG とは何か3文で」への実応答が完全な幻覚だった:
正解は Retrieval-Augmented Generation。3B は RAG の知識を持たず、知らないと言えずに「RAG→ロシア」で自信満々に捏造した(temperature=0.7 の揺らぎも逸れを後押し)。「base_url 差し替えで動く」と「3B は事実問いに弱い」を同時に体感できた。
ex03(streaming)— TTFT 実測¶
stream=True で戻り値がチャンクのイテレータに変わり、delta.content(差分)を継ぎ足して逐次表示。実測 TTFT 0.01s / 合計 7.72s / 約135 chunk / 17.5 chunk/s。TTFT がほぼ0なのはネット往復が無く、モデルが既にメモリ常駐(warm)だから(cold の初回はモデルロード時間が乗る)。
ex04(temperature / seed)— 全ノブが効いた¶
temperature=0.0 ×3: コーヒー・ハウス ×3 ← 決定的(毎回最確トークン)
temperature=1.2 ×3: はじめや / ミルクサロン / ルドルフ ← 多様(分布が平ら)
seed=42, temp=0 ×2: コーヒー・ハウス ×2 → ✅一致 ← 再現
temperature=「確率分布を尖らせるか平らにするか」、seed=再現性。評価/抽出は温度0、ブレストは高温、の使い分けが実出力で確認できた。
ex05(構造化出力)— 小型でも JSON 成立¶
response_format={"type":"json_object"} + 温度0 で {"shop":"海風亭","score":4,"good":["刺身","米"]} を取得、score は int でパースできた。ただし100%保証ではないので json.loads を try で囲む(境界の検証)。これが langchain/dspy の構造化層の最下層。
ex06(統合・ルーター)— ローカル優先+クラウド縮退¶
キー無しで実行。通常は [local] こんにちは。 とローカルで応答し、わざとローカルを失敗させた分岐は OPENAI_API_KEY 未設定のため [local-failed] フォールバック先なし と安全に縮退した。ローカル/クラウドが同型(base_url だけ違う)なので、ルーターは try: ローカル except: クラウド の数行。振り分け基準=コスト/レイテンシ/プライバシー/品質/オフライン。クラウド経路も試すなら op run --env-file=.env.op -- uv run python ex06_local_vs_cloud.py。
num_ctx 実証 — 机からはみ出すと冒頭が黙って消える(重要)¶
冒頭に合言葉『紫の月曜日』を仕込み、約2361トークンの埋め草で押し出してから合言葉を尋ねた:
| num_ctx | 応答 | 覚えてた? |
|---|---|---|
| 128(極小) | 「グレート」です。 | ❌ 忘れた(幻覚) |
| 4096(十分) | 紫の月曜日です。 | ✅ 覚えてた |
サーバログが物理的な切り捨てを示す:
num_ctx=128 → n_ctx_slot=256, n_keep=4, task.n_tokens=127 ← 2361→127 に切り捨て
num_ctx=4096 → n_ctx_slot=4096, n_keep=4, task.n_tokens=2361 ← 全部載った
- num_ctx を超えると、先頭(
n_keep=4 の制御トークン以外)から黙って切り捨てられる。エラーは出ず200 OK(=静かなバグ)。だから「一番古い指示」が真っ先に死ぬ。 - 重要 gotcha:
/v1(OpenAI互換)はextra_body.options.num_ctxを無視する。最初OpenAI()経由で同じ実験をしたら両方とも合言葉を覚えていて、ログはn_ctx_slot=32768(既定のまま)だった。num_ctx を本当に効かせるにはネイティブ/api/chat(またはollamaPython SDK)でoptions.num_ctxを渡す必要がある。→ ex04 のextra_body={"options":{"num_ctx":2048}}も /v1 経由なので実際には効いていない点に注意。
全体像(このフォルダのデータの流れ)¶
flowchart LR
subgraph yourmachine["自分のマシン"]
code["Python コード<br/>(OpenAI SDK)"]
subgraph ollama["ollama serve (:11434)"]
api["/v1/chat/completions<br/>(OpenAI 互換)"]
native["/api/chat<br/>(Ollama ネイティブ)"]
model[("llama3.2:3b<br/>等のモデル重み")]
end
end
cloud["api.openai.com/v1<br/>(gpt-4o-mini)"]
code -- "ex01: 生 HTTP" --> native
code -- "ex02-06: base_url 差し替え" --> api
api --> model
native --> model
code -. "ex06: フォールバック" .-> cloud
ポイントは ex01 だけ /api/chat(Ollama 独自の形)を生 HTTP で叩き、ex02 以降は /v1(OpenAI 互換)に寄せて OpenAI SDK で叩くこと。後者にすると、クラウドへ向けるのも base_url を消すだけになる(ex06 のフォールバックがそれ)。
各 ex で「何が分かれば OK か」¶
| ex | 一言 | 「分かった」の判定 |
|---|---|---|
| 01 | 生 HTTP | 「LLM API は localhost にも立つただの HTTP サーバ」と言える / tok/s を計算できる |
| 02 | ドロップイン | base_url を変えるだけで既存 OpenAI コードがローカルで動く理由を説明できる |
| 03 | streaming | stream=True で戻り値がイテレータになり delta.content を継ぎ足すと説明できる / TTFT の意味 |
| 04 | 制御 | temperature と seed が何を操るか、評価時はなぜ温度0+seed固定かを言える |
| 05 | 構造化 | JSON を出させても境界で検証が要る理由(100%保証されない)を言える |
| 06 | 統合 | ローカル/クラウドが同型だから try/except で縮退できる、振り分け基準を挙げられる |
つまずきやすい点(実際に踏む所)¶
ollama serveを起動し忘れる → 接続エラー。各例はcheck_server()で起動手順を出して止まる。GUI 版アプリ起動中なら既に :11434 で動いていることが多い- モデル未 pull → サーバには繋がるが生成で失敗。
ollama pull llama3.2:3bを先に api_keyを空にすると OpenAI SDK が怒る → ローカルでは検証されないので"ollama"等の適当な非空文字列を渡す(_client.local_client()がやっている)- 小型モデルの JSON 逸脱 →
response_formatを付けても壊れた JSON が来うる。json.loadsを try で囲む(ex05)。プロンプトに例を入れる/温度0/モデルを大きくするで改善 - seed 固定でも完全一致しないことがある → 量子化・並列実装・バックエンド差で揺れる。「クラウドより決定的に寄せやすい」程度に捉える
extra_body={"options": {...}}→num_ctx(文脈長)など Ollama 固有パラメータは OpenAI SDK の正式引数に無いのでextra_bodyから渡す(ex04)。ただし/v1(OpenAI互換)はこのoptions.num_ctxを無視する(実測。上の「num_ctx 実証」参照)。本当に効かせたいならネイティブ/api/chatかollamaPython SDK を使う
用語集¶
混同しやすい語を対比で並べる。「それはローカル固有の話か、LLM 一般の話か」を毎回切り分けるのがコツ。
ランタイムと API の階層¶
| 語 | 何 | 具体例(このフォルダ) |
|---|---|---|
| ランタイム / サーバ | モデル重みを読み込み、HTTP で推論を提供するプロセス | ollama serve(:11434)。他に LM Studio(:1234)、llama.cpp の llama-server(:8080)、vLLM |
| モデル | 重みファイル本体。ランタイムが読み込む差し替え可能な部品 | llama3.2:3b、qwen2.5:7b。ollama pull で取得 |
| ネイティブ API | そのランタイム独自の形のエンドポイント | Ollama の /api/chat・/api/generate・/api/tags(ex01) |
| OpenAI 互換 API | api.openai.com と同じ形(/v1/chat/completions 等)を出すエンドポイント |
Ollama の /v1(ex02 以降)。これがあるから OpenAI SDK が流用できる |
判定: 「OpenAI SDK でそのまま叩けるか?」→ Yes なら /v1(互換)経由、No ならネイティブ /api/*。
呼び出しパラメータ(LLM 一般 = クラウドでも同じ)¶
| 語 | 何 | どこで | 効く向き |
|---|---|---|---|
| temperature | サンプリング分布の平坦さ | ex04 | 0=決定的・最確、高い=多様/突飛 |
| seed | 乱数の種 | ex04 | 固定すると(理想的に)再現する |
| stream | 逐次返すか一括か | ex03 | True=トークンが流れる、False=出来てから一括 |
| response_format | 出力形式の制約 | ex05 | {"type":"json_object"} で JSON のみ |
| num_ctx | 読み込む最大トークン長=「作業机の広さ」(入力+出力の合計。Ollama 固有) | ex04 | 大=長い履歴可・メモリ食う。超過分は先頭から黙って切り捨て。/v1 経由では無視(ネイティブ /api/chat で効く) |
計測語¶
| 語 | 何 | 出どころ |
|---|---|---|
| TTFT (Time To First Token) | 最初の1トークンが返るまでの待ち | ex03 で実測 |
| tok/s (tokens per second) | 生成スループット = 自分のマシンの実力 | ex01(eval_count/eval_duration)、ex03 |
| 量子化 (quantization) | 重みを低ビット化してメモリ削減・高速化(精度は少し落ちる) | Ollama の既定タグは大抵 4bit 量子化済み |
モデルのサイズと速度(B・MoE・メモリ帯域)¶
ローカルで「載るか・速いか」を左右する語。容量(GB)・遅延(ns)・帯域(GB/s)を別軸で切り分けるのがコツ。
| 語 | 何 | 効く向き |
|---|---|---|
| B (Billion) | パラメータ数の単位。10B=100億、70B=700億。学習で得た重みの数 = 規模・賢さの目安 |
大=賢い/知識多いが、メモリも計算も食う |
AxB(例 30B-A3B) |
MoE(Mixture of Experts)の表記。A=Active。総30B のうち1トークンで実際に発火するのは3B だけ |
総数=知識量・メモリ容量、Active=計算量・速度。両者を分離するのが MoE |
| メモリ帯域 (memory bandwidth) | CPU/GPU↔メモリで1秒に運べるデータ量(GB/s)。道路の太さ=運搬能力 | 大量データを繰り返し読む処理で律速になる |
判定のキモ:
- メモリ性能は3軸: 容量(どれだけ入るか/GB=倉庫の広さ)・遅延(1回の往復/ns=1個届く速さ)・帯域(単位時間の総量/GB/s=道路の太さ)。別物。大量運搬では遅延より帯域が効く。
- LLM 推論は「メモリ帯域律速」: トークンを1個出すたびに全重みを読み直すので、生成速度は GPU の計算力(TFLOPS)でなくメモリ帯域(GB/s)でほぼ決まる。「演算器は速いのに遅い」=帯域が詰まっている状態(compute-bound でなく memory-bound)。HBM や Apple Silicon のユニファイドメモリ帯域が売りになる理由。
- MoE の
30B-A3Bが手元で美味しい理由: メモリ容量は30B ぶん必要(どの専門家が呼ばれるか不明なので全員載せる)が、1トークンの計算・読込は3B ぶん。「30B の賢さを 3B 並みの速さで」。容量は食うが速度は軽い、の典型。量子化(重みを低ビット化)と組み合わせると容量側も削れる。
ざっくり帯域の桁: ふつうの DDR5 PC メモリ≈数十 GB/s、GPU の GDDR≈数百 GB/s〜1 TB/s、データセンタ GPU の HBM≈数 TB/s。
混同しやすいペア¶
- ローカル固有 vs LLM 一般:
ollama serve/pull/num_ctx/ 量子化 = ローカル固有。temperature / seed / stream / messages / 構造化出力 = LLM 一般(クラウドでも同じ)。新しい挙動に出会ったらまずこの仕分け /api/chat(ネイティブ)vs/v1/chat/completions(互換): 同じモデルに届くが、リクエスト/レスポンスの JSON の形が違う。ex02 以降は互換側に寄せて SDK 一本化- 「無料」vs「タダではない」: API 課金はゼロだが、ダウンロード容量・ディスク・VRAM/メモリ・電気代・速度を払っている。クラウドはその逆
困りごと → 見る/使うもの(クイック早見表)¶
| 困りごと | 見る/使う |
|---|---|
| 繋がらない | ollama serve 起動 → curl localhost:11434/api/tags で疎通確認 |
model not found |
ollama pull <model>。入っているものは ollama list |
| 遅い | 小型モデル(llama3.2:1b)に変える / GPU を使う / 量子化版を選ぶ |
| 既存 OpenAI コードをローカル化 | OpenAI(base_url=".../v1", api_key="ollama") に差し替えるだけ(ex02) |
| JSON が崩れる | response_format + 温度0 + プロンプトに例 + json.loads を try(ex05) |
| ローカルが頼りない | 難しいタスクだけクラウドへフォールバック(ex06) |
| 別ランタイムを使いたい | OLLAMA_HOST を LM Studio(:1234) 等に向ける(OpenAI 互換なら動く) |
次にやること¶
eval_basics/で温度0+seed固定の決定性を評価に活かすdspy_basics/langchain_v1/で ex05 の構造化出力がフレームワークの中でどう抽象化されるか見る- 本番サービングが要るなら vLLM(バッチ処理・高スループット)を別途調べる
作成: 2026-06-12 / 最終更新: 2026-06-27