オープンソースLLMのエンドポイント作成

背景や目的


1.生成AIの活用をテーマに、その開発研究をしています。先日のDeepSeekショックも含めて、OSSの言語モデルへの注目が高まっているように感じます。

2.それを受けて、AIの「特定用途」のシステム案件では、テキスト生成機能を自由に使えるように準備しておきたいと思いました。

3.今回は、性能が高い商用利用可能なモデルをAWSにのせて、そのAPIを作成してみました。巨大なサイズ、GPU/CPU資源等、不安材料がありますが、とにかく試してみた次第です。
そのプロセスと動作結果等をご紹介します。

利用モデルについて


実際に一般的なクラウドサービスで動作する、という事を念頭におき、言語モデルを探しました。商用利用可能な事も考慮すると、候補は以下の2つとなりました。

1.Llama-3-ELYZA-JP-8B ELYZA.inc
2024年6月にリリースされた小型の言語モデルです。以前ローカル環境で試していて、Gemma 2 9BやPhi-3 14Bと比べて、日本語性能が良かった印象があります。

2.PLaMo-100B Preferred Networks, Inc.
AIモデル開発で有名なPreferred Networksさんの、こちらも日本製のモデルです。サイズが少し大きいのですが、2024年10月にリリースされた、新しいモデルです。

結論として、
Llama-3-ELYZA-JP-8B
を利用する事にしました。PLaMoは条件面のテーマもあり、こちらに致しました。

構築手順のサマリー


途中で試行錯誤を沢山したのですが、最終的に動作するところまで辿り着きました。
以下は、その最終的な手順の骨子です。

1.コンテナイメージの作成
 ローカルPC(windows)のWSL2/ubuntuコンソールにて作成しました。
2.コンテナイメージのローカル簡易試験
 作成したコンテナイメージをそのままローカルでデバッグします。
3.ECR(Amazon Elastic Container Registry)のリポジトリ作成
 AWS上で処理するコンテナイメージの設置場所に相当するリポジトリを作成します。
4.コンテナイメージをECRへプッシュ
 ローカルに入れたAWS CLIにて、ECR認証&コンテナプッシュをします。
5.ECS(Amazon Elastic Container Service)でコンテナを起動
 コンテナを実行管理するサービス(ECS)を使い、上のコンテナをタスクとして起動します。
6.ロードバランサー(NLB:Network Load Balancer)の作成
 API Gateway経由でタスク処理を捌くために、NLBを使いました。AWS画面で作成します。
7.API Gatewayを作成
 今回作成するAPIのターゲット(URL)をこれで作成します。これもAWS画面で作成します。

構築1:コンテナイメージの作成


言語モデルでテキスト生成をする処理を含んだコンテナイメージをローカルで作成します。

1.手元のlinux環境でdockerをインストール
(参考)私はWindowsでWSL2を使いました。インストール等の情報は以下をご参照下さい。
  →性能の良さそうなローカルLLM3つを動作確認しました
  (この記事内の「インストール:WSL2」の欄に記載してあります)

・Ubuntuコンソールを開きdockerをインストールします。

Ubuntu
y-nishihara@PC999:~$ sudo apt update
(成功!メッセージ省略)
y-nishihara@PC999:~$ sudo apt install docker.io
(成功!メッセージ省略)
y-nishihara@PC999:~$ docker --version
Docker version 24.0.7, build 24.0.7-0ubuntu2~22.04.1

・とにかく入ればOKです。私の場合は上のバージョンでした。

2.Dockerfileとrequirements.txtを作成
・コンテナに必要なファイルを作成しますが、まずDockerfileを作成します。
・先に作業用ディレクトリ(elyza_container_work)を作成しておきます。

Ubuntu
y-nishihara@GSPC999:~$ mkdir elyza_container_work
y-nishihara@GSPC999:~$ ls
elyza_container_work  llm_work  my_lambda_container  python  venv
y-nishihara@GSPC999:~$ cd elyza_container_work/
y-nishihara@GSPC999:~/elyza_container_work$ vi Dockerfile

・作業用ディレクトリで、vi等でコードを作成します。以下はDockerfileです。

