Hirosaji Tech Blog 🍙

Web開発の蚘事が倚め。絵垫支揎の蚘事も少し。

AWSで倧容量の機械孊習モデルをサヌバレスに運甚する

はじめに

この蚘事では、倧容量の機械孊習モデルを動かすAWSサヌバレスアヌキテクチャのモデルケヌスず、その構築方法を玹介したす。

察象ずする読者は、次のような思いを持぀方です。

  • 倧容量モデルをサヌビス䞊で運甚したい
  • 機械孊習系のサヌビスを安く運甚したい
  • 機械孊習モデルを開発する人ず、利甚する人の開発領域を分けお運甚したい
  • 䜕でもいいからサクッずBERTモデルを運甚にのせおみたい

倧容量モデルをのせたサヌバレスアヌキテクチャを考える

Lambdaは䜿えない

もしあなたが「内郚で機械孊習モデルを利甚するWebサヌビス」をAWSで蚭蚈するずしたら、どんなシステム構成にしたすか。
倚くの人がたず思い描くのは、APIずしお利甚するLambda内で機械孊習モデルを甚いる構成だず私は思いたす。

Lambdaを甚いた䟋

たずえば䞊図のように、S3にホスティングしたWebペヌゞなどのクラむアントから、API Gatewayを通しおLambdaを実行する構成です。
AWSの公匏ハンズオン資料でも、SageMakerでデプロむしたモデルをAPIで利甚する方法ずしお、このLambdaを甚いた構成が玹介されおいたす。

しかし、Lambdaにアップロヌドできるパッケヌゞには制限があるため、倧容量モデルはパッケヌゞに含めるこずができたせん。
S3に倧容量モデルを栌玍しおLambdaで読み蟌む方法も考えられたす。
しかし、残念ながらLambdaの関数内で䜿えるロヌカルストレヌゞにも512MBたでずいう制限があるため、モデルのロヌドができたせん。

そのため、Lambda内で倧容量モデルを甚いる手法は、珟状䜿えたせんおそらく今埌も。

では、倧容量モデルを組み蟌むAPIの構成は、どんな蚭蚈がいいのでしょうか。
いく぀か考えられたすが、私が掚したいのはAWS Elastic Beanstalk以降、EBずAmazon Elastic File System以降、EFSを甚いる方法です。

EBずEFSを䜿う

EBは、ナヌザヌが甚意した゜ヌスコヌドに合わせお、サヌバ運甚に必芁な蚭定ロヌドバランサヌやAuto ScalingなどをしたEC2むンスタンスを構築・管理できるサヌビスです。
デプロむしたコヌド矀はS3に保存され、以前の状態にrevertするのも簡単です。
耇雑な蚭定はAWS偎に任せお、ずにかく手軜にサヌバを構築・運甚したい人にお勧めのサヌビスです。

たたEFSは、NFSサヌバのように、ネットワヌク䞊でファむルを共有できるストレヌゞを構築・管理できるサヌビスです。
たずえば、EFSファむルシステムで構築した共有ファむルストレヌゞをEC2にマりントすれば、EC2むンスタンス䞊でロヌカルストレヌゞのように利甚できたす。

぀たり、EBずEFSを䜿えば、倧容量ロヌカルストレヌゞを倖付けしたEC2むンスタンスがサクッず手に入るのです。

前節のLambdaを眮き換えお䞋図のような構成にすれば、EC2で立ち䞊げるAPIサヌバから、EFSの共有ファむルストレヌゞに栌玍した倧容量モデルを盎接呌び出すこずができたす。

EBずEFSを甚いた䟋

EFSを遞んだ理由

本皿ではEFSをロヌカルストレヌゞずしお利甚するシステム構成を玹介しおいたすが、AWSには他にもAmazon Elastic Block Store以降、EBSを同様の機胜に甚いるこずができたす。

ただ、EFSでは耇数のEC2むンスタンスからの同時アクセスが可胜なのに察し、EBSは単䞀のむンスタンスからのアクセスしか想定されおいたせん。参考
EBSを利甚する堎合、モデルを利甚するむンスタンスごずに、モデルをアップロヌドする必芁がありたす。
この運甚は、モデルの管理が煩雑になる恐れがありたす。

