3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NatureLM-8x7B-Inst をローカル環境で動かす:Ollama / Lemonade / llama-cpp-python / 自作API 4方式比較

3
Posted at

はじめに

Microsoft Research AI for Science が開発した NatureLM(Nature Language Model)は、小分子・材料・タンパク質・DNA・RNA を横断する科学基盤モデルです。本記事では、NVIDIA GPU を持たないローカル環境で NatureLM-8x7B-Inst を動かし、OllamaLemonade Serverllama-cpp-python自作 OpenAI 互換 API サーバー の 4 つのアプローチを比較検証しました。

NatureLM とは

実験環境

項目 スペック
OS Windows 11
CPU AMD(スレッド数: 全コア使用)
GPU AMD Radeon 8060S(4GB VRAM)
RAM 64 GB
Python 3.13.12
Ollama 0.20.0
Lemonade Server 10.0.1
llama-cpp-python 0.3.19
PyTorch 2.11.0+cpu
transformers 5.5.0

NVIDIA GPU なしのため、vLLM(CUDA 必須)は使用できません。本記事では CPU 推論および AMD GPU(Vulkan)推論で検証しています。

1. Ollama でのセットアップ

1.1 GGUF モデルの取得

NatureLM の公式 HuggingFace リポジトリは safetensors 形式のみ提供しています。Ollama で使用するために、コミュニティが変換した GGUF 版を利用しました。

GGUF リポジトリ 量子化バリエーション
DevQuasar/microsoft.NatureLM-8x7B-Inst-GGUF 2-bit 〜 16-bit
gabriellarson/NatureLM-8x7B-Inst-GGUF IQ1_S 〜 F16

1.2 インストール

# HuggingFace から直接 pull(Q4_K_M 量子化、約 28GB)
ollama pull hf.co/DevQuasar/microsoft.NatureLM-8x7B-Inst-GGUF:Q4_K_M

ダウンロード完了後、ollama list で確認:

NAME                                                        ID              SIZE      MODIFIED
hf.co/DevQuasar/microsoft.NatureLM-8x7B-Inst-GGUF:Q4_K_M    08f0eafd76d9    28 GB     ...

1.3 プロンプトテンプレートの設定

NatureLM は論文中で以下のテンプレートを使用しています:

Instruction: {instruction}


Response:
{response}

Ollama のデフォルトではテンプレートが設定されないため、Modelfile を作成してカスタムモデルを登録しました。

Modelfile-NatureLM
FROM hf.co/DevQuasar/microsoft.NatureLM-8x7B-Inst-GGUF:Q4_K_M

TEMPLATE """Instruction: {{ .Prompt }}


Response:
"""

PARAMETER stop "Instruction:"
PARAMETER stop "</s>"
PARAMETER temperature 0.7
PARAMETER num_predict 512
ollama create naturelm -f Modelfile-NatureLM

1.4 Ollama テスト結果

テスト方法

Ollama REST API(/api/generate)を使用し、raw: true で直接プロンプトを送信。

curl http://localhost:11434/api/generate -d '{
  "model": "naturelm",
  "prompt": "Instruction: Predict the logP value of the molecule CC(=O)OC1=CC=CC=C1C(=O)O\n\n\nResponse:\n",
  "stream": false,
  "raw": true,
  "options": { "num_predict": 256, "temperature": 0.7 }
}'

結果

タスク プロンプト Ollama 出力 判定
物性予測(logP) Predict the logP value of the molecule CC(=O)OC1=CC=CC=C1C(=O)O 0.89 logP
物性予測(分子量) Predict the molecular weight of the molecule CC(=O)OC1=CC=CC=C1C(=O)O 268.20
分子生成 Generate a molecule with four hydrogen bond donors. (空)
SMILES 生成 What is the SMILES notation for caffeine? (空)
逆合成 Perform retrosynthesis on the molecule CC(=O)OC1=CC=CC=C1C(=O)O (空)

数値を出力するタスクは成功するが、SMILES・分子構造・タンパク質配列などの科学エンティティを生成するタスクは出力が空になる。

原因分析

