[ChatStream] Web サーバー(ASGI server) の起動

[ChatStream] Web サーバー(ASGI server) の起動

こんにちは! (株)Qualiteg プロダクト開発部 です!

本稿では、 ChatStream 搭載した Webサーバーの起動方法について説明いたします!

uvicorn(内部起動)

ChatStreamは FastAPI/Starlette に対応しているため、 ASGI サーバーで動作させることができます。

uvicorn をコード内で定義するには以下のように実装します

def start_server():
    uvicorn.run(app, host='localhost', port=9999)


def main():
    start_server()


if __name__ == "__main__":
    main()

ソースコード全体

import torch
import uvicorn
from fastapi import FastAPI, Request
from fastersession import FasterSessionMiddleware, MemoryStore
from transformers import AutoTokenizer, AutoModelForCausalLM

from chatstream import ChatStream, ChatPromptTogetherRedPajamaINCITEChat as ChatPrompt

model_path = "togethercomputer/RedPajama-INCITE-Chat-3B-v1"
device = "cuda"  # "cuda" / "cpu"

tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype=torch.float16)
model.to(device)

chat_stream = ChatStream(
    num_of_concurrent_executions=2,
    max_queue_size=5,
    model=model,
    tokenizer=tokenizer,
    device=device,
    chat_prompt_clazz=ChatPrompt,
)

app = FastAPI()

app.add_middleware(FasterSessionMiddleware,
                   secret_key="your-session-secret-key",  # Key for cookie signature
                   store=MemoryStore(),  # Store for session saving
                   http_only=True,  # True: Cookie cannot be accessed from client-side scripts such as JavaScript
                   secure=True,  # False: For local development env. True: For production. Requires Https
                   )


@app.post("/chat_stream")
async def stream_api(request: Request):
    # handling FastAPI/Starlette's Request
    response = await chat_stream.handle_chat_stream_request(request)
    return response


@app.on_event("startup")
async def startup():
    # start request queueing system
    await chat_stream.start_queue_worker()


def start_server():
    uvicorn.run(app, host='localhost', port=9999)


def main():
    start_server()


if __name__ == "__main__":
    main()

uvicorn(外部起動)

次に uvicorn 外部起動 のパターンについてみていきましょう。

./example にある example_server_redpajama_simple.py をサーバーとして起動する場合

uvicorn example.web_server_redpajama_simple.py:app --host 0.0.0.0 --port 3000

のようになります。こちらのほうがサーバーとアプリの分離ができていて、より本番に近いアプローチとなります。

uvicornの起動オプション

https://www.uvicorn.org/settings/

ソースコード

example_server_redpajama_simple.py

import torch
import uvicorn
from fastapi import FastAPI, Request
from fastersession import FasterSessionMiddleware, MemoryStore
from transformers import AutoTokenizer, AutoModelForCausalLM

from chatstream import ChatStream, ChatPromptTogetherRedPajamaINCITEChat as ChatPrompt

model_path = "togethercomputer/RedPajama-INCITE-Chat-3B-v1"
device = "cuda"  # "cuda" / "cpu"

tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype=torch.float16)
model.to(device)

chat_stream = ChatStream(
    num_of_concurrent_executions=2,
    max_queue_size=5,
    model=model,
    tokenizer=tokenizer,
    device=device,
    chat_prompt_clazz=ChatPrompt,
)

app = FastAPI()

app.add_middleware(FasterSessionMiddleware,
                   secret_key="your-session-secret-key",  # Key for cookie signature
                   store=MemoryStore(),  # Store for session saving
                   http_only=True,  # True: Cookie cannot be accessed from client-side scripts such as JavaScript
                   secure=True,  # False: For local development env. True: For production. Requires Https
                   )


@app.post("/chat_stream")
async def stream_api(request: Request):
    # handling FastAPI/Starlette's Request
    response = await chat_stream.handle_chat_stream_request(request)
    return response


@app.on_event("startup")
async def startup():
    # start request queueing system
    await chat_stream.start_queue_worker()

gunicorn

次は gunicorn を使用する方法についてです。こちらは、本番のAPIサーバーとして、もっとも一般的なアプローチとなります。ここで起動した gunicorn を APIサーバーとして Nginx などの Webサーバーをリバースプロキシとして組み合わせることで、本番システムとして稼働させることができます。

./example にある example_server_redpajama_simple.py をサーバーとして起動する場合

gunicorn example.web_server_redpajama_simple.py:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:3000

