【備忘録】Sagemakerの推論インスタンスがopencvが原因で立ち上がらなくなった件

Building wheel for opencv-python (PEP 517): still running... というメッセージがひたすら続いてSagmakerがコケてしまう現象を直しました。
2023.01.19

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

せーのでございます。

今日は先日遭遇したちょっとしたトラブルについて備忘録的に残しておこうとおもいます。

経緯

先日、あるシステムの起動に失敗するようになった、と連絡がありました。
そのシステムは起動処理の最中にLambdaからSagemakerにてセットされているモデルを元に推論インスタンスを起動させる、というフローが入っているのですが、どうもここでコケているようでした。

該当するLambdaのコードはこちらです。

    def create_inference_endpoint(self, endpoint_name: str):
        # check endpoint already exists
        list_endpoints_result = self._sagemaker.list_endpoints()
        for endpoint in list_endpoints_result["Endpoints"]:
            if endpoint["EndpointName"] == endpoint_name:
                print(f"endpoint_name: {endpoint_name} already exists")
                return

        # create sagemaker endpoint config
        create_endpoint_config_api_response = self._sagemaker.create_endpoint_config(
            EndpointConfigName=endpoint_name,
            ProductionVariants=[
                {
                    "VariantName": "AllTraffic",
                    "ModelName": "XXX-inference-XXXX",  
                    "InitialInstanceCount": 4,
                    "InstanceType": "ml.g4dn.xlarge",
                },
            ],
        )
        print("create_endpoint_config_api_response", create_endpoint_config_api_response)

        # create sagemaker endpoint
        create_endpoint_api_response = self._sagemaker.create_endpoint(
            EndpointName=endpoint_name,
            EndpointConfigName=endpoint_name,
        )
        print("create_endpoint_api_response", create_endpoint_api_response)

        # 推論エンドポイント作成完了を待つ
        while True:
            endpoint_status = self._sagemaker.describe_endpoint(EndpointName=endpoint_name)["EndpointStatus"]
            print(endpoint_status)
            if endpoint_status == "InService":
                break
            time.sleep(10)

sagemakerのモデル名を指定してcreate_endpoint()を叩き、ステータスを10秒ごとにdescribe_endpoint()で監視する、というものです。大きめの推論処理を動かすため、ml.g4dn.xlargeを4台動かしています。

どうもこれがいつまで立ってもInServiceにならないため、Lambdaの実行時間を過ぎてしまってエラー、という結果のようです。

数ヶ月ぶりに動かしたシステムだったのですが、ロジックは以前と変わっていません。どうして動かなくなったのでしょう。

調査

まずは元凶となっているSagemakerのエンドポイントを確認してみました。

見事に起動に失敗しています。メッセージを見てみると

失敗の理由 The primary container for production variant AllTraffic did not pass the ping health check. Please check CloudWatch logs for this endpoint.

なんだか良くわかりません。

とりあえずCloudWatch logsを見ろ、ということなので、見てみます。

どうやらOpenCVのインストールをひたすらしているようです。

Building wheels for collected packages: opencv-python Building wheel for opencv-python (PEP 517): started

というログ内容から、apt-getなどのOSコマンドで入れているライブラリではなく、pipやcondaなどからインストールされているpython用のopencvですね。 ちなみにしばらく待つと

インストールが終わってインスタンスが起動しています。

つまり原因は

python-opencvのインストールに時間がかかりすぎて推論インスタンスの立ち上げ時間をすぎてしまったのでエラーになった

ということのようです。

Sagemakerの公式FAQには

https://aws.amazon.com/jp/premiumsupport/knowledge-center/sagemaker-endpoint-creation-fail/

少なくとも、コンテナは HTTP 200 OK ステータスコードと空の本文で応答して、コンテナが推論リクエストを受け入れる準備ができていることを示す必要があります。このエラーは、SageMaker がコンテナの起動後 4 分以内にコンテナから一貫したレスポンスを得られない場合に発生します。エンドポイントがヘルスチェックに応答しないため、SageMaker はエンドポイントが正常であるとはみなしません。そのため、エンドポイントは [Failed] (失敗) とマークされます。

とあります。つまり、何かインストールがある場合は4分以内に済まさないといけないわけですね。

とはいえ、このようなエラーは以前には出ていませんでした。なぜ急にこのようなエラーが出始めたか、調べていくとどうもpython-opencvのバージョンに問題があるようです。

python-opencvはC++にてビルドされるパッケージなのですが、バージョン4辺りからどんどん巨大化していて、現在では90MBを超える巨大ライブラリに成長しています(私が以前使ってた3.4は20MBそこそこだった)。そのためこれをビルドするのに通常のCPUだと10分以上かかります。フルパッケージであるopencv-contrib-pythonだと2時間かかった人もいるそうで。。。4分以内にはとてもじゃないけど終わらないようです。

これを解決するにはpython-opencvを最初から入れてパスだけ通すようにする、ライブラリを全部入れしたコンテナをカスタムで用意する、などいくつか方法が考えられますが、今回は特に最新機能はいらなかったので「wheelをビルドしない古いpython-opencvをインストールする」というお手軽解決でいきたいと思います。

方法

方法は至ってシンプルです。

モデルをダウンロード

Sagemakerよりcreate endpointで指定するモデルを開きます。

実モデルが入っているS3のリンクが書かれていますので、そちらを開き、ダウンロードします。名前は「model.tar.gz」となっています。

このファイルを解凍すると中に「学習されたモデル」と「推論コード」が入っています。推論コードは「code」というフォルダに入っています。

この形で入れておくと、create endpointしたときに推論用のインスタンスの/opt/ml/codeというパスにこのcodeフォルダの中身がコピーされます。

中には推論コードとモジュールをインストールする「requirements.txt」が入っています。ここに最初に入れてほしいライブラリ名を書いておくとpipインストールしてくれます。

今回はそのrequirements.txtの中にあるpython-opencvのバージョンを古いものに固定します。

opencv-python==3.4.0.14

できたら元通りmodel.tar.gzに圧縮して

tar zcvf model.tar.gz *

元のS3バケットに戻しておきます。何かあったときのためにバージョニングを忘れずにしておきましょう。

再びcreate endpointしてみると

無事、エンドポイントが立ち上がりました。気になるopencvは

早い!わずか1秒でインストールできました。

まとめ

今回はcreate endpointする際のちょっとしたトラブルを解決しました。
最新に合わせるためにはカスタムコンテナを作ったり、pythonやpipのバージョンを上げることでも解決できると思いますが、今回は推論内で使用している他のライブラリの関係上古いpythonから上げられない、という事情もあり、このような形を取りました。
python-opencvは育っている、という事だけでも頭の隅にあると色々便利かと思います。