SONUB
JA English 한국어 日本語 中文 हिन्दी বাংলা اردو Español Português Français Deutsch Русский العربية Bahasa Indonesia Tiếng Việt ไทย Türkçe

通知を有効にする方法

このブラウザーまたはアドレスでは通知を利用できません。対応ブラウザーで安全な接続のSONUBサイトを開いてください。

通知がブロックされている場合は、ブラウザーの設定で許可してください。

  1. アドレスバー横のサイト設定、またはブラウザーのサイト設定を開き、このサイトの通知を許可に変更してください。
  2. 端末の設定でブラウザーの通知を許可し、おやすみモードや集中モードを確認してください。
  3. SONUBに戻ってページを再読み込みしてください。通知が有効になるとベルは表示されなくなります。

iPhoneやiPadではSONUBをホーム画面に追加し、そこから開いて通知を許可してください。

ログイン ログアウト 表示テーマ 標準テーマ ダークテーマ ピンクテーマ チャット 1:1 チャット グループチャット 掲示板 お知らせ ニュース掲示板 ご意見掲示板 自己紹介 メインメニュー

さあ、あなただけの人工知能を作ってみましょう。

したを実際の で動かすを作ります。runner.py をバックグラウンドで起動し、 で と /generate を呼んでみましょう。

単語のブロックが一列に並び、最後に空きマスがある。その上に高さのちがう棒がついた候補ブロックが3つあり、そのうち1つが選ばれて列の最後につながり、矢印が最初に戻ってまた次を選ぶ。
8

RunnerとAPI

はを一度読み、順に要求を処理します。 は状態、 /generateは文章を返します。

は自身です。はここで実行し、の住所欄とは区別します。と&はタブが生きている間だけ有効で、は不要です。

なぜサーバーにするのか — 一度読んで何度も使う

generate.py をで実行すると、実行するたびにチェックポイントを読んでを作り直します。質問が多いと、そのたびに同じ準備を繰り返します。Runner はモデルを一度だけ読んでに置き、要求が来るたびに generate だけを呼びます。

に分けると、役割も分かれます。

  • Runner(サーバー): モデルを持って計算だけをします。
  • (): 人と言葉をやりとりする画面だけを担当します。
  • 2つの間の約束(): どのアドレスにどんな を送れば、どんな JSON が返ってくるか。

約束さえ守れば、クライアントは何にでもなれます。の XChat の代わりに、Web ページ・スマートフォンのアプリ・別のサーバーが同じ Runner を呼べます。した重みを読んで計算を担うこのようなを、 ともいいます。Runner はとても小さなランタイムで、実際には ・・ のようなランタイムが同じ仕事を速く行います。

HTTP の基礎 — 要求と応答の形

は、クライアントが要求を送り、サーバーが応答を返す約束です。

  • 要求: メソッド(GET は取得、POST は送信)・(/health、/generate)・ヘッダー(: application/json のような本文の種類)・本文(POST の JSON)。
  • 応答: ・ヘッダー・本文。
  • ステータスコード: 成功、400 間違った要求(クライアントのせい)、 存在しないパス、500 サーバーエラー(サーバーのせい)、503 まだ準備ができていない。

curl で要求を自分で作れます。

  • curl -s アドレス: GET 要求を送り、本文だけを静かに表示します。
  • -H 'Content-Type: application/json': ヘッダーをつけます。
  • -d '{…}': 本文をつけます。本文があると curl は POST で送ります。
  • -i: 応答のステータス行とヘッダーまで表示します。curl -i http://127.0.0.1:8765/health を打ってみてください。HTTP/1.0 200 OK、Content-Type、Content-Length の行が見えます。

Runner の API — アドレス・フィールド・制限

Runner は2つのアドレスだけを開きます。

  • GET /health: 生きているかを確かめます。応答は {"status": "ok", "model": ""}。
  • POST /generate: 文章を生成します。応答は {"text": "生成した文章", "model": "XSLM"}。
  • それ以外のアドレス: 404 と {"error": "not found"}。

/generate の本文のフィールドです。

  • (必須): 文字列、1〜512文字。
  • : 整数、1〜64(max_tokens)。なければ24。
  • : 0以上の数。なければ 0.8。
  • : 0以上の整数。なければ8。
  • seed: 整数。なければ7。

