はじめに
前回の記事で Livebook から Web アプリをデプロイする方法を紹介しました
今回はその発展形で、Bumblebee を使った AI Web アプリをインターネット上に公開してみます
デプロイ先は Fly.io です
少しのアプリを動かすだけであれば無料で利用できます
実装したノートブックはこちら
コンテナ実装
特に必要な依存モジュール等がなければ以下の Web サイトから簡単に Livebook を Fly.io にデプロイすることが可能です
しかし、今回は Livebook が 0.9 以上であることなど、必要な条件があるため、自分でカスタマイズしたコンテナを Fly.io にデプロイします
幸い私は普段から Livebook をコンテナで起動していたため、以下のリポジトリーにある Dockerfile をそのまま使えました
FROM ghcr.io/livebook-dev/livebook:0.9.0
...
CMD ["/app/bin/livebook", "start"]
この Dockerfile は以下の目的で作られています
- livebooks ディレクトリー配下の自作ノートブックを実行する
- evision や Image などのモジュールがインストールできる
- docker CLI を使える
- export して WSL にインポートする
- コンテナ内で Phoenix アプリのデモを行う
なので、 Bumblebee を動かすだけであれば上記 Dockerfile の全てが必要なわけではありません
重要なのは冒頭 FROM ghcr.io/livebook-dev/livebook:0.9.0
で Livebook 0.9.0 を指定していることと、末尾で CMD ["/app/bin/livebook", "start"]
として Livebook を起動していることです
それ以外の箇所は目的に応じて変更してください
.dockerignore の作成
これは環境によりますが、私の場合は tmp
ディレクトリー配下に AI のモデルファイル等の巨大ファイルを大量に保存していたため、そのままデプロイしようとするとかなり時間がかかってしまいました
コンテナに不要なディレクトリー、ファイルは .dockerignore ファイルに書いておきましょう
.dockerignore 記載例
tmp
Fly.io へのデプロイ
Fly.io のアカウント準備
まだ Fly.io を使ったことがない場合、以下の Speedrun に従って認証まで実行してください
macOS の場合は以下のようなコマンドになります
brew install flyctl
fly auth signup
fly.toml の作成
以下のような内容で Dockerfile と同じディレクトリーに fly.toml を作ります
# fly.toml file generated for livebook-rwakabay on 2023-03-30T09:19:15+09:00
app = "livebook-rwakabay"
kill_signal = "SIGTERM"
kill_timeout = 5
primary_region = "nrt"
processes = []
[env]
[experimental]
allowed_public_ports = []
auto_rollback = true
[[services]]
http_checks = []
internal_port = 8080
processes = ["app"]
protocol = "tcp"
script_checks = []
[services.concurrency]
hard_limit = 25
soft_limit = 20
type = "connections"
[[services.ports]]
handlers = ["http"]
port = 80
[[services.ports]]
handlers = ["tls", "http"]
port = 443
[[services.tcp_checks]]
grace_period = "30s"
interval = "15s"
restart_limit = 6
timeout = "2s"
livebook-rwakabay
の部分はデプロイするアプリ毎の ID なので、各人で変更してください
アプリの作成
fly launch
コマンドを実行し、質問には全てデフォルトのまま(何も入力せずに)Enter を押してください
$ fly launch
An existing fly.toml file was found for app livebook-rwakabay
? Would you like to copy its configuration to the new app? Yes
Creating app in /Users/oec/dx/elixir-learning
Scanning source code
Detected a Dockerfile app
? Choose an app name (leaving blank will default to 'livebook-rwakabay')
automatically selected personal organization: Ryo Wakabayashi
Some regions require a paid plan (fra, maa).
See https://fly.io/plans to set up a plan.
? Choose a region for deployment: Tokyo, Japan (nrt)
Created app 'livebook-rwakabay' in organization 'personal'
Admin URL: https://fly.io/apps/livebook-rwakabay
Hostname: livebook-rwakabay.fly.dev
? Would you like to set up a Postgresql database now? No
? Would you like to set up an Upstash Redis database now? No
? Create .dockerignore from 5 .gitignore files? No
Wrote config file fly.toml
最後の ? Would you like to deploy now?
に y
で変更すればそのままデプロイできます
デプロイ
fly deploy
コマンドでデプロイできます
しばらくして deployed successfully
が表示されればデプロイ成功です
$ fly deploy --ha=false
==> Verifying app config
--> Verified app config
==> Building image
Remote builder fly-builder-polished-waterfall-3907 ready
==> Creating build context
--> Creating build context done
==> Building image with Docker
--> docker host: 20.10.12 linux x86_64
[+] Building 43.6s (0/1)
[+] Building 162.9s (19/19) FINISHED
=> [internal] load remote build context
...
==> Monitoring deployment
Logs: https://fly.io/apps/livebook-rwakabay/monitoring
1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing]
--> v0 deployed successfully
2023年06月現在、 fly deploy --ha=false
と指定しないと、勝手に2台構成で起動してしまい、認証できなくなります
リソースの変更
fly scale memory 2048
コマンドでリソースを 2GB に拡張します
デフォルトの 256 MB だと EXLA のインストール時にメモリ不足でエラーが発生します
$ fly scale memory 2048
Scaled VM Memory size to 2 GB
CPU Cores: 1
Memory: 2 GB
ログの表示
fly logs
でコンテナのログを参照できます
Livebook の場合、アクセスするためにトークンが必要なので、ログを参照してトークンを取得します
$ fly logs
2023-03-30T00:37:47Z runner[0ab58f2c] nrt [info]Starting instance
2023-03-30T00:37:47Z runner[0ab58f2c] nrt [info]Configuring virtual machine
2023-03-30T00:37:47Z runner[0ab58f2c] nrt [info]Pulling container image
2023-03-30T00:38:26Z runner[0ab58f2c] nrt [info]Unpacking image
2023-03-30T00:39:28Z runner[0ab58f2c] nrt [info]Preparing kernel init
2023-03-30T00:39:29Z runner[0ab58f2c] nrt [info]Configuring firecracker
2023-03-30T00:39:29Z runner[0ab58f2c] nrt [info]Starting virtual machine
2023-03-30T00:39:30Z app[0ab58f2c] nrt [info]Starting init (commit: 8e03fa6)...
2023-03-30T00:39:30Z app[0ab58f2c] nrt [info]Preparing to run: `/app/bin/livebook start` as root
...
2023-03-30T00:39:33Z app[0ab58f2c] nrt [info][Livebook] Application running at http://0.0.0.0:8080/?token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
ログで [Livebook] Application running at http://0.0.0.0:8080/?token=<トークンの値>
となっている行からトークンの値をコピーしておきます
トークンの値はアプリを再起動するたびに変化するため、最後に表示されているものを使用してください(リソースの変更時に再起動されています)
Livebook を開く
以下のコマンドを実行すると、ブラウザで Livebook が起動します
fly open
取得しておいたトークンを入力します
Livebook のトップページが表示されました
Web アプリの実装
今回は画像識別 Web アプリを実装します
「New notebook」をクリックして新しいノートブックを開き、セルに以下のコードを入力、実行してください
セットアップ
必要なモジュールをインストールします
Mix.install(
[
{:bumblebee, "~> 0.2"},
{:nx, "~> 0.5"},
{:exla, "~> 0.5"},
{:kino, "~> 0.9"}
],
config: [nx: [default_backend: EXLA.Backend]]
)
画像識別サービスの作成
モデルファイルをダウンロードしてロードし、Bumblebee で画像識別のサービスを作成します
cache_dir = "/tmp/bumblebee_cache"
{:ok, resnet} =
Bumblebee.load_model({
:hf,
"microsoft/resnet-50",
cache_dir: cache_dir
})
{:ok, featurizer} =
Bumblebee.load_featurizer({
:hf,
"microsoft/resnet-50",
cache_dir: cache_dir
})
serving = Bumblebee.Vision.image_classification(resnet, featurizer)
フォームの作成
画像アップロードと実行用のボタンを作ります
inputs = [
image: Kino.Input.image("IMAGE", size: {224, 224})
]
form = Kino.Control.form(inputs, submit: "Classify")
結果表示用フレームの作成
結果を表示するフレームを作成します
frame = Kino.Frame.new()
画像識別処理の実装
入力フォームで Classify ボタンがクリックされたときの処理を定義します
Kino.listen(form, fn %{data: %{image: image}, origin: origin} ->
image =
image
|> then(fn input ->
input.data
|> Nx.from_binary(:u8)
|> Nx.reshape({input.height, input.width, 3})
end)
serving
|> Nx.Serving.run(image)
|> Map.get(:predictions)
|> Kino.DataTable.new()
|> then(&Kino.Frame.render(frame, &1, to: origin))
end)
&Kino.Frame.render(frame, &1, to: origin)
の to: origin
を指定することで、クライアント(アプリを開いているブラウザ)毎に個別の結果を表示します
これを指定していない場合、複数人でこのアプリを使っていると全員で結果を共有することになります
Livebook 上での画像識別実行
適当な画像をアップロードして Classify ボタンをクリックすると、表示用フレームに識別結果がテーブル表示されます
アプリのデプロイ
Livebook の左メニューからロケットのアイコンをクリックすると、デプロイ用のメニューが表示されます
Slug (URLの末尾部分)に適当な値(画像の例では「classifier」)を入力して「Deploy」ボタンをクリックしましょう
DEPLOYMENTS の Status が Running になれば Web アプリが起動しています
<Livebook の URL 先頭部分>/apps/<Slug に指定した値>
(今回の例ではhttps://livebook-rwakabay.fly.dev/apps/classifier)をブラウザで開きましょう
入力フォームと結果表示用フレームだけの画面が開きます
画像識別を実行してみます
実行できました
他の人にアプリを共有する場合、アプリの URL とデプロイ用メニューの「Password-protected」に入っている値(パスワード)を伝えてください
まとめ
ノートブックでアプリが実装できて、しかもデプロイまでできる、というのは本当に衝撃的です
しかも超短時間、超低コストで
今回のコードであれば実装からリリースまで数分でできてしまいます
Elixir の進化は計り知れないですね