NatureLM は標準 Mixtral の語彙(32,000 トークン)に加え、科学ドメイン用の特殊トークン約 6,000 個を追加しています:

  • <mol>, </mol> — 分子 SMILES の開始・終了
  • <m>C, <m>N, <m>=, <m>( … — SMILES 構成要素の個別トークン
  • <protein>, </protein> — タンパク質配列
  • <material>, </material> — 材料組成
  • <sg1><sg230> — 結晶空間群

GGUF 変換時にこれらの特殊トークンのテキスト表現が失われるため、Ollama(llama.cpp のデフォルトトークナイザー)ではデコードできず、出力が空文字列となります。

2. Lemonade Server でのセットアップ

Lemonade Server は AMD が支援するローカル AI サーバーで、Vulkan バックエンドによる AMD GPU アクセラレーションOpenAI 互換 API を提供します。

2.1 利用可能なバックエンド

lemonade recipes
Recipe     Backend   Status
llamacpp   vulkan    installed    # AMD GPU 推論 ✅
           rocm      installable  # ROCm(要インストール)
           cpu       installable  # CPU フォールバック

2.2 GGUF モデルの登録

Lemonade は独自のモデルレジストリを持ちますが、HuggingFace からカスタム GGUF を pull できます。

方法A: HuggingFace から直接 pull(再ダウンロード)

lemonade-server pull NatureLM-8x7B-Inst \
  --checkpoint DevQuasar/microsoft.NatureLM-8x7B-Inst-GGUF:Q4_K_M \
  --recipe llamacpp

方法B: Ollama の既存 GGUF を再利用(推奨)

Ollama で既にダウンロード済みの GGUF ファイル(28GB)を再ダウンロードせずに利用できます。

# 1. GGUF ファイルのハードリンクを作成
$ollamaBlob = "$env:USERPROFILE\.ollama\models\blobs\sha256-21191d7e9e0ff928540b94c4539822f1f75ba22fcfbcf82866c230003961c5af"
mkdir C:\Users\$env:USERNAME\lemonade-models
cmd /c mklink /H "C:\Users\$env:USERNAME\lemonade-models\NatureLM-8x7B-Inst-Q4_K_M.gguf" $ollamaBlob

# 2. Lemonade Server を --extra-models-dir 付きで起動
lemonade-server serve --extra-models-dir "C:\Users\$env:USERNAME\lemonade-models"

起動後、lemonade list で確認:

Model Name                                   Downloaded  Details
extra.NatureLM-8x7B-Inst-Q4_K_M.gguf        Yes         llamacpp

--extra-models-dir.gguf 拡張子のファイルをスキャンします。Ollama のブロブファイルには拡張子がないため、ハードリンク(またはコピー)で .gguf 拡張子を付ける必要があります。ハードリンクならディスク容量を消費しません。

2.3 モデルのロードとテスト

# モデルをロード(Vulkan GPU にオフロード)
lemonade load "extra.NatureLM-8x7B-Inst-Q4_K_M.gguf"

Lemonade は OpenAI 互換 API を提供するため、標準的なクライアントで利用できます。NatureLM は独自のプロンプトテンプレートを必要とするため、Completions API/v1/completions)で Instruction: / Response: フォーマットを直接指定します。

curl http://localhost:8000/api/v1/completions \
  -H "Authorization: Bearer lemonade" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "extra.NatureLM-8x7B-Inst-Q4_K_M.gguf",
    "prompt": "Instruction: Predict the logP value of CC(=O)OC1=CC=CC=C1C(=O)O\n\n\nResponse:\n",
    "max_tokens": 64,
    "temperature": 0.5
  }'
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8000/api/v1",
    api_key="lemonade"
)

# Completions API でNatureLMテンプレートを直接使用
response = client.completions.create(
    model="extra.NatureLM-8x7B-Inst-Q4_K_M.gguf",
    prompt="Instruction: Predict the logP value of CC(=O)OC1=CC=CC=C1C(=O)O\n\n\nResponse:\n",
    max_tokens=64,
    temperature=0.5,
)
print(response.choices[0].text)

2.4 Lemonade テスト結果

タスク プロンプト Lemonade 出力 速度 判定
物性予測(logP) Predict the logP value of CC(=O)OC1=CC=CC=C1C(=O)O 0.01 logP 8.9 tokens/s
物性予測(分子量) Predict the molecular weight of CC(=O)OC1=CC=CC=C1C(=O)O 3.35 ~9 tokens/s
SMILES 生成 What is the SMILES notation for aspirin? (空)※24トークン生成

Lemonade も内部的に llama.cpp を使用しているため、Ollama と同じ科学トークンのデコード問題が発生します。数値出力タスクは動作しますが、SMILES・タンパク質配列などの生成タスクは出力が空になります。