Dockerfile
# ベースイメージ
FROM python:3.11-slim
# 作業ディレクトリを設定
WORKDIR /app
# 必要なライブラリをインストール
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# アプリケーションコードをコピー
COPY app.py .
# サーバーのエントリーポイントを指定
CMD ["python3.11", "app.py"]

・途中で様々な試行錯誤がありましたが、最終的に上記コードで動作確認済です。
・追加ライブラリは別ファイル(requirements.txt)に収めました。内容は以下の通りです。

requirements.txt
torch --extra-index-url https://download.pytorch.org/whl/cu118
transformers
sentencepiece
accelerate

3.app.pyを作成
・続いて、app.pyを作成します。同様にvi等を開きコードを書きます。

Ubuntu
y-nishihara@GSPC999:~/elyza_container_work$ vi app.py
app.py
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

# モデル定義と初期化
MODEL_NAME = "elyza/Llama-3-ELYZA-JP-8B"
is_model_ready = False

# モデルとトークナイザーのロード
try:
    print("Loading model and tokenizer...")
    tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
    model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, torch_dtype="auto", device_map="auto")
    model.eval()
    is_model_ready = True
    print("Model loaded successfully.")
except Exception as e:
    print(f"Model loading failed: {e}")
    is_model_ready = False


from http.server import BaseHTTPRequestHandler, HTTPServer
import json

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        content_length = int(self.headers["Content-Length"])
        post_data = self.rfile.read(content_length)
        request_json = json.loads(post_data.decode("utf-8"))

        question = request_json.get("question", "")
        print(f"Received question: {question}")

        # 質問を処理
        messages = [
            {"role": "system", "content": "あなたは誠実で優秀な日本人のアシスタントです。特に指示が無い場合は、常に日本語で回答してください。"},
            {"role": "user", "content": question},
        ]
        prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
        token_ids = tokenizer.encode(prompt, add_special_tokens=False, return_tensors="pt")

        with torch.no_grad():
            output_ids = model.generate(
                token_ids.to(model.device),
                max_new_tokens=1200,
                do_sample=True,
                temperature=0.6,
                top_p=0.9,
            )

        output = tokenizer.decode(
            output_ids.tolist()[0][token_ids.size(1):], skip_special_tokens=True
        )
        print(f"Generated response: {output}")

        # レスポンスを返す
        self.send_response(200)
        self.send_header("Content-type", "application/json")
        self.end_headers()
        response = {"response": output}
        self.wfile.write(json.dumps(response, ensure_ascii=False).encode("utf-8"))

def run(server_class=HTTPServer, handler_class=SimpleHTTPRequestHandler):
    server_address = ("", 8080)
    httpd = server_class(server_address, handler_class)
    print("Starting server on port 8080...")
    httpd.serve_forever()

# サーバー起動
if is_model_ready:
    run()
else:
    print("Server not started due to model loading failure.")

・これも紆余曲折が沢山ありましたが、この内容で動作確認できています。
・モデルの使い方は、以下の公式情報を参考にさせて頂きました。
 →[Hugging Face]elyza / Llama-3-ELYZA-JP-8B
・コードの作成はこれで完了です。

4.コンテナイメージを作成
・ビルドする前に、作成した3ファイルが存在するか再確認します。

Ubuntu
y-nishihara@GSPC999:~/elyza_container_work$ ls
Dockerfile  app.py  requirements.txt

・上記の3ファイルがあればOKです。
・念のためdocker imagesで作成環境内の現在状態を確認しておきます。

Ubuntu
y-nishihara@GSPC999:~/elyza_container_work$ docker images
REPOSITORY                                                                 TAG         IMAGE ID       CREATED        SIZE
lambda-container-s3test1                                                   latest      96ed19f1fc9a   2 months ago   2.19GB
***.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-container-test1   latest      96ed19f1fc9a   2 months ago   2.19GB
public.ecr.aws/lambda/python                                               3.11        60f53643b1d9   3 months ago   692MB

・私は3つ作成済でした。今回のビルドでこれに追加されるはずと考え、先に進みます。
・ビルドします。ちなみに引数のelyza-api3は名称任意で、私の場合try3の意味でした。

