量子化LLMのAPIをSagemakerで作成した例

背景や目的


1.生成AIの活用をテーマに開発研究をしています。AIへの注目度は引き続き高いのですが、企業が具体的な効果を得るのはまだこれから、と理解しています。

2.その効果を得るために「企業独自のデータから新たな知恵を得る」という点に着目していますが、技術の方法としては検索で補う(RAG)かLLMを追加学習するか、の2通りありそうです。

3.今回は後者の独自モデル利用に注目し、その時に必要となりそうなOSSモデルのAPI作成を実施しました。実用レベルで試したく、LLMはDeepSeek-R1(32B版)、API環境はAmazon SageMakerをそれぞれ使いました。追加学習して質が高そうなLLM、自由度が高く現実的な費用のAWS環境、という観点でそれぞれ選び、とにかく構築しました。
既出の情報が少なかった印象でした。一つの動いた例としてお役に立てれば幸いです。

構築方針


以前の実験では以下の課題がありました。
【課題1】タイムアウト制限の回避 (AWS API Gateway利用時の問題)
 その手の制限にひっかからないようにする。
【課題2】回答文の逐次表示 (ChatGPT等でよくみかける少しづつ表示させる機能)
 UXを考慮して、このレベルも考慮しておく。
【課題3】優れたモデルを利用
 追加学習する場合、ベースモデルの質の高さも重要と考えています。

これらを考慮し、構築では以下を採用しました。

1.SageMakerAIのリアルタイム推論
タイムアウト制限が無く、推論コードを自由に書けて遂次処理ができそうなこの方法を採用しました。カスタムコンテナの使い方で試行錯誤がありましたが、最終的に動きました。
以下の記事を参考にさせて頂きました。
Amazon SageMaker におけるカスタムコンテナ実装パターン詳説
(記事中「4 つの実装パターン」のパターン3に相当する使い方となります。ご参考まで。)

2.cyberagent/DeepSeek-R1-Distill-Qwen-32B-Japaneseの量子化版
高性能と言われるDeepSeek-R1の蒸留版をcyberagentさんが日本語強化されたものを使用させて頂きました。GPU費用を考慮し、それを量子化して使いました。

構築手順のサマリー


試行錯誤もありましたが、最終的には以下の手順で動作をしています。

1.コンテナイメージの作成
 ローカルPC(windows)のWSL2/ubuntuコンソールで作成。
2.ECR(Amazon Elastic Container Registry)のリポジトリ作成
 AWS上で処理するコンテナイメージの設置場所に相当するリポジトリを作成します。
3.コンテナイメージをECRへプッシュ
 ローカルに入れたAWS CLIにて、ECR認証&コンテナプッシュをします。
4.モデル(量子化済)をS3へアップ
 モデルを調達し、それをS3へアップしておきます。あとでSageMakerが参照します。
5.SageMakerAIメニューでの操作
 モデル作成、エンドポイント設定等、必要な操作をしエンドポイントを作成します。
6.エンドポイント自動作成&削除の構築
 ”現実的な価格”を考慮し、営業時間内のみ起動するようにします。LambdaとEventBridgeで実現します。

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


推論処理をコンテナイメージにします。量子化版だという点と、コンテナがSageMakerで起動可能だという事に注意して作成しました。手順は以下の通りです。

1.docker環境の準備
ローカルで使えるようにします。ここは必要に応じて以前の記事をご参照下さい。
 →性能の良さそうなローカル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を作成します。
・適当な作業用ディレクトリを作成しておきます。

Ubuntu
y-nishihara@GSPC999:~$ mkdir reform_api_container_work_awq
y-nishihara@GSPC999:~$ cd reform_api_container_work_awq/
y-nishihara@GSPC999:~/reform_api_container_work_awq$

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

Dockerfile
# ベースイメージを指定(Python 3.11)
FROM python:3.11

# SageMaker のワークディレクトリを指定
WORKDIR /opt/ml/code

# 必要なファイルをコピー
COPY requirements.txt .
COPY requirements2.txt .
COPY app.py .

# 必要なパッケージをインストール
RUN pip install --upgrade pip
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install --no-cache-dir -r requirements2.txt

# ポート8080を開放(SageMaker の仕様)
EXPOSE 8080

# エントリーポイントを指定
ENTRYPOINT ["python", "app.py"] #SageMakerの docker run <image> serve に対応

