Open11

ローカルLLMを試してみる

tapontapon

LLMを選ぶときのポイント

  1. gguf形式であること
  2. パラメータ数
  3. 量子化のビット数

多くの場合、モデルのファイル名に情報が含まれている

tapontapon

LLMプラットフォームのソフトウェア

Jan
Ollama
llama.ccp
LM Studio
AI Toolkit for VS Code

などなど

tapontapon

Janを使ってみる

私はM4 MacbookAirなのでMac版をダウンロード・インストールする。
起動したら下記のような画面で表示される。

左上のアイコンで、上から2番目のアイコン(Hub)をクリックすると、LLMをダウンロードする画面が表示される。
Hugging FaceのURLでの検索もできそう。

Googleが提供しているGemma2をダウンロードしてみる。
警告アイコンが表示されているバージョンはSlow on your deviceと表示される。私のMacではスペック不足みたい。Defaultにしておく。

ダウンロードが完了したら、左上のアイコンで一番上のアイコン(Thread)をクリックする。
ダウンロードしたモデルが選ばれていることを確認してチャットにテキストを打ち込んで送信してみる。

右上の一番左のアイコン(Thread Settings)でAssistantやModelの設定ができるみたい。

tapontapon

JanをLLMサーバとして動かしてみる

左下のアイコンで、一番上のアイコン(Local API Server)をクリックすると下記の画面が表示される
ここからサーバを起動できる様子

Start Serverボタンをクリックするとサーバが起動する

Stop Serverボタンの下にAPI Playgroundというボタンがある
クリックするとブラウザが立ち上がりAPIドキュメントが表示される

OpenAIと互換性があるとのことで、OpenAI用のライブラリを使って利用できるとのこと。

tapontapon

Ollamaを使ってみる

公式サイトからダウンロードする
https://ollama.com/download

ダウンロード後、Ollamaアプリを起動する
ターミナルでバージョンコマンドを実行する

% ollama --version
ollama version is 0.6.6

Llama3.2をダウンロード(ollama pull)してみる

ollama pull llama3.2
pulling manifest 
pulling dde5aa3fc5ff: 100% ▕████████████████████▏ 2.0 GB
pulling 966de95ca8a6: 100% ▕████████████████████▏ 1.4 KB
pulling fcc5a6bec9da: 100% ▕████████████████████▏ 7.7 KB
pulling a70ff7e570d9: 100% ▕████████████████████▏ 6.0 KB
pulling 56bb8bd477a5: 100% ▕████████████████████▏   96 B
pulling 34bb5ab01051: 100% ▕████████████████████▏  561 B
verifying sha256 digest 
writing manifest 
success

Llama3.2を起動(ollama run)してみるとメッセージ入力受付状態になる
メッセージを送ってみる

% ollama run llama3.2
>>> こんにちは
こんにちは!どういたしまして?どんな事がしたいのですか?

>>> Send a message (/? for help)

終了するには/byeを打ち込む or Ctrl + Dコマンドを押す

tapontapon

Ollamaのサーバ機能を使ってみる

Janと同様、OllamaもLLMサーバとして使うことができる

% ollama serve

crulでコマンドを実行してみる

% curl http://localhost:11434/api/generate -d '{"model": "llama3.2", "prompt": "こんにちは"}'
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.49096Z","response":"こんにちは","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.515362Z","response":"!","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.539749Z","response":"どう","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.563165Z","response":"で","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.588002Z","response":"した","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.612145Z","response":"か","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.635641Z","response":"?","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.659355Z","response":"何か","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.683375Z","response":"相","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.707329Z","response":"談","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.731449Z","response":"した","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.755231Z","response":"い","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.779348Z","response":"場合は","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.803348Z","response":"お","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.827296Z","response":"気","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.851341Z","response":"軽","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.875593Z","response":"に","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.899742Z","response":"話","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.923863Z","response":"して","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.947794Z","response":"ください","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.972483Z","response":"。","done":false}
{"model":"llama3.2","created_at":"2025-05-09T15:55:27.99576Z","response":"","done":true,"done_reason":"stop","context":[128006,9125,128007,271,38766,1303,33025,2696,25,6790,220,2366,18,271,128009,128006,882,128007,271,90115,128009,128006,78191,128007,271,90115,6447,104405,16556,56051,32149,11571,127091,50021,111813,56051,16995,126513,33334,95221,117355,20230,87177,39926,72315,1811],"total_duration":1302024708,"load_duration":680394833,"prompt_eval_count":26,"prompt_eval_duration":112055458,"eval_count":22,"eval_duration":508423375}

上記はストリームで回答が出力されるもの
"stream": falseを指定すると、ストリーム出力をオフにできる

% curl http://localhost:11434/api/generate -d '{"model": "llama3.2", "prompt": "こんにちは", "stream": false}'
{"model":"llama3.2","created_at":"2025-05-10T05:18:47.301139Z","response":"こんにちは!どういたしまして? (Oh, hi! How are you?) I'm here to help with any questions or topics you'd like to discuss. What's on your mind today?","done":true,"done_reason":"stop","context":[128006,9125,128007,271,38766,1303,33025,2696,25,6790,220,2366,18,271,128009,128006,882,128007,271,90115,128009,128006,78191,128007,271,90115,6447,104405,102334,103801,39926,11571,320,12174,11,15960,0,2650,527,499,10380,358,2846,1618,311,1520,449,904,4860,477,13650,499,4265,1093,311,4358,13,3639,596,389,701,4059,3432,30],"total_duration":1133144000,"load_duration":32521583,"prompt_eval_count":26,"prompt_eval_duration":37772208,"eval_count":40,"eval_duration":1062114709}
tapontapon

Web UIライブラリを使ってみる

Chainlit:Pythonのオープンソースライブラリ

下記ライブラリをインストールする

% pip install openai
% pip install chainlit

今回はollamaでLLMサーバを動かし、モデルにはphi4を使ってみる

from openai import AsyncOpenAI
import chainlit as cl

client = AsyncOpenAI(
    base_url="http://localhost:11434/v1",
    api_key="dummy"
)

@cl.on_chat_start
def start_chat():
    cl.user_session.set(
        "history",
        [
            {
                "role": "system",
                "content": "あなたは親切なアシスタントです。"
            }
        ]
    )

@cl.on_message
async def main(input):
    message = cl.Message(content="")
    await message.send()

    history = cl.user_session.get("history")
    history.append({"role": "user", "content": input.content})

    stream = await client.chat.completions.create(
        model="phi4",
        messages=history,
        stream=True
    )

    async for part in stream:
        token = part.choices[0].delta.content
        if token != "":
            await message.stream_token(token)
    
    history.append(
        {
            "role": "assistant",
            "content": message.content
        }
    )
    await message.update()

chainlit run コマンドで上記を記載したpythonファイルを指定して実行する
Web UIが自動で立ち上がる

% chainlit run chainlit_realtime.py
2025-05-10 14:46:45 - Your app is available at http://localhost:8000
2025-05-10 14:46:47 - Translated markdown file for ja not found. Defaulting to chainlit.md.

メッセージを送ってみると、回答がストリームで出力される

エンター押すとすぐにメッセージが送信されてしまうみたい。。