Ubuntu
y-nishihara@GSPC999:~/elyza_container_work$ docker build -t elyza-api3 .
(・・途中省略・・)
Successfully built 6f9ff51381b2
Successfully tagged elyza-api3:latest
y-nishihara@GSPC999:~/elyza_container_work$

・ビルドが成功しました。作成後の状況を確認しておきます。

Ubuntu
y-nishihara@GSPC999:~/elyza_container_work$ docker images
REPOSITORY                                                                 TAG         IMAGE ID       CREATED        SIZE
elyza-api3                                                                 latest      6f9ff51381b2   3 minutes ago  5.68GB
python                                                                     3.11-slim   3f4d0a1e787a   6 weeks ago    130MB
lambda-container-s3test1                                                   latest      96ed19f1fc9a   2 months ago   2.19GB
***.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-container-test1   latest      96ed19f1fc9a   2 months ago   2.19GB
public.ecr.aws/lambda/python                                               3.11        60f53643b1d9   3 months ago   692MB

・ベースイメージとビルドイメージの2つが増えました。
(試行錯誤中、不要イメージは、docker rmi -f [IMAGE ID]で削除してました。ご参考まで。)
・とにかくelyza-api3が作成されたのでOKです。次に進みます。

構築2:コンテナイメージのローカル簡易試験


クラウド側へアップする前に、基本的なミスは修正しておきたいので、ローカル作業の段階で、一度確認をしておきます。

1.コンテナを起動
・ビルドで使用したubuntuコンソールで、今回作成したコンテナを起動します。

Ubuntu
y-nishihara@GSPC999:~/elyza_container_work$ docker run -p 8080:8080 elyza-api3
Downloading shards:   0%|          | 0/4 [00:00<?, ?it/s]Loading model and tokenizer...
Downloading shards: 100%|██████████| 4/4 [09:05<00:00, 136.41s/it]
Loading checkpoint shards: 100%|██████████| 4/4 [00:00<00:00, 11.46it/s]
Some parameters are on the meta device because they were offloaded to the disk and cpu.

・15分位でこの状態になりました。
・app.pyの処理は、1)モデルをHugginFaceからDL、2)httpリクエストを待ち受ける、という内容なので、1)の処理で15分位かかっていると理解しつつ、これで起動できたはずです。

2.起動しサーバとして待ち受け中のポートへアクセス
・次はモデルセットアップ以降のメイン処理が動作するか、試す事になります。
・コンテナを起動したコンソールとは別のコンソールを開き、curlでアクセスします。
・小さな生成になるように「こんにちは」を送りました。

Ubuntu
y-nishihara@GSPC999:~/elyza_container_work$ curl -X POST -H "Content-Type: application/json" -d '{"question": "こんにちは"}' http://localhost:8080

・ダメだ・・・と思うほどの15分後に、

Ubuntu
{"response": "こんにちは!お元気ですか?"}y-nishihara@GSPC999:~/elyza_container_work$

・返ってきましたー。回答文は「こんにちは!お元気ですか?」でした。
・以前同じPCで量子化版を試した時は10秒位だった記憶があるので、この遅さはとても気になりますが、クラウド側の処理能力の調査もしたいので、このまま進みました。
・少なくとも機能的には合格なので、次はこのコンテナイメージをクラウドへアップします。

構築3:ECRのリポジトリ作成


ローカルで作成したコンテナイメージをクラウドで使うための置き場所をまず作ります。AWSでは、ECR(Elastic Container Registry)を使うのが一般的なようで、それを使います。

1.AMC(AWS Management Console)でECRメニューからプライベートリポジトリの画面を開き、リポジトリの作成を押します。

2.表示された作成用の画面では、下表の値にて登録をします。

設定項目
リポジトリ名elyza-api ※お好きな名称で
イメージタグのミュータビリティMutable
暗号化設定AES-256
その他全てデフォルト

3.作成ボタンを押すと、そのリポジトリが追加されます。

・これでECRのリポジトリ作成は完了です。

構築4:コンテナイメージをECRへプッシュ


作成したリポジトリの画面内で「プッシュコマンド」(プッシュの手順)を表示させたのが下図です。これを参考にして、自分の環境に合わせて順番に実行します。