ただし、Vulkan バックエンドにより AMD GPU で高速推論(CPU比約3倍)できる点が大きなメリットです。

3. llama-cpp-python + HuggingFace トークナイザー

3.1 アプローチ

推論は llama.cpp(GGUF)で行い、生成されたトークン ID を HuggingFace のオリジナルトークナイザーでデコードするハイブリッド方式を採用しました。

[GGUF モデル] → llm.generate() → トークン ID 列 → HF tokenizer.decode() → テキスト出力

3.2 環境構築

# llama-cpp-python(CPU プリビルド版)
pip install llama-cpp-python \
  --prefer-binary \
  --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu

# HuggingFace トークナイザー
pip install huggingface_hub transformers

# PyTorch CPU(トークナイザーの依存)
# ※ Windows Long Path 問題がある場合は短いパスにインストール
pip install torch --index-url https://download.pytorch.org/whl/cpu --target C:\torch_tmp

3.3 実装コード

"""
NatureLM-8x7B-Inst: Interactive runner
Usage: set PYTHONPATH=C:\torch_tmp && python naturelm_test.py
"""
import os
from llama_cpp import Llama
from transformers import AutoTokenizer

GGUF_PATH = os.path.expanduser(
    r"~\.ollama\models\blobs\sha256-21191d7e9e0ff928540b94c4539822f1f75ba22fcfbcf82866c230003961c5af"
)

# HuggingFace からオリジナルトークナイザーをロード
tokenizer = AutoTokenizer.from_pretrained(
    "microsoft/NatureLM-8x7B-Inst", trust_remote_code=True
)

# GGUF モデルをロード(CPU推論)
llm = Llama(
    model_path=GGUF_PATH,
    n_ctx=2048,
    n_threads=os.cpu_count(),
    verbose=False,
)

def generate(instruction: str, max_tokens=512, temperature=0.7) -> str:
    """NatureLM の Instruction-Response テンプレートで生成"""
    prompt = f"Instruction: {instruction}\n\n\nResponse:\n"
    input_tokens = llm.tokenize(prompt.encode("utf-8"))

    gen_tokens = []
    eos = llm.token_eos()
    for token in llm.generate(input_tokens, temp=temperature):
        gen_tokens.append(token)
        if token == eos or len(gen_tokens) >= max_tokens:
            break

    # HF トークナイザーでデコード(科学トークンを正しく復元)
    return tokenizer.decode(gen_tokens, skip_special_tokens=False).strip()

# 使用例
result = generate("What is the SMILES notation for caffeine?")
print(result)
# => <mol><m>C<m>n<m>1<m>c<m>(<m>=<m>O<m>)<m>c<m>2<m>c<m>(<m>n<m>c<m>n<m>2<m>C<m>)<m>n<m>(<m>C<m>)<m>c<m>1<m>=<m>O</mol>

3.4 テスト結果

タスク プロンプト llama.cpp デコード HF トークナイザー デコード 判定
分子生成 Generate a molecule with four hydrogen bond donors. (空) <material><i>Fe<i>Fe...<i>O<i>O...<sg1></material>
SMILES 生成 What is the SMILES notation for caffeine? (空) <mol><m>C<m>n<m>1<m>c<m>(<=<m>O<m>)...<m>=<m>O</mol> ✅🎯
logP 予測 Predict the logP value of CC(=O)OC1=CC=CC=C1C(=O)O 0.86 logP 0.69 logP

カフェインの SMILES 生成結果 Cn1c(=O)c2c(ncn2C)n(C)c1=O は、正しいカフェインの SMILES 表記と一致します。

4. OpenAI 互換 API サーバー(自作)

4.1 動機

3方式の検証から、以下の課題が明らかになりました:

  • Ollama / Lemonade:OpenAI 互換 API はあるが、科学トークンをデコードできない
  • llama-cpp-python + HF Tokenizer:科学トークンは正しくデコードできるが、API サーバー機能がない

この両方を解決するため、科学トークンを正しくデコードする OpenAI 互換 API サーバーを FastAPI で自作しました。

4.2 アーキテクチャ

クライアント (openai-python, curl, etc.)
    │
    ▼ OpenAI 互換 API