間違った要求には、サーバーは落ちずに 400 と {"error": "invalid request"} で答えます。本文が4,096を超える、JSON ではない、フィールドの形式が違う、prompt がない — これらはすべて 400 です。制限を設ける理由は、サーバーを守るためです。tokens を100万で送る要求1つが、サーバーを何時間も占有することがあります。公開サーバーなら、このような検査が必ず必要です。

127.0.0.1 とポート — 誰が接続できるのか

Runner は 127.0.0.1 の8765番で要求を待ちます。

  • 127.0.0.1(): ループバックアドレスで、「このコンピューター自身」を意味します。同じコンピューターの中のプログラムだけが接続できます。
  • ポート: 1台のコンピューターで複数のサーバーを区別する番号です。8765 は settings.json の port です。
  • 0.0.0.0: すべてのから受け付けるという意味です。別のコンピューターから接続させるにはこう開きますが、なしでインターネットに開くと誰でも自分のサーバーを使えてしまうので注意します。

ここでの「このコンピューター」は、ブラウザーの中の仮想 です。だから curl と XChat は必ず右のターミナルの中で実行します。自分のコンピューターのブラウザーのアドレス欄に localhost:8765 と打つと、仮想 Linux ではなく自分のコンピューターを指すので接続できません。

同じポートを2つのプログラムが同時に使うことはできません。Runner がすでに起動しているのにもう一度起動すると、OSError: になります。

バックグラウンド実行 — &・nohup・PID・kill

Runner は終わらずに要求を待ち続けるプログラムです。そのまま実行するとターミナルを占有して他のコマンドを打てないので、裏で(で)実行します。

  • &: コマンドの最後につけると、裏で実行してすぐにプロンプトを返します。
  • nohup: ターミナルが閉じてもプログラムが終わらないようにします。
  • -u: 出力をためずにすぐ書きます。ログが遅れて出ることがありません。
  • > runner.log : 通常の出力とエラー出力を、両方 runner.log ファイルに書きます。
  • $!: 直前に裏で始めたプログラムのプロセス番号()です。 $! > runner.pid で保存しておきます。
  • "$(cat runner.pid)": 保存した番号のプログラムに、終われという信号を送ります。
  • ps や jobs: 今動いているプログラムを見ます。

実際のサーバーは、コンピューターが再起動しても、プログラムが落ちても、自動でもう一度起動する必要があります。Linux は systemd のサービス(Restart=always)で、 は launchd でこれを行います。この仮想 Linux の Runner は、このタブが開いている間だけ動きます。

同時の要求 — 1つずつ処理することと順番待ち

XSLM の は、要求を一度に1つずつ処理します。生成が終わる前に来た要求は、前の要求が終わるまで待ちます。授業用には単純で安全です。

多くの人が使うサーバーは違うやり方をします。

  • ThreadingHTTPServer: 要求ごとにスレッドを別に作り、同時に受け付けます。にあります。
  • FastAPI + uvicorn: (async)で多くの接続を受け付け、実際の生成は Semaphore(1) で一度に1つずつ順番に並べます。 のメモリーは1つしかないので、同時に複数の生成を動かすとメモリーがあふれることがあるからです。
  • バッチ処理: 短い時間(例: 100ms)に来た要求をまとめて一度に計算すると、GPU をずっと効率よく使えます。大きなサービスの核心技術です。

FastAPI は、要求の形式の検査(pydantic)と /docs の API 文書(Swagger)を自動で作ってくれます。XSLM は同じ検査を runner.py に手で書きます。

実際のサービスにするには — 速度・大きさ・安全

Runner を実際のサービスに育てるには、こうしたものを加えます。

  • 速いランタイム: llama.cpp(・Apple のチップに強い)、ONNX Runtime(さまざまな装置)、vLLM(GPU サーバー)のように計算を最適化したエンジンで重みを実行します。
  • : 重みを32・16ビットの実数の代わりに8・4ビットの整数で保存し、メモリーと時間を節約します。8ビットは256段階で数を表し、整数 × スケール値で元の数に近く戻します。精度は少し下がります。
  • KV : 前のトークンの key・value を保存し、トークンごとに全体を計算し直さないようにします。
  • : トークンが出るたびにすぐ送り、ユーザーを待たせないようにします。
  • 安全: 認証()、要求回数の制限、入力サイズの制限、ログと監視、HTTPS。
  • メモリーの計算: 実行メモリー = 重み + KV キャッシュ + です。 モデルを4ビットに減らしても、実際には かかります。