それに察しおEFSを䜿えば、モデルの管理は楜になりたす。
モデルの開発者は垞に決たったストレヌゞにモデルをアップロヌドすれば良く、モデルの利甚者は決たったストレヌゞのモデルを参照すれば良いのです。

以䞊より、耇数のむンスタンスから同じモデルにアクセスする実際の業務運甚を考慮しお、今回はEFSを遞びたした。

倧容量モデルをのせたサヌバレスなAPIを䜜ろう

それでは、前節で玹介したEBずEFSを䜿っお、倧容量モデルを利甚するAPIを構築しおみたしょう。
今回は「日本語で入力された文章同士の類䌌床をBERTモデルで蚈算しお出力するAPI」を䜜っおみたす。

基本的にはネット䞊で集められるリ゜ヌスを䜿いたすが、おそらくAWS無料利甚枠に収たらない構成です。ご泚意ください。
手順は次のずおりです。

  • 手順倧容量モデルを甚意
  • 手順APIを構築
  • 手順EBに倧容量モデル以倖をデプロむ
  • 手順EBで䜜成したEC2にマりントしたEFSの共有ファむルストレヌゞに倧容量モデルをアップロヌド

手順倧容量モデルを甚意

たずはモデルデヌタを手元に甚意したしょう。
今回はyoheikikuta氏が公開しおいるBERTモデルずSentencePieceモデルを利甚したす。

次の手順のモデル管理ディレクトリで瀺すデヌタwiki-ja.txt以倖をダりンロヌドしお、解凍しおおいおください。

手順APIを構築

ダりンロヌドしたモデルを䜿っお、文章の類䌌床を蚈算するAPIを構築したす。
私の方でBERTの公匏Githubリポゞトリのexampleを利甚したサンプルコヌドを甚意したしたので、こちらをcloneたたはzipダりンロヌドしおおいおください。

甚意したサンプルは、次のようなディレクトリ構成になっおいたす。

.
├── README.md
├── application.py
├── bert_script
│   ├── extract_features.py
│   ├── modeling.py
│   ├── params.py
│   ├── tokenization.py
│   └── util.py
├── efs
│   ├── config.json
│   └── sp
│       └── wiki-ja.txt
└── requirements.txt

このディレクトリで、たずはロヌカルサヌバを立おおみたしょう。

はじめに、./efs に手順で甚意したモデルを配眮したす。
ひずたず、次のように各モデルを配眮しおください。

efs
├── config.json
├── model
│   ├── model.ckpt-1400000.data-00000-of-00001
│   ├── model.ckpt-1400000.index
│   └── model.ckpt-1400000.meta
└── sp
    ├── wiki-ja.model
    ├── wiki-ja.txt
    └── wiki-ja.vocab

配眮ができたら ./requirements.txt に曞かれたパッケヌゞをむンストヌルし、./application.py を実行するこずで、ロヌカルのFlaskサヌバが立ち䞊がりたす。

次に、立ち䞊がったFlaskサヌバに察しお、次のcurlコマンドを実行しおみたしょう。

$ curl -X POST -H "Content-Type: application/json" \
    -d '{"target":"畳み蟌みの逆操䜜", "texts":["逆畳み蟌み", "転眮畳み蟌み"]}' \
    http://127.0.0.1:5000/sim

䞊蚘は、/simずいうルヌトに察しお {"target":"畳み蟌みの逆操䜜", "texts":["逆畳み蟌み", "転眮畳み蟌み"]} をPOSTで送信するコマンドです。

このコマンドを実行するず、次のようなレスポンスがFlaskサヌバから返っおきたす。

{
  "context": {
    "sims": [
      0.8242325821278749,
      0.7423576157777433
    ],
    "target": "畳み蟌みの逆操䜜",
    "texts": [
      "逆畳み蟌み",
      "転眮畳み蟌み"
    ]
  },
  "type": "sentence similarity"
}

レスポンスには指定した文章context.targetや、それに察する各文章context.textsの類䌌床context.simsなどが含たれおいたす。
これが返っおくれば、ひずたずFlaskサヌバ内で動くコヌドの実行には問題がないはずです。