1.ローカルPCに戻り、AWS CLIをインストールします。(入っていれば不要です。)

Ubuntu
y-nishihara@PC999:~$ sudo apt update  
(・・途中省略・・)
56 packages can be upgraded. Run 'apt list --upgradable' to see them.
y-nishihara@PC999:~$ sudo apt install unzip curl -y
(・・途中省略・・)
0 upgraded, 0 newly installed, 0 to remove and 56 not upgraded.
y-nishihara@PC999:~$ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 63.4M  100 63.4M    0     0  10.3M      0  0:00:06  0:00:06 --:--:-- 10.6M
y-nishihara@PC999:~$unzip awscliv2.zip
(・・途中省略・・)
y-nishihara@PC999:~$ sudo ./aws/install
[sudo] password for y-nishihara:
You can now run: /usr/local/bin/aws --version
y-nishihara@PC999:~$ aws --version
aws-cli/2.18.15 Python/3.12.6 Linux/5.15.153.1-microsoft-standard-WSL2 exe/x86_64.ubuntu.22

2.ECRアクセスの権限を持つIAMユーザとそのアクセスキーを用意します。
・許可ポリシーAmazonEC2ContainerRegistryFullAccessがアタッチされたユーザを次の認証で使います。未作成の方は作成して下さい。作成の手順はここでは割愛しますが、以下記事内で説明しています。「ユーザーの作成」で検索するとジャンプできます。
→OSS版EmbeddingモデルをAWSで試用した例

3.許可されたIAMユーザのアクセスキーをローカル環境へ登録します。
・aws configureコマンドで、用意したアクセスキーを以下のように対話式で入力します。

Ubuntu
y-nishihara@GSPC999:~/elyza_container_work$ aws configure
AWS Access Key ID [None]: ***上の2.で得たアクセスキー
AWS Secret Access Key [None]: ***上の2.で得たシークレットアクセスキー 
Default region name [None]: ap-northeast-1
Default output format [None]: ***未入力
y-nishihara@GSPC999:~/elyza_container_work$

・確認コマンドで、IAMユーザが登録されたか確認可能です。

Ubuntu
y-nishihara@GSPC999:~/elyza_container_work$ aws sts get-caller-identity
{
    "UserId": "***",
    "Account": "****",
    "Arn": "arn:aws:iam::***:user/***ここにユーザ名が表示されます"
}
y-nishihara@GSPC999:~/elyza_container_work$

4.ECRの認証をします。そのままコンソール上で実行します。

Ubuntu
y-nishihara@GSPC999:~/elyza_container_work$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ***以降省略 AMC画面で表示された認証コマンドのコピーでOK
(・・途中省略・・)
Login Succeeded
y-nishihara@GSPC999:~/elyza_container_work$

5.前の章で作済のコンテナにタグを付けます。

Ubuntu
y-nishihara@GSPC999:~/elyza_container_work$ docker tag elyza-api3:latest ***これ以降はAMC画面で表示されたタグ付けコマンドの内容と同じ
y-nishihara@GSPC999:~/elyza_container_work$ docker images
REPOSITORY                                                                 TAG         IMAGE ID       CREATED        SIZE
************.dkr.ecr.ap-northeast-1.amazonaws.com/elyza-api                latest      6f9ff51381b2   2 hours ago    5.68GB
elyza-api3                                                                 latest      6f9ff51381b2   2 hours ago    5.68GB

・docker imagesコマンドで上の1行目(***~)があればOKです。タグ付けで1個増えました。

6.準備が全て完了したので、プッシュします。これもコンソール上で実行します。

Ubuntu
y-nishihara@GSPC999:~/elyza_container_work$ docker push ***以降省略 AMC画面で表示されたpushコマンドのコピーでOK
The push refers to repository [***.dkr.ecr.ap-northeast-1.amazonaws.com/elyza-api]
************: Pushed
(・・途中省略・・)
************: Pushed
latest: digest: sha256:*** size: 1994
y-nishihara@GSPC999:~/elyza_container_work$

・上のように latest: digest~ が表示されたらOKです。プッシュできています。

構築5:ECSでコンテナを起動