21行目はSageMaker仕様に合わせた箇所で、これで起動します。他も色々とありましたが、最終的に上記コードで動作しています。
・追加ライブラリはautoawqが上手く入らず、以下の2ファイル(requirements.txtとrequirements2.txt)に分けて収めました。内容は以下の通りです。

requirements.txt
https://download.pytorch.org/whl/cu124/torch-2.6.0%2Bcu124-cp311-cp311-linux_x86_64.whl
https://download.pytorch.org/whl/cu124/torchvision-0.21.0%2Bcu124-cp311-cp311-linux_x86_64.whl
transformers
sentencepiece
accelerate
Flask
multi-model-server
sagemaker-inference
numpy<2
requirements2.txt
autoawq

3.app.pyを作成
推論処理のapp.pyを用意します。
・同様にvi等でコードを書きます。

Ubuntu
y-nishihara@GSPC999:~/elyza_container_work$ vi app.py

最終的なコードは以下の通りです。

app.py
from flask import Flask, request, jsonify, Response
import torch
from awq import AutoAWQForCausalLM #AWQ版
from transformers import AutoTokenizer,TextIteratorStreamer
import os
import time
import threading
import sys

app = Flask(__name__)

IS_SAGEMAKER = True

if IS_SAGEMAKER:
    MODEL_DIR = "/opt/ml/model"  # SageMaker環境では S3 から展開されたモデルを使用
else:
    MODEL_DIR = ""  # ローカル試験する場合の予備部分

print(f"Loading model from {MODEL_DIR} ...")

tokenizer = AutoTokenizer.from_pretrained(MODEL_DIR)
model = AutoAWQForCausalLM.from_quantized(MODEL_DIR,device="cuda",fuse_layers=True,trust_remote_code=True) #AWQ版

@app.route("/ping", methods=["GET"])
def ping():
    """ヘルスチェック用エンドポイント"""
    return jsonify({"status": "healthy"}), 200

def generate_text(system_prompt,question):
    if system_prompt == "":
        system_prompt = f"あなたは誠実で優秀な日本人のアシスタントです。以下の質問に対して、1問1答で答えてください。\n\n質問:{question}\n回答:" #R1を考慮したプロンプト
    inputs = tokenizer(system_prompt, return_tensors="pt").to("cuda")

    streamer = TextIteratorStreamer(tokenizer, skip_special_tokens=True,skip_prompt=True) #回答文だけ返却する

    generation_kwargs = {
        "input_ids": inputs["input_ids"],
        "attention_mask": inputs["attention_mask"],
        "max_new_tokens": 5000,
        "do_sample": False, 
        "pad_token_id": tokenizer.eos_token_id,
        "streamer": streamer
    }
    thread = threading.Thread(target=model.generate, kwargs=generation_kwargs) #別スレッドで生成を実行
    thread.start()
    for new_text in streamer: #逐次出力
        yield new_text
    yield "\n"

@app.route("/invocations", methods=["POST"])
def invocations():
    data = request.json
    system_prompt = data.get("system_prompt", "")
    question = data.get("question", "")
    return Response(generate_text(system_prompt,question), content_type="text/plain", status=200)

if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "serve": #SageMakerの docker run <image> serve を受け、その引数が入るはず
        app.run(host="0.0.0.0", port=8080, threaded=True)
    else:
        print("This container is for SageMaker! (atd)")

「#AWQ版」 をつけた箇所は、量子化モデルを扱うために考慮した部分です。
遂次出力は44行目~48行目で実行しています。その他ここも色々とありましたが、この内容で動作確認済です。
これでコードの準備は完了です。

4.コンテナイメージを作成
最後にコンテナイメージを作成します。
・4ファイルの存在確認をし、よければビルドします。

Ubuntu
y-nishihara@GSPC999:~/reform_api_container_work_awq$ ls
Dockerfile requirements2.txt app.py requirements.txt
y-nishihara@GSPC999:~/reform_api_container_work_awq$ docker build -t reform-api-deepseek310-10 .
(・・途中省略・・)
Successfully tagged reform-api-deepseek310-10:latest
y-nishihara@GSPC999:~/reform_api_container_work_awq$

・念のため作成後の状況を確認しておきます。以下のようにimageが増えたはずです。

Ubuntu
y-nishihara@GSPC999:~/reform_api_container_work_awq$ docker images
REPOSITORY                                                                 TAG         IMAGE ID       CREATED        SIZE
reform-api-deepseek310-10                                                  latest      ************   ************   7.32GB

imageが作成できました。次に進みます。

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