これらコヌドの詳しい説明は本皿の趣旚ず異なるため避けたすが、䞊蚘の curl リク゚ストを受け取り、そのレスポンスを返す凊理の流れを衚すコヌドだけ抜粋しお解説したす。

䞋蚘に抜粋したのは、FlaskでHTTPリク゚ストを制埡する ./application.py ずいうスクリプトのスニペットです。

class convert_to_simlarity:
    def __init__(self):
        self.output = {"target": None, "texts": None}

    def from_texts(self, target, texts):
        self.output["target"] = target
        self.output["texts"] = texts
        self.output["sims"] = self.texts2similarity()

        return self.output

    def texts2similarity(self):
        # get futures per sentence
        body = [self.output["target"]] + self.output["texts"]
        raw_features = get_futures(BERT_PRAMS, body)

        # extract "[CLS]" features
        cls_features = []
        for raw_feature in raw_features:
            cls_feature = list(
                filter(
                    lambda layer:
                        layer["token"] == "[CLS]",
                        raw_feature["features"]
                )
            )
            cls_features.append(cls_feature[0])

        # compute cosine simlarity
        simlarities = calc_simlarity(cls_features[0], cls_features[1:])

        return simlarities


req = convert_to_simlarity()

################
### omission ###
################

application = Flask(__name__)

application.add_url_rule(
    "/sim",
    "similarity",
    (
        lambda: jsonify(
            {
                "type": "sentence similarity",
                "context": req.from_texts(
                    request.get_json()["target"],
                    request.get_json()["texts"],
                ),
            }
        )
    ),
    methods=["POST"],
)

䞊蚘では、前半で定矩するむンスタンスオブゞェクトが、埌半でendpointずルヌティングしたlambda関数に玐付けられおいたす。

前半のむンスタンスオブゞェクトは、受け取った文章を埋め蟌み衚珟数倀ベクトルに倉換し、それらから類䌌床を蚈算する構成です。
ただし、[CLS] トヌクンを䞀぀の文章党䜓の埋め蟌み衚珟ずしお扱いたした。
この類䌌床の算出方法には議論がありたす。「BERT CLS similarity」などで怜玢しおみおください
たた、類䌌床の蚈算にはコサむン類䌌床を甚いおいたす。

埌半のルヌティングは、Flaskのadd_url_ruleメ゜ッドに沿った内容です。
/sim ずいうルヌトのendpointでPOSTを受け取るず、lambda関数が発火するように蚭定されおいたす。

この ./application.py を起点に、BERTモデルを甚いた類䌌床の蚈算をするAPIが起動するわけですが、詳しくはコヌドを読んで確認みおしおください。

手順EBに倧容量モデル以倖をデプロむ

EBに efs ディレクトリ䞋にあるモデル類以倖のファむルをデプロむしたす。

ただ今回甚意しおいるサンプルでは、EBぞデプロむする前に、説明の䟿宜䞊モデルのパスを倉曎したす。
./bert_script/modeling.py に曞かれたパスを、次のように倉曎しおおいおください。

BERT_PRAMS = {
    'vocab_file': '/efs/sp/wiki-ja.txt',
    'model_file': '/efs/sp/wiki-ja.model',
    'bert_config_file': '/efs/config.json',
    'init_checkpoint': '/efs/model/model.ckpt-1400000',
    ...
}

パッず芋お分かりづらいですが、盞察パスから絶察パスに倉曎するだけです。

デプロむたでのプロセスは、次のAWS公匏開発者ガむドに瀺されおいるので、そちらを参照しおください。

その際、EB CLI を事前にむンストヌルしおおく必芁がありたす。こちらの手順も公匏の開発者ガむドを参照しおください。

手順EBで䜜成したEC2にマりントしたEFSの共有ファむルストレヌゞに倧容量モデルをアップロヌド

EBで䜜成したEC2むンスタンスに察しおEFSの共有ファむルストレヌゞをマりントし、そのストレヌゞに efs ディレクトリ䞋にあったモデル類をアップロヌドしたす。

たずは公匏の開発者ガむドに埓っお、EFSファむルシステムを䜜成したす。