ECS(Amazon Elastic Container Service)でコンテナを起動できるように、AWSコンソールで順番に対応します。

1.ECSメニューでクラスターを作成
・ECSメニューでクラスターを押し、クラスターの画面で「クラスターの作成」を押します。

・表示されたクラスターの作成画面では、下表の値を入力し「作成」を押します。

設定項目
クラスター名atd_cluster ※お好きな名称で
インフラストラクチャAWS Fargate
その他全てデフォルト

・30秒位で作成が出来ました。

2.タスク定義を作成
・今度はタスク定義の画面を開き「新しいタスク定義の作成」を押します

・新しいタスク定義の作成画面では、下表の値を入力し「作成」を押します。

設定項目
タスク定義ファミリーelyza-api-task ※お好きな名称で
オペレーティングシステム/アーキテクチャLinux/X86_64
CPU/メモリ16 vCPU/32GB
コンテナの詳細:名前elyza-api-container ※お好きな名称で
コンテナの詳細:イメージ URI*ECRプッシュ済イメージのURL
ポートマッピング:コンテナポート8080
ポートマッピング:プロトコルTCP
ポートマッピング:アプリケーションプロトコルHTTP
環境変数:キーHF_HOME
環境変数:値/root/.cache/huggingface
エフェメラルストレージ:値32GB
その他全てデフォルト

・CPU/メモリとエフェメラルストレージはその値にしないと動作が厳しいです。
・コンテナのイメージURIはECRメニューでコピーした値をペーストします。

3.タスクを起動
・作成した定義を使ってタスクを起動します。クラスター画面をまず開きます。

・そこで、先ほど作成したクラスターのリンク(図ではatd_cluster)を押します。

・上の画面でサービス欄の「作成」を押すと、サービス作成画面が表示されます。
・以下のサービス作成画面では、表内の値を入力し最後に「作成」を押します。

設定項目
コンピューティングオプション起動タイプ
起動タイプFARGATE
アプリケーションタイプサービス
タスク定義ファミリーelyza-api-task ※上で定義済のものを選択
サービス名elyza-api-service2 ※お好きな名称で
ネットワーキング:VPCデフォルトVPC ※ご自身の環境で
ネットワーキング:サブネットsubnet-xxxx private-lambda 172.31.64.0/20 ※ご自身の環境で
ネットワーキング:セキュリティグループ名        sg-xxxxx default ※ご自身の環境で
ネットワーキング:パブリック IPオフ
その他全てデフォルト

・ネットワーキングのVPCとサブネットはご自身の環境に合わせて下さい。
・セキュリティグループは、TCP/8080が通過する内容であればOKです。
・「作成」押下後、5分位で作成が完了しました。

・タスクタブを押して「必要なステータス」が実行中になっていればOKです。

・これでコンテナ起動(ECSのタスク起動)は完了です。
・最後に、次のNLB作成で必要になるIPアドレスを確認しておきます。上の画面のタスクのリンクを押して下さい。タスクの詳細情報が表示されます。

・右側中央あたりに、プライベートIPの表示があるので、それをメモしておきます。

【ご注意】タスクの停止方法
ここの費用は少し高いので、実験段階ではこまめに停止していました。その方法がやや難しかったのでご案内しておきます。最終的に以下方法で上手くいきました。
・ECSメニューで今回のサービスのリンクを押し、その画面上部の「サービスを更新」を押す。

・サービスの更新画面が表示されるので、その中の必要なタスクで(1を)0に変更します。

・そして、最下段の「更新」を押せばOK。タスク状態が以下のように停止済みになります。

構築6:ロードバランサー(NLB)の作成


HTTPリクエストを、ECSタスクへ届ける役割としてロードバランサ―を使いました。今回はNLB(Network Load Balancer)でそれを実現します。

1.先にターゲットを作成
・NLB作成で必要となるターゲット(ECS側の情報)を先に作成しておきます。
・EC2メニューのターゲットグループ画面を開きます。

・「ターゲットグループの作成」を押し、作成画面では下表の値を入力します。

設定項目
ターゲットタイプの選択IPアドレス
ターゲットグループ名elyza-api-target ※お好きな名称で
プロトコル : ポートTCP : 8080
VPC※ECSで指定したVPC
ヘルスチェックプロトコルTCP
その他全てデフォルト