ローカルで作成したコンテナイメージをクラウドで使うための置き場所として、ECR(Elastic Container Registry)のリポジトリを作成します。

・AWSコンソールでECRメニューからプライベートリポジトリの画面を開き、リポジトリの作成を押します。

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

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

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

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

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


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

・ローカル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

・ECRアクセスの権限を持つIAMユーザとそのアクセスキーを用意します。許可ポリシーAmazonEC2ContainerRegistryFullAccessがアタッチされたユーザであればOKです。作成の手順は、以下記事をご参照下さい(「ユーザーの作成」で検索できます)。
→OSS版EmbeddingモデルをAWSで試用した例

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

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

・IAMユーザが登録されたか以下コマンド確認しておきます。

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

・ユーザの準備を終えたので、ECRの認証に進みます。コンソール上で実行します。

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

・次は、前の章でビルドしたコンテナにタグを付けます。

Ubuntu
y-nishihara@GSPC999:~/reform_api_container_work_awq$ docker tag reform-api-deepseek310-10:latest **(これ以降はAWS画面で表示されたタグ付けコマンドの内容と同じ)
y-nishihara@GSPC999:~/reform_api_container_work_awq$ docker images
REPOSITORY                                                                 TAG         IMAGE ID       CREATED        SIZE
************.dkr.ecr.ap-northeast-1.amazonaws.com/reform-api-deepseek      latest      ************   ************   7.32GB
reform-api-deepseek310-10                                                  latest      ************   ************   7.32GB

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

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

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

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

構築4:モデル(量子化済)をS3へアップ


推論モデルのファイルをS3にアップしておきます。後でSageMakerから参照させます。

1.量子化されたモデルを用意
試したいAWQ版のモデルファイルをご用意下さい。私が作成した量子化版は以下にあります。
https://huggingface.co/nishihara-ATD/DeepSeek-R1-Distill-Qwen-32B-Japanese-AWQ
トライアルで必要な場合はここからダウンロードしてお使い下さい。(量子化自体のプロセスは今回は割愛させて頂きます。)

2.AWS S3の準備
とにかくバケットを用意します。既存で使えるバケットがあればそれでも大丈夫です。
・AWSコンソールでS3メニューを開き、「バケットの作成」を押します。

・作成画面では、下表の値を入力し「バケットを作成」を押します。

設定項目
S3バケット名abc-reform-api ※お好きな名称で
(その他)(全てデフォルト)

・そして、そのバケット内にモデルのフォルダを作成しておきます。私は以下を作成しました。

 
S3バケット名DeepSeek-R1-Distill-Qwen-32B-Japanese-AWQ-ATD ※お好きな名称で

これで、S3の準備は完了です。

3.S3へモデルファイルをアップロード
私がアップロードしたのは以下のファイルでした。

No.
1config.json
2generation_config.json
3model-00001-of-00004.safetensors
4model-00002-of-00004.safetensors
5model-00003-of-00004.safetensors
6model-00004-of-00004.safetensors
7model.safetensors.index.json
8special_tokens_map.json
9tokenizer_config.json
10tokenizer.json

・試したいAWQ版モデルのファイルを手元に用意したら、それを作成したバケットにアップロードします。以下はGoogle colabでアップしたコードです。(手動でも良いのですが遅いです。)