(注意:Windows 環境では動作しません)

ソースコード

example_server_redpajama_simple.py

import torch

from fastapi import FastAPI, Request
from fastersession import FasterSessionMiddleware, MemoryStore
from transformers import AutoTokenizer, AutoModelForCausalLM

from chatstream import ChatStream, ChatPromptTogetherRedPajamaINCITEChat as ChatPrompt

model_path = "togethercomputer/RedPajama-INCITE-Chat-3B-v1"
device = "cuda"  # "cuda" / "cpu"

tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype=torch.float16)
model.to(device)

chat_stream = ChatStream(
    num_of_concurrent_executions=2,
    max_queue_size=5,
    model=model,
    tokenizer=tokenizer,
    device=device,
    chat_prompt_clazz=ChatPrompt,
)

app = FastAPI()

app.add_middleware(FasterSessionMiddleware,
                   secret_key="your-session-secret-key",  # Key for cookie signature
                   store=MemoryStore(),  # Store for session saving
                   http_only=True,  # True: Cookie cannot be accessed from client-side scripts such as JavaScript
                   secure=True,  # False: For local development env. True: For production. Requires Https
                   )


@app.post("/chat_stream")
async def stream_api(request: Request):
    # handling FastAPI/Starlette's Request
    response = await chat_stream.handle_chat_stream_request(request)
    return response


@app.on_event("startup")
async def startup():
    # start request queueing system
    await chat_stream.start_queue_worker()

いかがでしたでしょうか。ChatStreamが FastAPI/Starlette ベースで実装されているので本番に向けては標準的なアプローチでサーバー構築することが可能です。

ただし、商用環境を考慮しますと、通常、1つの ChatStream APIサーバーだけで運用することは稀で、1つの ChatStream API サーバーを ChatStreamノードと呼び、複数のChatStreamノードでリージョンごとのクラスターを作ります。Qualiteg では、このようなスケールアウト型の負荷対策をさせるシステム構成を推奨しております。

Read more

Raspberry Pi 5のWebサーバーをインターネットに公開する。ポートは開けない、WireCanalのトンネルで

Raspberry Pi 5のWebサーバーをインターネットに公開する。ポートは開けない、WireCanalのトンネルで

受信ポートを閉じたまま、Raspberry Pi 5のWebサーバーをインターネットに公開します。WireCanalのAgentをワンライナーで入れ、canalを作り、systemdで常駐化。ポート開放もルーター設定も不要で、操作はおよそ5分。再起動後の自動復活まで実測で検収しました。

By Qualiteg プロダクト開発部
Raspberry Pi 5を堅牢化する。SSH鍵認証・UFW・fail2banと、再起動でIPv6が復活する罠

Raspberry Pi 5を堅牢化する。SSH鍵認証・UFW・fail2banと、再起動でIPv6が復活する罠

セットアップ直後のRaspberry Pi 5は、パスワード認証が有効なまま、ファイアウォールなし、更新可能なパッケージ172件と無防備です。SSHの鍵認証化・UFW・fail2ban・自動更新・IPv6無効化を実コマンドと実測検収で解説します。sysctlのIPv6無効化が再起動で復活する現象と恒久対策も実機で確認しました。

By Qualiteg プロダクト開発部
LINE Botをローカルで開発する。WebhookのURLが毎回変わる問題を固定URLで解決する

LINE Botをローカルで開発する。WebhookのURLが毎回変わる問題を固定URLで解決する

こんにちは! LINE Botを作ろうとして最初にぶつかる壁は、コードではなくWebhookです。 LINEのMessaging APIは、ユーザーがBotに送ったメッセージを、LINEのサーバーからこちらのサーバーへHTTPSのPOSTで通知してきます。 この受け口(Webhook URL)にはインターネットから届くHTTPSのURLしか指定できず、開発中の http://localhost:5000 をそのまま書くことはできません。 トンネルツールで一時URLを発行して回避する方法が定番ですが、今度は別の問題が待っています。トンネルの方式によっては、起動のたびにランダムなURLが発行されます。その場合、開発を再開するたびにLINE Developersコンソールを開いてWebhook URLを書き換えることになります。コードを直すたび、翌日再開するたび、まずURLの貼り直しから。これが地味に堪えます。 結論から言うと、 ローカルPCの開発サーバーに「変わらない公開URL」を1本付けてしまえば、Webhook URLの設定は最初の1回だけで終わります。 この記事では、L

By Qualiteg プロダクト開発部