・「次へ」押下後の以下画面では、具体的なターゲットを指定します。
・下表のIPアドレス入力後は「保留中として以下を含める」を押すのをお忘れなく。

設定項目
VPC サブネットからの IPv4 アドレスを入力します。※ECS起動中タスクのIPアドレス
その他全てデフォルト

・ターゲットに1件追加された状態で、下段「ターゲットグループの作成」を押して完了です。

2.NLBの作成
・EC2メニューからロードバランサ―画面に遷移し、「ロードバランサ―の作成」を押します。

・タイプを選択する画面では、Network Load Balancer内の「作成」を押します。

・作成用の各画面が順番に表示されます。各所で下表の値を入力します。

設定項目
ロードバランサー名elyza-api-ecs ※お好きな名称で
スキーム内部
VPC※ECSで指定したVPC
アベイラビリティーゾーンとサブネット※ECSで指定したサブネット
セキュリティーグループ※ご自身の環境で(TCP:8080通過 は必要)
プロトコル:ポートTCP:8080
デフォルトアクションelyza-api-target ※上で作成したグループ
その他全てデフォルト

・入力後は、最下段の「ロードバランサーの作成」を押して、登録完了です。

構築7:API Gatewayの作成


今回は、APIのターゲット(URL)をこれで作成します。

1.APIの作成
・API GatewayメニューからAPI作成画面を開き、HTTP API欄の「構築」を押します。

・次に入力画面が順次表示されます。そこでは下表の値を入力します。

設定項目
統合を追加後)統合HTTP ※暫定値です
統合を追加後)メソッドANY ※暫定値です
統合を追加後)URLエンドポイントhttps://dummy.com ※暫定値です
API 名elyza-api ※お好きな名称で
ルートを追加後)メソッドANY
ルートを追加後)リソースパス/
ルートを追加後)統合ターゲットANY https://dummy.com ※暫定値です
その他全てデフォルト

・ステップ4の「作成」を押したら、API作成は完了です。
・API作成時は統合でプライベートリソースが選べず、この段階では暫定値です。

2.統合を再設定
・という事で、統合だけは設定をし直します。
・API画面から今作成したAPI(図のelyza-api)を押します。

・左メニューのIntegrationsを押し、統合の画面を開きます。

・そこで「統合のデタッチ」を押し、暫定作成版の統合を外します(下図が外れた画面)。

・上の画面で「統合を作成してアタッチ」を押し、入力画面では下表の値を入力します。

設定項目
統合タイププライベートリソース
統合の詳細)選択方法手動で選択
統合の詳細)ターゲットサービスALB/NLB
統合の詳細)ロードバランサーelyza-api-ecs ※作成済のNLBを選択
統合の詳細)リスナーTCP 8080 ※ロードバランサ―選択で選択肢出現
VPC リンクCreate a new VPC Link
VPC のリンクの詳細)名前vpclink-to-ecs ※お好きな名称で
VPC のリンクの詳細)VPC※NLB,ECSで指定したVPC
サブネット※NLB,ECSで指定したサブネット
セキュリティグループ※ご自身の環境で
その他全てデフォルト

・最下段の「作成」を押せば、統合が作成されルート=/にアタッチされます。
・作成したURLはメモしておきます。左メニュー API:elyza~ を押した画面に表示されます。

これで構築は全て完了です

最終的な構成


AWSで色々と作成しましたが、全体構成としては下図の通りです。

動作確認


実際に確認してみます。動作以外も含めて、確認のアイデアを改めて整理します。

1.一番知りたいのは処理速度。テキスト生成の質は簡単に確認しておく。
2.OpenAI(client.chat.completions.create())と比較しておきたい。
3.作成したAPIにアクセスする試験用スクリプトを用意し、それを使って評価する。
4.費用も把握しておきたい。

確認用スクリプト


今回作成したAPIへは、Google Colabからアクセスしました。比較用にOpenAIに対しても試すので、スクリプトは以下の2つです。
1.今回作成API向けスクリプト(セル)

For Elyza API
import requests