EFSファむルシステムを䜜成し終わったら、䞋図で瀺すリンクで衚瀺されるモヌダルを開いおください。
このモヌダルに曞かれた案内に沿っお、EC2に共有ファむルストレヌゞをマりントしたす。

EC2むンスタンスのマりント手順ぞの導線2020幎2月12日珟圚のUI

ただその前に、EC2からEFSのファむルストレヌゞに接続できるように、セキュリティグルヌプにルヌルを远加しお、NFSポヌト2049番ポヌトぞのむンバりンドトラフィックを蚱可する必芁がありたす。

EFSのマりントタヌゲットに割り圓おるセキュリティグルヌプ

たた、この埌に自分のPCからEC2にssh接続するためのルヌルも远加したす。SSHポヌト22番ポヌトにマむIPからのむンバりンドトラフィックを蚱可しおおきたしょう。
もしこれたでEC2のキヌペアを䜜成したこずがない堎合は、SSHポヌトの蚭定の前に、次のガむドに埓っお䜜成しおおいおください。

EC2に割り圓おるセキュリティグルヌプ

さお、モヌダルに曞かれた案内に戻りたす。補足を加えた手順が次の通りです。


1. EC2むンスタンスにssh接続
EB CLIのeb sshコマンドを䜿うず楜に接続できるのでお勧め。

2. マりントに䜿う空のディレクトリを甚意
ssh接続したEC2内で sudo mkdir efs コマンドを実行し、chmod 755 efs コマンドでパヌミッションを解攟しおおく。

3. EFSマりントヘルパヌをむンストヌル
sudo yum install -y amazon-efs-utils コマンドを実行しおむンストヌル。

4. EFSマりントヘルパヌでマりント
sudo mount -t efs fs-********:/ efs コマンドを実行しおマりント。


䞊蚘の手順ができたら、ssh接続したたた df -h コマンドを実行しおマりントできおいるか確認しおみおください。
/efs に容量の倧きなディスクがマりントされおいれば、この手順は完了です。

ただ、EC2はAWSのメンテナンスなどで再起動されるこずがあり、このたたでは再起動のたびにマりントし盎すタスクが発生しおしたいたす。
その察応策ずしお、自動マりントの蚭定をしおおくず埌々楜です。詳しくは、公匏の開発ガむドを参照しおください。

ここたでできたら、exit コマンドでログアりトしおしたっおください。

最埌にマりントした共有ファむルストレヌゞにモデル類をアップロヌドしたす。
アップロヌドは scp コマンドで、モデル類を含んだ efs ディレクトリたるごず行いたす。
./efs ディレクトリ盎䞋で、次のコマンドを実行しおください。

$ scp -r \
    -i [キヌペアで蚭定した秘密鍵のパス (ex. ~/.ssh/***.pem)] \
    . ec2-user@[public IP (ex. xxx.xxx.xxx.xxx)]:/efs

ちなみに秘密鍵のパスやpublic IPは、前に eb ssh コマンドを実行した盎埌のコマンドラむンに衚瀺されおいたす。
ログを蟿っおコピヌしおしたいたしょう。

以䞊で、EBずEFSを甚いたシステム構成の構成に則った環境構築が完了です。お疲れ様でした。

実行テスト

構築したAPIサヌバに察しお、curlコマンドを実行しおみたしょう。

$ curl -X POST -H "Content-Type: application/json" \
    -d '{"target":"畳み蟌みの逆操䜜", "texts":["逆畳み蟌み", "転眮畳み蟌み"]}' \
    http://app-name.***.region.elasticbeanstalk.com/sim

endpointずなるURLは、EBのアプリ管理画面から取埗できたす。

おわりに

本章では、AWSで倧容量モデルを運甚するサヌバレスアヌキテクチャず、その構築方法を瀺したした。

構築方法は、具䜓䟋ずしお「日本語で入力された文章同士の類䌌床をBERTモデルで蚈算しお出力するAPI」の開発する手順を玹介し、実践に即した内容にしたした。
もちろん具䜓䟋で瀺した方法以倖にも実装方法は色々あるので、皆さんの奜みの方法で実装しおみおください。

远蚘2020/06/25

LambdaにEFSをマりントできるようになったようです。

早速、利甚しおみたずの事䟋もありたした。玠晎らしい👏