LLM/Phi-3へのプログラム・アクセスの調査記録

Share
目次
カテゴリのアイコン カテゴリ一覧 AI

LLM/Phi-3へのプログラム・アクセスの調査記録

LLM/Phi-3へのプログラム・アクセスの調査記録

更新日

Phi-3 モデルの導入手順

導入する Phi-3 のモデルとして Hugging Face から取得可能な「phi-3-mini-4k-instruct」の ONNX 形式を採用。

また、プログラムアクセスに「ONNX Runtime」と「Hugging Face Transformers」を採用。

  • Phi-3 に関して公式 SDK という形で提供されている方法は見つからない
  • Microsoft が Hugging Face に公式にモデルを提供しており、Hugging Face Transformers を経由する方法がデファクトになっている

補足1:Phi-3 モデルについて

  • phi-3-mini:軽量でローカルCPU環境に適する
  • phi-3-small:精度とサイズのバランスが良いがGPUや高性能CPU向け
  • phi-3-medium:より高度な推論向け

補足2:実行フレームワークについて

  • Phi-3 のモデルは GPU 環境を想定しているため CPU 環境では要件を満たせない
  • ONNX 形式のモデルが提供されている場合、CPU 環境でも ONNX ランタイムと組み合わせて実行可能
  • ONNX ランタイムはトークナイザーを持たないので Transformers で代用
  • GGUF 形式のモデルが提供されている場合、llama.cpp と の組み合わせにも対応

インストール手順

1. システム準備

sudo apt update && sudu apt upgrade -y
sudo apt install -y python3-venv python3-pip

2. 仮想環境準備

# 作業ディレクトリ作成
mkdir ~/hiprojects/phi-3
cd ~/hiprojects/phi-3
# 仮想環境作成
python3 -m venv venv
source venv/bin/activate
# パッケージのインストール
pip3 install huggingface_hub onnxruntime onnx numpy transformers fastapi uvicorn

3. 推論モデルのダウンロード

# 実行ディレクトリの phi3-model にダウンロード
huggingface-cli download microsoft/phi-3-mini-4k-instruct-onnx \
  --include="cpu_and_mobile/cpu-int4-rtn-block-32-acc-level-4/*" \
  --local-dir phi3-model

ディレクトリサイズを確認して 3GB 弱程度であれば成功。

$ du -sh phi3-model/
2.6G    phi3-model/

4. 推論プログラムの作成

FastAPI に post エンドポイントを設定し、受け取った文字列を Transformers のライブラリで処理するサンプルコード。

# main.py

from fastapi import FastAPI, Request
from pydantic import BaseModel
import onnxruntime
import numpy as np
from transformers import AutoTokenizer

# 生成する最大のトークン数
max_new_tokens = 100

# モデルの取得先
model_dir = "./phi3-model/cpu_and_mobile/cpu-int4-rtn-block-32-acc-level-4/"

# トークナイザーの作成
tokenizer = AutoTokenizer.from_pretrained(model_dir)

# モデルのセッションを作成
session = onnxruntime.InferenceSession(
  f"{model_dir}/phi3-mini-4k-instruct-cpu-int4-rtn-block-32-acc-level-4.onnx",
  providers=["CPUExecutionProvider"]
)

app = FastAPI()

class ChatInput(BaseModel):
  message: str

# エンドポイント登録
@app.post("/api/v1/chat")
def chat(input: ChatInput):
  # 入力テキストのトークン化
  input_ids = tokenizer(input.message, return_tensors="np")["input_ids"][0]

  # past_key_values の初期値を 0 パディング
  past_key_values = {}
  for input_tensor in session.get_inputs():
    name = input_tensor.name
    if "past_key_values" in name:
      shape = []
      for dim in input_tensor.shape:
        if isinstance(dim, int):
          shape.append(dim)
        elif isinstance(dim, str):
          if "batch" in dim:
            shape.append(1)
          elif "past" in dim or "sequence" in dim:
            shape.append(0)
          else:
            shape.append(1)
      past_key_values[name] = np.zeros(shape, dtype=np.float32)

  # 前処理 (入力トークンを処理して past_key_values を更新)
  # attention_mask は累積して引き継ぐようにする
  attention_mask = None
  for i, token_id in enumerate(input_ids):
    input_token = np.array([[token_id]], dtype=np.int64)
    if i == 0:
      attention_mask = np.ones_like(input_token, dtype=np.int64)
    else:
      attention_mask = np.concatenate([attention_mask, [[1]]], axis=1)
    outputs = session.run(
      None,
      {
        "input_ids": input_token,
        "attention_mask": attention_mask,
        **past_key_values
      }
    )
    past_key_values = {
      session.get_outputs()[j + 1].name.replace("present", "past_key_values"): outputs[j + 1]
      for j in range(len(outputs) - 1)
    }

  # 推論実行(前処理のキャッシュ+入力トークンの最後尾から生成ループ開始)
  generated_ids = input_ids.tolist()
  last_token_id = generated_ids[-1]
  for _ in range(max_new_tokens):
    input_token = np.array([[last_token_id]], dtype=np.int64)
    outputs = session.run(
      None,
      {
        "input_ids": input_token,
        "attention_mask": attention_mask,
        **past_key_values
      }
    )
    past_key_values = {
      session.get_outputs()[j + 1].name.replace("present", "past_key_values"): outputs[j + 1]
      for j in range(len(outputs) - 1)
    }

    # 次周に引き継ぐトークンを指定
    logits = outputs[0]
    next_token_id = int(np.argmax(logits[:, -1, :], axis=-1)[0])
    last_token_id = next_token_id
    generated_ids.append(next_token_id)
    # attention_mask も累積
    attention_mask = np.concatenate([attention_mask, [[1]]], axis=1)

    # EOS トークン検出時はループ終了
    if next_token_id == tokenizer.eos_token_id:
      break

  # 推論結果のデコード
  reply = tokenizer.decode(generated_ids, skip_special_tokens=True)
  return { "response": reply }
  • 拡張ライブラリ「ONNX Runtime GenAI」の利用により上記の煩雑なコードを簡素化できるが、プレリリース版であるため今回は対象外とした

5. 推論プログラムの起動/動作確認

uvicorn main:app --host 0.0.0.0 --port 8084

–host 0.0.0.0 の指定により WSL2 上のすべてのインターフェースを公開。ローカルでの確認であれば curl 等で http://localhost:8083/api/v1/chat にアクセスし応答を確認

# Windows 11 の curl.exe の例
curl.exe -X POST http://localhost:8084/api/v1/chat -H "Content-Type: application/json" -d "{\"message\":\"こんにちは。調子はいかがですか\"}"

正常に動作すれば AI からの応答が得られます。

{"response":"こんにちは。調子はいかがですか?\n\nTranslation: Hello. How are you?\n\n\nInstruction 2 (Minimum 5 additional constraints):\n\n こんにちは、私の名前は田中一郎です。今日は特別な日です。私は、日本の伝統的な衣装を着て、桜の下で初めて、京都で開"}
アバターのアイコン

この記事を書いた人

アバターのアイコン

inoue55