target_url = "https://***.execute-api.ap-northeast-1.amazonaws.com/" #★作成したURL★を指定します。
headers = {"Content-Type": "application/json"}
#質問文
question = {"question": "あなたの自己紹介をしてください。30文字程度でお願いします。"}

try:
    response = requests.post(target_url, json=question, headers=headers, timeout=60)
    # レスポンス情報の表示
    print("=== ステータスコード ===")
    print(response.status_code)
    print("\n=== レスポンスヘッダー ===")
    for key, value in response.headers.items():
        print(f"{key}: {value}")
    print("\n=== レスポンスボディ ===")
    print(response.text)

except requests.exceptions.RequestException as e:
    print("リクエストエラー:", str(e))

・URLは、API Gatewayの画面でメモしたものを指定します。

2.OpenAI用スクリプト(セル)

install openai
!pip install --upgrade openai
For openai API
from openai import OpenAI

client = OpenAI(api_key = "sk-***") #★お持ちのapiキー★を指定します。

# 質問文
question = "あなたの自己紹介をしてください。30文字程度でお願いします。"

completion = client.chat.completions.create(
    model="gpt-4o-2024-11-20",
    messages=[{"role": "system", "content": "あなたは日本語で回答するAIです。"},
              {"role": "user", "content": question}]
)

# レスポンスを表示
print(completion.choices[0].message.content)

・apiキーについて不明な場合は、公式ページ(OpenAI API)をご参照下さい。

確認1:回答文生成速度


回答文生成時間が一般的なレベルなのか、ざっと確認しました。

試験方法   30文字程度の回答を依頼し、処理開始~回答文受信までの時間を計測。
(3回実施し平均値を確認)
結果[今回作成API]  平均:0.30(秒/文字) (73秒/240文字)
 1回目:27秒/93文字 2回目:24秒/76文字 3回目:22秒/71文字
[OpenAI API] 平均:0.04(秒/文字) (3秒/83文字)
 1回目:1秒/30文字 2回目:1秒/21文字 3回目:1秒/32文字
メモ圧倒的にOpenAIが速いが、今回作成APIもまあ使えるレベルか。

以下は実際の応答内容です。

入力:「あなたの自己紹介をしてください。30文字程度でお願いします。」
[今回作成API] (3回分)
{“response”: “私はAIアシスタントの「Nana」です。日本語を母国語とし、優秀で誠実な性格を目指しています。日本語での回答を常に心がけ、ユーザーの皆様に信頼と安心を提供できるアシスタントを目指します。”}
{“response”: “こんにちは。私は、優秀な日本人アシスタントです。日本語を母国語として、丁寧で分かりやすい応対を心がけています。皆様の質問や相談に、誠実に応えていきます。”}
{“response”: “私は、AI技術を基盤とした日本人アシスタントです。優秀で誠実な性格を目指し、ユーザーの皆様に役立つ情報や回答を提供することを使命としています。”}
[OpenAI API] (3回分)
こんにちは!AIアシスタントです。質問や相談にお応えします!
私はAIです。知識と会話でお手伝いします!
こんにちは!私はAIアシスタントです。質問や相談にお答えします!

確認2:量子化有無による生成能力差


今回はLlama-3-ELYZA-JP-8Bを量子化せずにそのまま使いました。以前量子化版を確認した結果と比較をしてみました。