┌──────────────────────────────────────────┐
│  NatureLM API Server (FastAPI)           │
│                                          │
│  ┌───────────┐    ┌───────────────────┐  │
│  │ llama.cpp │───▶│ Token ID 列       │  │
│  │  (GGUF)   │    │ [32001, 32045..]  │  │
│  └───────────┘    └────────┬──────────┘  │
│                            │             │
│                   ┌────────▼──────────┐  │
│                   │ HF Tokenizer      │  │
│                   │ (science tokens)  │  │
│                   └────────┬──────────┘  │
│                            │             │
│                   ┌────────▼──────────┐  │
│                   │ <mol>C=O...</mol> │  │
│                   └───────────────────┘  │
└──────────────────────────────────────────┘

4.3 主な機能

機能 詳細
POST /v1/chat/completions NatureLM テンプレートを自動適用
POST /v1/completions 生プロンプトで直接制御
GET /v1/models モデル情報・ケイパビリティ一覧
科学トークンデコード HF トークナイザーで <mol>, <protein> 等を正しく復元
ストリーミング stream: true で SSE リアルタイム出力
リトライ機能 空出力時に自動リトライ(シード変更 + 温度昇温)
CORS 対応 ブラウザアプリから直接利用可

4.4 リトライ機能

NatureLM は量子化モデル (Q4_K_M) で一部のプロンプトに対して即座に EOS トークンを出力することがあります。この問題に対処するため、自動リトライ機能を実装しました。

リトライ戦略:
  1回目: temperature=0.7, seed=random
  2回目: temperature=0.85, seed=random  (+0.15)
  3回目: temperature=1.0, seed=random   (+0.15)
  ...
  最大20回まで(max_retries パラメータで制御)

リトライの仕組み:

  1. 高レベル API (llm()) でランダムシードを変えながら生成
  2. テキスト出力があればそのまま返却
  3. トークンは生成されたがテキスト化できない場合(科学トークン)→ トークンレベル API + HF デコードにフォールバック
  4. 全リトライ失敗時は空文字列を返却、timing.attempts に試行回数を記録

4.5 インストールと起動

# 依存パッケージ
pip install fastapi uvicorn llama-cpp-python \
  --prefer-binary \
  --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu
pip install huggingface_hub transformers
pip install torch --index-url https://download.pytorch.org/whl/cpu --target C:\torch_tmp

# 起動
set PYTHONPATH=C:\torch_tmp
python naturelm_server.py
# => http://localhost:8080

4.6 使用例

Python (openai クライアント)

from openai import OpenAI

client = OpenAI(base_url="http://localhost:8080/v1", api_key="unused")

# SMILES 生成
r = client.chat.completions.create(
    model="naturelm-8x7b-inst",
    messages=[{"role": "user", "content": "What is the SMILES for caffeine?"}],
)
print(r.choices[0].message.content)
# => <mol><m>C<m>n<m>1<m>c<m>(<m>=<m>O<m>)<m>c<m>2<m>c<m>(<m>n<m>c<m>n<m>2<m>C<m>)<m>n<m>(<m>C<m>)<m>c<m>1<m>=<m>O</mol>

# リトライ付き生成(拡張パラメータ)
r = client.chat.completions.create(
    model="naturelm-8x7b-inst",
    messages=[{"role": "user", "content": "Generate a SMILES of a kinase inhibitor."}],
    extra_body={"max_retries": 10},
)

curl

curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [{"role":"user","content":"What is the SMILES for ibuprofen?"}],
    "max_tokens": 256,
    "temperature": 0.7,
    "max_retries": 5
  }'

4.7 テスト結果

タスク プロンプト 結果 リトライ 判定
SMILES 生成 What is the SMILES for caffeine? <mol>Cn1c(=O)c2c(ncn2C)n(C)c1=O</mol> 1回 ✅🎯
SMILES 生成 What is the SMILES of ibuprofen? <mol>CC(C)Cc1ccc(C(C)C(=O)O)cc1</mol> 1回 ✅🎯
分子設計 Generate a SMILES of a kinase inhibitor. <mol>Cn1cc(-c2cnc3ccc(NC...)cc32)...</mol> 3回
logP 予測 Predict the logP of CC(=O)OC1=CC=CC=C1C(=O)O 0.40 logP 1回
ストリーミング Predict the logP of c1ccccc1 -0.41 logP (SSE) 1回

キナーゼ阻害剤の生成は最初の2回は EOS で失敗しましたが、3回目のリトライ(temperature=1.0, 新しいシード)で SMILES の生成に成功しました。リトライ機能の有効性を示す好例です。