入力して実験

runner.pyを作り起動。直後にPIDを保存し、ログ・health・生成を確認します。

cd ~/xslm
micro runner.py
nohup python3 -u runner.py > runner.log 2>&1 &
echo $! > runner.pid
cat runner.log
curl -s http://127.0.0.1:8765/health
curl -s http://127.0.0.1:8765/generate -H 'Content-Type: application/json' -d '{"prompt":"Q: hi\nA: ","tokens":8,"temperature":0.8}'
ターミナルで実習する
runner.py手入力する完全なソース

モデルを共有し、入力を検証して JSONを返します。

import json
from http.server import BaseHTTPRequestHandler, HTTPServer
from generate import load, generate


def main():
    model, tokenizer, cfg = load()

    class Handler(BaseHTTPRequestHandler):
        def reply(self, status, body):
            data = json.dumps(body, ensure_ascii=False).encode('utf-8')
            self.send_response(status)
            self.send_header('Content-Type', 'application/json; charset=utf-8')
            self.send_header('Content-Length', str(len(data)))
            self.end_headers()
            self.wfile.write(data)

        def do_GET(self):
            if self.path == '/health':
                self.reply(200, {'status': 'ok', 'model': 'XSLM'})
            else:
                self.reply(404, {'error': 'not found'})

        def do_POST(self):
            if self.path != '/generate':
                return self.reply(404, {'error': 'not found'})
            try:
                length = int(self.headers.get('Content-Length', '0'))
                if not 0 < length <= 4096:
                    raise ValueError('request size')
                data = json.loads(self.rfile.read(length))
                prompt = data['prompt']
                tokens = data.get('tokens', cfg['tokens'])
                temperature = data.get('temperature', 0.8)
                top_k = data.get('top_k', 8)
                seed = data.get('seed', cfg['seed'])
                if not isinstance(prompt, str) or not 0 < len(prompt) <= 512:
                    raise ValueError('prompt')
                if type(tokens) is not int or not 1 <= tokens <= cfg['max_tokens']:
                    raise ValueError('tokens')
                if type(top_k) is not int or top_k < 0 or type(seed) is not int:
                    raise ValueError('top_k/seed')
                if type(temperature) not in (int, float):
                    raise ValueError('temperature')
                result = generate(model, tokenizer, prompt, tokens, temperature, top_k, seed)
                self.reply(200, {'text': result, 'model': 'XSLM'})
            except (ValueError, KeyError, TypeError):
                self.reply(400, {'error': 'invalid request'})

    address = ('127.0.0.1', cfg['port'])
    print('XSLM Runner:', address, flush=True)
    HTTPServer(address, Handler).serve_forever()


if __name__ == '__main__':
    main()

コードを1行ずつ読み解く

runner.py モデルを一度だけ読む

def main():
    model, tokenizer, cfg = load()

main は始まるときに load() でモデル・・設定を一度読みます。下の クラスは main の中で定義されていて、外側の model・tokenizer・cfg をそのまま使います(クロージャー)。要求ごとにモデルを作り直しません。

runner.py reply — 応答を送る

    class Handler(BaseHTTPRequestHandler):
        def reply(self, status, body):
            data = json.dumps(body, ensure_ascii=False).encode('utf-8')
            self.send_response(status)
            self.send_header('Content-Type', 'application/json; charset=utf-8')
            self.send_header('Content-Length', str(len(data)))
            self.end_headers()
            self.wfile.write(data)

Handler は BaseHTTPRequestHandler を継承し、要求を1つ担当します。reply はを JSON に変えて UTF-8 のバイトにし(ensure_ascii=False なので日本語もそのまま)、ステータスコード → ヘッダー(Content-Type・Content-Length)→ 空行(end_headers)→ 本文の順に送ります。Content-Length は文字数ではなくバイト数なので、エンコードした の長さを使います。

runner.py GET /health

        def do_GET(self):
            if self.path == '/health':
                self.reply(200, {'status': 'ok', 'model': 'XSLM'})
            else:
                self.reply(404, {'error': 'not found'})

