ローカルLLMを用いて霧雨魔理沙とおしゃべりできるlinebotを作った話②linebot編

# 宣伝

｢おしゃべり魔理沙AI｣という霧雨魔理沙とおしゃべりできるline-botを作りました。
よかったら遊んでみてください。

![S_gainfriends_2dbarcodes_GW.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950973/a90fc9c4-932e-f7e9-03ff-6f9150ace3b5.png)

* linebotのurl
    * https://lin.ee/DAY6iGc
* linebotの運営用Xアカウント
    * https://twitter.com/osyaberi_marisa

作者のお財布事情とlinebotとの兼ね合いにより、通算で月200回しか使えないです。
｢動かないな｣と思ったら、そういうことです。
赤字垂れ流しなので許してください。

![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950973/fa314434-e4eb-f4e7-5b8b-d2e6fddefaa7.gif)


# 概要

ローカルLLMをファインチューニングして、東方projectというゲームに出てくる霧雨魔理沙とおしゃべりできるlinebotを作りました。
Qiitaの記事としては｢①モデル編｣と｢②linebot編｣の2つになります。

まず最初に｢①モデル編｣からよむことをおすすめします

https://qiita.com/tsukemono/items/990490e0090c0607b00c

｢②linebot編｣では｢①モデル編｣でつくった｢おしゃべり魔理沙モデル｣をどうlinebotに組み込んだのかについて話をしていきます。

# 詳細

## 全体像について
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950973/a3fab300-1192-617f-85ed-cf8531e3cbf0.png)

全体像は上の画像を御覧ください。
AWSのlambdaを中心にlinebotを作りました。

* linebot
* lambda
* dynamoDB

のそれぞれについて軽く説明していきます。

## linebotについて

linebotとlambdaを組み合わせる手法については、すでにわかりやすい既存の記事がいくつか出ているため、説明を省略します。
簡単に説明しますと、line上にユーザーからの投稿があると、webhookを通じてlambdaが起動します。
次にlambdaでapiを叩くことで、lineにbotが返信を投稿します。

## lambdaについて

lambdaとはイベント(今回はlineの投稿)が起こったときに、特定のプログラム(今回はモデル推論とlineの返信)をサーバーレスで行ってくれるサービスです。

### モデルファイルの参照方法

今回はlambdaでモデルを推論させるため、lambda上でモデルを読み込めるようにする必要があります。
lambda上でファイルを参照するためには下表の手法があります。

|手法|サイズ上限|lambda以外の料金|
|---|---|---|
|zipファイルを直接アップロード|250MB|なし|
|ECR経由でdocker環境を反映|10GB|ECRの利用料|
|EFSなどにファイルをあげ、モデル読み込みのたびにそこを参照|なし|EFSの利用料|

今回は、この中でECRを用いる手法を選びました。
｢zipファイルを直接アップロード｣は250MBしかアップロードできないのでLLMは当然動きません。
｢EFSなどにファイルをあげ、モデル読み込みのたびにそこを参照｣はサイズ制限はないものの、ファイルの読み書きごとに課金されるため、lambdaがコールドスタートする度に数GB分の課金がなされます。
そのため、利用を見送りました。
(最初はそこに気づかず2000円くらい溶かした)

### 10GBにどう抑えたか

｢ECR経由でdocker環境を反映｣を選んだ場合、docker自体のサイズを10GBに抑える必要があります。
japanese-novel-gpt-j-6bのファイルサイズを確認してみましょう
11GBです。
残念ながらこのままでは、どう頑張ってもdockerのサイズが10GBを超過します。

ファイルサイズを抑える手法として

* CTranslate2
* インストールするpytorchをcpuのみにする

の2つを行いました。

#### CTranslate2

CTranslate2とは既存の巨大モデルを量子化&CPUに最適化することで、コンパクト&高速に推論できるようにするライブラリです。
これを用いて｢おしゃべり魔理沙モデル｣をint8に変換したところ11GBが6GBになりました。
(ただ、これを施すと精度が落ち、返答が印象として無愛想になるので、できれば使いたくない)

#### インストールするpytorchをcpuのみにする

上記のことをしても、まだ10GBを超過していました。
理由としては、Pytorchのライブラリの巨大さです。
デフォルトのPytorchのインストールでは、CPU推論用のコードとGPU推論用のコードの両方が入っています。
lambdaではCPU推論しかできないため、GPU推論用のコードは余分です。
なので、インストールしないようにすることで容量の削減をしました。

やり方としては
requirements.txtのtorchを
```
torch==1.13.1+cpu
```
のように書けばOKです。
これだけで数GBの容量を節約できます。

### コールドスタート問題

awsのlambdaにはコールドスタートとウォームスタートの二種類のスタート方法があります。
コールドスタートとは新しく環境自体をたちあげるスタート方法ことで、ウォームスタートはすでにある環境から実行関数を実施するスタート方法です。
lambdaは自動的に環境が落ちるため、基本的には｢しばらく放置するとコールドスタート｣｢直近の利用があるとウォームスタート｣と考えていいでしょう。

CTranslate2を導入したこともあり、モデルの推論は自体は数秒で終わります。
しかしながら、モデルをメモリに載せる(モデルの読み込み)のところで数分の処理時間がかかります。
そのため、lambdaがコールドスタートするとlineの応答が数分かかる自体になってしまいます。
linebotで返信が数分待ちはなかなかしんどいところがあるので、何か対策を考えたいものです。

コールドスタートの問題をマシにする手法として以下の3つの手法が挙げられます。
* Provisioned Concurrencyを使う
* snapstartを使う
* 定期的にlambdaを実行する

それぞれについて説明していきます

#### Provisioned Concurrency

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/provisioned-concurrency.html

Provisioned Concurrencyとはlambda環境を常にウォームスタート状態にする設定項目のことです。
これを使うのが一番正統派な解決方法だと思います。
しかし、通常のLambdaの利用料金に加え、追加で時間あたりでコストがかかります。
そこそこの額がかかるため、今回は利用を見送りました。

#### snapstartを使う

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/snapstart.html

環境自体をキャッシュにして保存することで、コールドスタートの時間を短くしてくれるオプションです。
無料でつかえるみたいなのですが、まだjavaでしか使えないようです。
残念。

#### 定期的にlambdaを実行する

定期的にlambda関数を実行してあげることで、コールドスタートになるずらくします。
この方法は確実性がなく、5分に一度起動していても1時間に1回くらい落ちます。
しかし、Provisioned Concurrencyよりもは価格はかからなそうでした。

なので今回はeventbrifgeをlambdaと組み合わせることで、定期的にlambdaを読み出して、コールドスタートを防ぐ手法を採用しました。

## dynamoDBについて

魔理沙の発言を推論させるに当たり、過去の会話ログをプロンプトに入れたほうが精度が良くなります。
具体的に言うと、過去の会話ログをプロンプトに含めると
```
ユーザー: 好きな食べ物は?
魔理沙: 肉だ
ユーザー: それのどんなところが好き?
魔理沙: 味だな
```
のような会話ができるようになります。

そのため、過去の会話ログを記録するデータベースと紐づけました。

# まとめ

linebotに既存の言語モデルをどう組み込むかについて説明しました。
みなさんもお気に入りのキャラクターを公開してみてはいかがでしょうか。