5. 4方式比較まとめ

観点 Ollama Lemonade Server llama-cpp-python + HF 自作 API Server
セットアップ難易度 ⭐ 簡単 ⭐⭐ 簡単 ⭐⭐⭐ やや複雑 ⭐⭐⭐ やや複雑
推論バックエンド llama.cpp (CPU) llama.cpp (Vulkan GPU 🚀) llama.cpp (CPU) llama.cpp (CPU)
推論速度 ~3 tokens/s ~9 tokens/s ~3 tokens/s ~3 tokens/s
数値出力タスク ✅ 動作 ✅ 動作 ✅ 動作 ✅ 動作
SMILES 生成 ❌ 空出力 ❌ 空出力 ✅ 正しくデコード 正しくデコード
タンパク質/材料生成 ❌ 空出力 ❌ 空出力 ✅ 正しくデコード 正しくデコード
OpenAI 互換 API ❌ 独自 API ✅ 標準対応 ❌ スクリプトのみ 標準対応
リトライ機能 自動リトライ
ストリーミング SSE 対応
AMD GPU 活用 ✅ Vulkan
既存アプリ連携 ✅ OpenAI 互換

使い分けガイド

用途に応じて最適な方式を選択:

┌─ 科学トークン + API 連携が必要(推奨)
│   └─→ 自作 API Server(科学トークン + OpenAI互換 + リトライ)
│
├─ 科学トークンが必要(スクリプト利用)
│   └─→ llama-cpp-python + HF Tokenizer
│
├─ 速度重視・AMD GPU(数値タスクのみ)
│   └─→ Lemonade Server(Vulkan GPU加速)
│
└─ とりあえず試したい
    └─→ Ollama(最も簡単)

6. 注意事項と制限

カスタムトークナイザーの問題

NatureLM の語彙拡張(38,078 トークン)は GGUF フォーマットのトークナイザーメタデータに完全には反映されません。これは NatureLM 固有の問題ではなく、カスタム語彙を持つモデルを GGUF に変換する際の一般的な課題です。

推奨環境

フル機能でNatureLMを利用するための推奨環境:

方式 GPU 要件 メモリ要件 科学トークン OpenAI API 推論速度
vLLM + transformers(推奨) NVIDIA A100 × 2 以上 80GB+ VRAM ✅ 完全対応 最速
自作 API Server 不要(CPU可) 32GB+ RAM ✅ 対応 低速
Lemonade Server (Vulkan) AMD/NVIDIA GPU 32GB+ RAM ⚠️ 数値のみ 高速
llama-cpp-python + HF Tokenizer 不要(CPU可) 32GB+ RAM ✅ 対応 低速
Ollama(GGUF) 不要(CPU可) 32GB+ RAM ⚠️ 数値のみ 低速

量子化モデルでの生成安定性

Q4_K_M 量子化モデルでは、一部の生成タスクで EOS トークンが即座に出力されることがあります。特に「N個の水素結合ドナーを持つ分子を生成」のような抽象度の高いプロンプトで発生しやすい傾向がありました。対策として:

  • プロンプトの具体化Generate a molecule with 4 HBD. よりも What is the SMILES of ibuprofen? のような具体的な指示の方が安定します
  • リトライ機能:自作 API Server ではシード変更 + 温度昇温による自動リトライを実装済み
  • より高精度の量子化:Q5_K_M や Q8_0 を使用することで改善が期待できます(要メモリ)

おわりに

NatureLM は科学ドメインに特化したユニークなモデルですが、ローカル環境での利用にはカスタムトークナイザーの扱いに注意が必要です。

  • Lemonade Server:AMD GPU の Vulkan バックエンドで CPU 比約3倍の高速推論が可能。OpenAI 互換 API により既存アプリとの連携も容易。ただし科学トークンのデコード問題あり。
  • 自作 API Server:科学トークンの正しいデコード + OpenAI 互換 API + 自動リトライを統合した最も実用的なソリューション。
  • llama-cpp-python + HF トークナイザー:ハイブリッド方式で科学トークンを正しくデコードでき、スクリプト利用に適する。
  • Ollama:最も手軽だが、科学トークンの制限は Lemonade と同様。

NVIDIA GPU がない AMD 環境でも、用途に応じて方式を使い分けることで NatureLM を実用的に活用できることを確認しました。

参考リンク

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?