Colab
/content# curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
/content# unzip awscliv2.zip
/content# ./aws/install
/content# aws --version
/content# aws configure
AWS Access Key ID [None]: ***アクセスするIAMユーザのアクセスキー
AWS Secret Access Key [None]: ***アクセスするIAMユーザのシークレットアクセスキー
Default region name [None]: ap-northeast-1
Default output format [None]:
/content# 
aws s3 cp --recursive \
/content/drive/MyDrive/model/***手元の用意したモデルフォルダ \
s3://abc-reform-api/DeepSeek-R1-Distill-Qwen-32B-Japanese-AWQ-ATD/

アクセス可能なユーザでcopyされるので、適当なIAMユーザで実行しています。
また、手元ファイルはgoogle driveに事前にセットしておきました。
・完了したらS3メニューで確認しておきます。

これでS3へのアップロードは完了です。

構築5:SageMakerAIメニューでの操作


モデルと推論処理が参照できる状態になったので、ここでまとめ上げるイメージです。

1.モデルを作成
SageMakerのモデル」が必要なので、それを作成します。
・AWSコンソールでSageMaker AIメニューを開き、推論→モデルを選択します。

・そこで「モデルの作成」を押し、作成画面では下表の値を入力し、最後に「モデルの作成」を押します。

設定項目
モデル名deepseek-awq ※お好きな名称で
IAMロールAmazonSage*** ※自動作成をお勧めします
コンテナ入力オプションモデルアーティファクトと推論イメージの場所を指定します。
モデル圧縮タイプUncompressedModel ※S3側と合わせる
S3 データタイプS3Prefix ※S3側と合わせる
推論コードイメージの場所***.dkr.ecr.ap-northeast-1.amazonaws.com/reform-api-deepseek:latest
*ECRにセットしたイメージのURIです。
アーティファクトの場所s3://abc-reform-api/DeepSeek-R1-Distill-Qwen-32B-Japanese-AWQ-ATD/
*作成したモデルファイルのS3 URIです。
(その他)(全てデフォルト)

・作成後は、一覧画面で確認しておきます。(直ぐ作成されるはずです。)

これで、SageMakerのモデル作成は完了です。

2.エンドポイント設定の作成
エンドポイントを作るための情報を登録する箇所となります。
・同様にSageMaker AIメニューで、推論→エンドポイント設定を選択します。

・そこで「エンドポイント設定の作成」を押し、作成画面では下表の値を入力し、最後に一番下の「エンドポイント設定の作成」を押します。

設定項目
エンドポイント設定名deepseek-awq-endpoint1 ※お好きな名称で
エンドポイントのタイププロビジョン済み
暗号化キー – オプションカスタム暗号無し
非同期呼び出し設定オフ (デフォルト)
データキャプチャを有効にするオフ (デフォルト)
本番稼働用バリアントを作成/モデルの追加deepseek-awq ※上で作成したモデル
[バリアント編集モーダル/バリアント名]deepseek-awq-variant1 ※お好きな名称で
[バリアント編集モーダル/インスタンスタイプ]ml.g6.xlarge ※NVIDIA L4/GPUメモリ24GB/メモリ16GB
[バリアント編集モーダル/初期インスタンス数]1 ※複数起動が可能そうだが、まずは1
[バリアント編集モーダル/初期重量]1 ※複数起動の振り分けらしい。自動的に1
[バリアント編集モーダル/モデルデータのダウンロードタイムアウト](空欄) ※Default 60分のまま
[バリアント編集モーダル/コンテナの起動タイムアウト]1200 ※20分にした(Default 300秒)

※インスタンスタイプml.g6シリーズは、私の場合はそのままでは使えませんでした。使えない場合はAWSコンソールのService Quotasメニューでリクエストをして下さい。サービス=Amazon SageMaker、クオータ名称=ml.g6.xlarge for endpoint usageを指定したリクエストをして、直ぐに使えるようになりました。
・一覧画面を表示させると、作成されたか確認できます。

3.エンドポイントの作成
準備は終わったので、あとは起動するだけです。
・同様にメニューから、推論→エンドポイントを選択します。

・そこで「エンドポイントの作成」を押し、作成画面では下表の値を入力し、下にある「エンドポイントの作成」を押します。

設定項目
エンドポイント名deepseek-awq-endpoint1-exec1 ※お好きな名称で
エンドポイント設定のアタッチ既存のエンドポイント設定の使用
エンドポイント設定deepseek-awq-endpoint1
※上で作成したものを選択し「エンドポイント設定の選択」を押す

・作成後は確認をします。まず左の画面のようにステータス=Creatingで表示され、モデルのロードが終了したらInserviceに変わります。(7,8分かかります)

右の画面が表示されたら、全て完了です。アクセス可能な状態になりました。

構築6:エンドポイント自動作成&削除の構築


APIが使える状態になったので試してみますが、その前に構築の最後のところを、さらっとご紹介します。営業時間外には削除して費用削減しておきたく、その設定です。

1.エンドポイント作成削除処理の作成 (Lambdaの作成)
まず処理部分が必要です。今回はAWS Lambdaで作成します。
・AWSコンソールでLambdaメニューを開き関数を一つ作成します。設定値は下表の通りです。
(Lambda関数の作成方法は、必要に応じてこちらをご参照下さい→AWS公式サイト

設定項目
関数の作成一から作成
関数名reform_api_endpoint_make_and_delete ※お好きな名称
ランタイムPython3.11
アーキテクチャx86_64
実行ロール既存のロールを使用する
既存のロールatd_role_for_lambda *お手元の適当なロールで
タイムアウト(作成後に設定)30 秒 *少し増やしておきました。
(その他)(全てデフォルト)

・作成後は以下の様に関数一覧に表示されます。ご確認下さい。

・その処理でSageMakerにアクセスできるようにロールに下表の権限を追加します。
(IAMロールの画面操作は、必要に応じてこちらをご参照下さい→AWS公式サイト

設定項目
許可ポリシーAmazonSageMakerFullAccess

・コードは以下の通りです。Lambda関数内コードとしてセットすればOKです。

lambda_function.py
import json
import boto3

def lambda_handler(event, context):
    print("----START ENDPOINT ACTION----\n")
    sagemaker = boto3.client("sagemaker")
    
    endpoint_name = "deepseek-awq-endpoint1-exec1"
    config_name = "deepseek-awq-endpoint1"#作成したエンドポイント設定の名前を指定
    action = event.get("action", "")

    # 存在チェック
    try:
        sagemaker.describe_endpoint(EndpointName=endpoint_name)
        exists = True
    except sagemaker.exceptions.ClientError as e:
        if "Could not find" in str(e):
            exists = False
        else:
            raise e

    if action == "delete":
        if exists:
            print(f"エンドポイント {endpoint_name} を削除します")
            sagemaker.delete_endpoint(EndpointName=endpoint_name)
        else:
            print(f"エンドポイント {endpoint_name} は既に存在しません")
    
    elif action == "create":
        if exists:
            print(f"エンドポイント {endpoint_name} は既に存在しています")
        else:
            print(f"エンドポイント {endpoint_name} を作成します(設定: {config_name})")
            sagemaker.create_endpoint(
                EndpointName=endpoint_name,
                EndpointConfigName=config_name
            )
    
    else:
        print(f"無効なアクションです: {action}")
    
    return {
        "statusCode": 200,
        "body": json.dumps(f"{action} 処理が完了しました")
    }

これでLambdaの部分は完了です。

2.EventBridgeの設定
時間が来たら上の処理を実行するように設定します。
・コンソールでAmazon EventBridgeメニューを開き、下表の値でスケジュールを作成します。
(EventBridgeの画面操作は、必要に応じてこちらをご参照下さい→AWS公式サイト
まず起動用です。

スケジュール1:起動用
名前invoke_reform_api_endpoint_make ※お好きな名称で
説明APIサービス開始時刻にエンドポイントを作成する。
スケジュールグループDefault
[スケジュールのパターン/頻度]定期的なスケジュール
[スケジュールのパターン/タイムゾーン](UTC+09:00) Asia/Tokyo
[スケジュールのパターン/スケジュールの種類]cron ベースのスケジュール
[スケジュールのパターン/cron 式]30 8 * * ? *  ※朝に発動
[スケジュールのパターン/フレックスタイムウィンドウ]オフ
[ターゲットの詳細/ターゲット API]テンプレート化されたターゲット→AWS Lambda Invoke
[Invoke/Lambda 関数]reform_api_endpoint_make_and_delete
※上で作成したLambda関数を選択
[Invoke/ペイロード]{“action”: “create”}
※関数に渡すパラメータです。
[アクセス許可/実行ロール]このスケジュールの新しいロールを作成
*お手元の環境でお選びください
[アクセス許可/ロール名]Amazon_EventBridge_Scheduler_LAMBDA_82be**
*私は自動作成させ適当な名前を付けました。
(その他)(全てデフォルト)

・続いて、同様に削除用も作成します。設定値は下表の通りです(起動用と変わる部分のみ記載)。

スケジュール2:削除用
名前invoke_reform_api_endpoint_delete ※お好きな名称で
説明APIサービス終了時刻にエンドポイントを削除する。
[スケジュールのパターン/cron 式]30 18 * * ? *  ※営業終了時に発動
[Invoke/ペイロード]{“action”: “delete”}
※関数に渡すパラメータです。
[アクセス許可/実行ロール]既存のロールを使用
[アクセス許可/ロール名]Amazon_EventBridge_Scheduler_LAMBDA_82be**
*起動用で自動作成したもの。

・2件作成後は以下の様にスケジュール一覧に表示されます。ご確認下さい。

これで、自動作成と削除の設定は完了です。
そして、全ての構築が完了しました。

最終構成


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

動作確認(準備)


クライアント側の環境はGoogle Colabのセルを使用し、そこそこのスピードで推論結果のテキストが遂次表示されか、試してみました。

・Colabを準備したら、必要なライブラリをまずインストールします。スクリプトは以下の通りです。

Colab/cel1
###Deepseek-AWQ版Sagemakerエンドポイント作成後の表示確認 インストール
!pip install boto3
!pip install requests-aws4auth

aws認証と遂次表示にこれらを使います。
・次に推論のコードを用意します。以下がスクリプトです。

Colab/cel2
###Deepseek-AWQ版Sagemakerエンドポイント作成後の表示確認 推論コード
import boto3
import json

# IAMユーザーの認証情報
aws_access_key = "***" # 適当なIAMユーザのアクセスキー
aws_secret_key = "***" # 適当なIAMユーザのシークレットアクセスキー
region = "ap-northeast-1"
# boto3 セッション作成
session = boto3.Session(aws_access_key_id=aws_access_key,aws_secret_access_key=aws_secret_key,region_name=region)

# SageMaker Runtime クライアント作成
client = session.client("sagemaker-runtime")

payload = {
    "system_prompt": "",
    "question": "世界で一番長い川は?"
}

response = client.invoke_endpoint_with_response_stream(
    EndpointName="deepseek-awq-endpoint1-exec1",#作成したエンドポイント名を指定
    ContentType="application/json",
    Body=json.dumps(payload).encode("utf-8")
)

##表示処理----------------------------------------------
import time
import threading
#スピナー表示用(点滅アニメーション)
stop_blinking = False
def blink_confirming():
    dots = ["・", "・・", "・・・", "・・・・"]
    i = 0
    while not stop_blinking:
        print(f"\r受信中{dots[i % len(dots)]}", end="", flush=True)
        i += 1
        time.sleep(0.5)
blink_thread = threading.Thread(target=blink_confirming)
blink_thread.start()
#回答文表示
has_think_ended = False
buffer = ""
start_time = time.time()
timeout_sec = 0 #</think>受信まで待ってからそれ以降を表示させたい時に指定
for event in response['Body']:
    if 'PayloadPart' in event:
        chunk = event['PayloadPart']['Bytes'].decode("utf-8")
        if not has_think_ended:
            buffer += chunk
            if "</think>" in buffer:
                has_think_ended = True
                stop_blinking = True
                blink_thread.join()
                after_think = buffer.split("</think>")[-1]
                print(after_think, end="", flush=True)
                buffer = ""
            elif time.time() - start_time > timeout_sec:
                has_think_ended = True
                stop_blinking = True
                blink_thread.join()
                print("\n",buffer, end="", flush=True)
        else:
            print(chunk, end="", flush=True)

・コード中「適当なIAMユーザ」には、Sagemakerの以下の権限をAWSで追記しておいてください。推論実行時にエラーが出てしまいますので。

IAMユーザへの追加許可(JSON)
{ "Sid": "Statement1", "Effect": "Allow", "Action": "sagemaker:InvokeEndpoint", "Resource": "arn:aws:sagemaker:ap-northeast-1:*****:endpoint/deepseek-awq-endpoint1-exec1" }
Actionのところに、作成したエンドポイントのARNを指定します。Sidのところはお手元の設定状況に合わせて指定して下さい。

これで、動作確認の準備は完了です。

動作確認結果


上で用意したセルをShift+Enterで実行しました。

大丈夫です。表示されました。
・遂次表示もされています。
・表示速度は0.62秒/1文字でした。少し遅めです。

まとめ


自由度が高く現実的な費用のAWS環境にて、質が高そうなOSSのLLMのAPIを、とにかく作成できました。

速度はそこそこで、費用は月額$440前後(ml.g6.xlarge単価:$1.64/h、月の稼働時間270h(1日9時間x30)として)となり、OpenAIのAPIを従量制で使うより有利な場合がありそうです。
追加学習をしてそのLLMを使いたい時に、この方法が選択肢の一つになるでしょう。

一方で、よりハイスペックなGPUを使いたい場合にAWS以外の選択肢がありそうだ、とも感じました。次はLambda.aiやRunpod等のGPU環境特化?サービスも試してみたいと思います。

最近は「AIエージェント」というキーワードで、色んなツールがAI的機能を組み込んでいますね。私も各案件でAI利用機能を積極的に提案し、色々と確認したいと思います。参考になりそうな情報はまたご紹介させて頂きます。お読み頂きましてありがとうございました。

お気づきの点がありましたら、以下の「ご連絡フォーム」から、コメントを頂けますと幸いです。

ご連絡フォーム


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

この記事に関して

その他のご連絡

DevAIsをもっと見る

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

続きを読む