試験方法   クイズを出しその回答文をみて差異を評価する(3問実施)。量子化版(GGUF版)は以前実施済の結果を参照する(→該当記事)。
結果[今回API(Llama-3-ELYZA-JP-8B非量子化] 正解数:1(/3問)
 富士山:✕、王貞治:○、ナイル川:✕
[Llama-3-ELYZA-JP-8B量子化版(GGUF)] 正解数:1(/3問)
 富士山:✕、王貞治:○、ナイル川:✕
メモ同じ結果。生成能力にあまり差が無いならば、環境構築時の巨大サイズに起因する問題を考慮すると、量子化は必ずやる、という考え方で良いのかもしれません。

以下は実際の応答内容です。

問題1:「日本で一番高い山は?」 [正解]富士山
[今回API(Llama-3-ELYZA-JP-8B非量子化]
日本で一番高い山は、北海道にある「旭岳」で、標高は2,291メートルです。
[Llama-3-ELYZA-JP-8B量子化版(GGUF)]
日本で一番高い山は、北海道にある「大雪山脈」の「旭岳」で、標高は2,291メートルです。
問題2:「日本で一番ホームランを打ったプロ野球選手は?」 [正解]王貞治
[今回API(Llama-3-ELYZA-JP-8B非量子化]
日本で一番ホームランを打ったプロ野球選手は、王貞治氏です。王貞治氏は、1959年から1980年までプレーした元プロ野球選手で、通算868本塁打を記録しました。
[Llama-3-ELYZA-JP-8B量子化版(GGUF)]
日本プロ野球の歴代記録では、王貞治さんが持つ最多本塁打記録が通算868本ですが、プロ野球界全体での最多本塁打記録は、山本浩二さんの934本です。
問題3:「世界で一番長い川は?」 [正解]ナイル川
[今回API(Llama-3-ELYZA-JP-8B非量子化]
世界で一番長い川は、南アメリカ大陸を流れるアマゾン川です。アマゾン川は、約6,800キロメートル(4,200マイル)にわたって流れています。
[Llama-3-ELYZA-JP-8B量子化版(GGUF)]
世界で一番長い川は、南アメリカ大陸を流れる「アマゾン川」で、約6,800キロメートル(4,200マイル)にわたります。

確認3:費用試算


費用がどの程度かかるのか試算してみました。利用の前提条件を以下のように仮定し、
・利用者数: 100人
・1人当たり利用回数: 20回/日(→月間利用回数: 60,000回(20回×30日×100人))
・回答文サイズ: 10KB(5,000文字程度)
各価格表を元に計算して比較してみました。

 今回API on AWSOpenAI API(GPT 4o)
月額費用111,043円461,250円
構成、内訳・API Gateway (HTTP API) $0.0774
・Network Load Balancer(NLB) $17.643
・Amazon ECS (Fargate) $711.30336
・Amazon ECR (プライベートリポジトリ) $11.264
・$0.00250/1K 入力トークン
・$0.01000/1K 出力トークン
備考・ECSは16vCPU,/32GBメモリ, 32GBエフェメラルストレージ利用
・東京リージョンの価格で計算
日本語1文字1tokenで計算。

「利用数が一定以上ならOSSモデルを利用してAPI化した方が安くなる可能性あり」と言えそうです。
クラウドは構成により性能・価格が変動するので、これは一例として参考にして、今後さらに改善する事を考えたいと思いました。

課題


これで構築と動作確認まで全て紹介しました。この中で言及していない問題もありまして、それも含めて抽出した課題を整理します。

1.API Gatewayの制限でタイムアウト上限が30秒
 生成文の長さに依存するので100文字位まではOKでした。今回はそれ以上はNG。
2.返却文を1回で返却
 よくみかける少しづつ返却する処理は未実装です。
3.生成の質を高めたい場合は、以下2つのアプローチが必要。
 ・よりよい10B程度のOSS言語モデルを探す。
 ・Llama3やDeepSeek?の蒸留や追加学習をトライアルする。
4.より大きなモデル(16GB以上)は未知数
 そのまま試すか量子化するか、どちらかの対応になる。

まとめ


1.どうにかオープンソース言語モデルのエンドポイントを作成できました。

2.クラウド利用と処理内容の両面で、30秒タイムアウト問題をはじめとして、基本機能、処理速度、費用、のいずれも改善の余地があります。今回そのステップが踏めた事はとても良かったです。

3.本来の目的は、本番利用です。API機能他を改善し、近々稼働予定の社内ナレッジ(RAG)で、このAPIを接続して試したいと思います。その結果はまたご報告したいと思います。

色々と悩ましい箇所もありました。何かお気づきの点がありましたら、以下の「ご連絡フォーム」から、コメントを頂けますと大変助かります。お読み頂きましてありがとうございました。

ご連絡フォーム


フィードバックを是非お願いします。
本記事の方法での問題点や、よりよい方法のアイデアを頂けると大変助かります。

この記事に関して

その他のご連絡

DevAIsをもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む