GET 要求が来ると do_GET が呼ばれます。self.path が /health なら 200 と状態を、そうでなければ 404 を送ります。状態確認のアドレスはモデルの計算なしですぐに答えるので、サーバーが生きているかを軽く確かめるのに使います。

runner.py POST /generate — 本文を読んで検査する

        def do_POST(self):
            if self.path != '/generate':
                return self.reply(404, {'error': 'not found'})
            try:
                length = int(self.headers.get('Content-Length', '0'))
                if not 0 < length <= 4096:
                    raise ValueError('request size')
                data = json.loads(self.rfile.read(length))
                prompt = data['prompt']
                tokens = data.get('tokens', cfg['tokens'])
                temperature = data.get('temperature', 0.8)
                top_k = data.get('top_k', 8)
                seed = data.get('seed', cfg['seed'])
                if not isinstance(prompt, str) or not 0 < len(prompt) <= 512:
                    raise ValueError('prompt')
                if type(tokens) is not int or not 1 <= tokens <= cfg['max_tokens']:
                    raise ValueError('tokens')
                if type(top_k) is not int or top_k < 0 or type(seed) is not int:
                    raise ValueError('top_k/seed')
                if type(temperature) not in (int, float):
                    raise ValueError('temperature')

パスが /generate でなければ 404 です。その次に try の中で1つずつ検査し、どれか1つでも違えば を起こします。

  • Content-Length ヘッダーで本文の長さを知り、1〜4,096バイトでなければ拒否します。その長さだけを rfile から読みます。
  • json.loads で本文を辞書に変えます。JSON でなければ ValueError です。
  • prompt は必ずなければならず(なければ )、残りは get で基本値を与えます。
  • type(x) is not int で検査するのは、isinstance(True, int) が真だからです。true をトークン数として送る要求を防ぎます。
  • temperature は整数と実数の両方を受け付け、負・ は generate がもう一度拒否します。

runner.py 生成して答える

                result = generate(model, tokenizer, prompt, tokens, temperature, top_k, seed)
                self.reply(200, {'text': result, 'model': 'XSLM'})
            except (ValueError, KeyError, TypeError):
                self.reply(400, {'error': 'invalid request'})

検査を通ると generate を呼び、200 と {"text": …} で答えます。ValueError・KeyError・TypeError はすべて間違った要求なので 400 で答え、サーバーは動き続けます。エラーのたびにサーバーが落ちると、要求1つでサービス全体が止まってしまうからです。

runner.py 127.0.0.1 で待つ

    address = ('127.0.0.1', cfg['port'])
    print('XSLM Runner:', address, flush=True)
    HTTPServer(address, Handler).serve_forever()

アドレスは 127.0.0.1 と設定のポートです。起動の文言を =True ですぐに表示し、runner.log で起動したかがわかるようにします。HTTPServer(…).serve_forever() は、要求を1つずつ受け取って Handler に渡すことを果てしなく繰り返します。kill か で止めます。

理解を確認

status okとtextを確認。空JSONは400で拒否されます。

困ったとき

はログと重みを確認。ポート使用中なら旧PIDを停止。再学習後はRunnerを再起動します。

よくある質問

ブラウザーのアドレス欄で localhost:8765/health を開くと、なぜだめなのですか?

Runner はブラウザーの中の仮想 Linux で動き、その Linux の 127.0.0.1 だけで待っています。アドレス欄の localhost は自分の本物のコンピューターなので、そこには Runner がありません。curl は右のターミナルで実行してください。

Address already in use が出ます。

8765番ポートを、すでに別の Runner が使っています。起動中の Runner をそのまま使うか、kill "$(cat runner.pid)" で止めてから起動し直します。PID ファイルがなければ、ps で python3 runner.py の番号を探して kill します。

学習を追加したのに、Runner の答えが変わりません。

Runner は起動時に checkpoints/model.json を一度だけ読んでメモリーに置きます。ファイルが変わっても気づきません。止めて起動し直してはじめて、新しい重みを使います。実際のサービスでは、新しいモデルを横に立ち上げてから要求を移すやり方で、止めずに入れ替えます。

ガイドライン

人工知能

エージェントツール

バイブコーディング

AI活用

AI資格

AI授業

IT基礎

チャット

掲示板

制作

コミュニティ

その他

用語辞典

知らない言葉を選んでみましょう。やさしい説明と関連用語が見られます。

その用語はまだありません。

用語を選ぶと説明が表示されます。