
チャットのエンジンと、そのサーバ〜クライアントを作ってみます。
関連
- ◯
- サーバ証明書を取得〜更新する(ワイルドカード証明書、ネームサーバ経由):Let's Encrypt
検証
- ・
- クラウド:GCP
- ・
- コンテナ:Docker
- ・
- ゲストOS:Ubuntu 22.04
- ・
- ウェブブラウザ:Chrome
- ・
- 画像生成アプリ:ComfyUI
概要
- ◯
- 背景
いまはそれなりの言語モデルを、ローカル環境でも動かせるようになってきました。
まず量子化による劣化が、そこまで酷くならないことが分かってきました。さらに、マージツール(mergekit)によるさまざまなマージ手法(Chat Vector, MoE, ...)が試され、一定の成果を上げています。とくに進化計算を組み合わせたツール(mergekit-evolve)によるマージは、比較的小さな資源で、大きな効果を得られることで話題になりました。[※1]
- ◯
- 対応
ここでは、チャットのエンジンと、そのサーバ〜クライアントの、かんたんな実装を試みます。
- ◯
- 構成
言語モデルの一プロジェクト”LocalNovelLLM-project”から、適度なサイズでそれなりの言語モデルが出ていますーーここでは、このモデルを使います。LLaMA 系なので、インストラクションの形式は [INST] [/INST] になります:
- ・
- https://huggingface.co/Local-Novel-LLM-project
- ・
- https://huggingface.co/Local-Novel-LLM-project/Vecteus-v1
- ※1
- Sakana AI 社は、錬金術ともいわれていたマージの技法に、進化計算を適用して成果を上げましたーーただ、そのソースコードは開示されていないので、ゴダード氏が、オープンソース版を開発〜公開しています:
- ・
- https://sakana.ai/evolutionary-model-merge-jp/
- ・
- https://blog.arcee.ai/tutorial-tutorial-how-to-get-started-with-evolutionary-model-merging/
設置
環境は、音声認識エンジン向けに作ったコンテナ/ライブラリを流用します:
- ◯
- 音声認識のエンジン/サーバ/クライアントを作る:Python (Transformers, aiohttp), Javascript (MediaRecorder)
実装
- ◯
- バックエンド:エンジン
- ・
- appllm/bin/llmprc.py
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
class LlmPrc:
def __init__(self, nummax, nmodel):
self.nummax = nummax
cnfbnb = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.bfloat16)
self.omodel = AutoModelForCausalLM.from_pretrained(
nmodel,
device_map="auto",
quantization_config=cnfbnb)
self.tokenz = AutoTokenizer.from_pretrained(
nmodel)
def setsys(self, prmsys):
self.prmsys = prmsys
self.prompt = ""
self.lstusr = []
self.lstres = []
def addusr(self, prmusr, lstusr):
lstusr.append(prmusr)
if len(lstusr) > self.nummax:
lstusr.pop(0)
return lstusr
def addres(self, outres, lstres):
lstres.append(outres)
if len(lstres) > self.nummax - 1:
lstres.pop(0)
return lstres
def genprm(self, lstusr, lstres):
prompt = ""
i = 0
while i < len(lstusr):
if i == 0:
if i == len(lstusr) - 1:
prompt = "<s>" + "[INST]" + "<<SYS>>" + self.prmsys + "<</SYS>>" + lstusr[i] + "[/INST]"
else:
prompt = "<s>" + "[INST]" + "<<SYS>>" + self.prmsys + "<</SYS>>" + lstusr[i] + "[/INST]" + lstres[i]
else:
if i == len(lstusr) - 1:
prompt = prompt + "</s>" + "<s>" + "[INST]" + lstusr[i] + "[/INST]"
else:
prompt = prompt + "</s>" + "<s>" + "[INST]" + lstusr[i] + "[/INST]" + lstres[i]
i = i + 1
return prompt
def genres(self, numout, numtmp, prmusr):
self.addusr(prmusr, self.lstusr)
self.prompt = self.genprm(self.lstusr, self.lstres)
inputs = self.tokenz([self.prompt], return_tensors="pt").to("cuda")
lstgen = self.omodel.generate(
**inputs,
max_new_tokens=numout,
temperature=numtmp,
do_sample=True,
pad_token_id=self.tokenz.eos_token_id)
output = self.tokenz.decode(lstgen[0]) # 推定結果の全体
outres = self.tokenz.decode(lstgen[0][len(inputs["input_ids"][0]):]) # 推定結果の追加部分(応答)
outres = outres.replace(" ", "")
outres = outres.replace("「", "")
outres = outres.replace("」", "")
outres = outres.replace("</s>", "")
#print("prompt[" + self.prompt + "]") # DBG
#print("output[" + output + "]") # DBG
#print("outres[" + outres + "]") # DBG
self.addres(outres, self.lstres)
return outres
- ◯
- バックエンド:サーバ
- ・
- appllm/bin/llmsrv.py
import json
from aiohttp import web
from llmprc import LlmPrc
async def res001(datreq):
global prmold
dicreq = await datreq.json()
txtreq = dicreq["txtreq"]
numout = dicreq["numout"]
numtmp = dicreq["numtmp"]
prmsys = dicreq["prmsys"]
if prmold != prmsys:
llmprc.setsys(prmsys)
prmold = prmsys
txtres = llmprc.genres(numout, numtmp, txtreq)
return web.Response(
text=json.dumps({"txtres": txtres}),
content_type="application/json",
)
prttgt = 8080
adr001 = "/"
nummax = 4
nmodel = "Local-Novel-LLM-project/Vecteus-v1"
prmold = ""
llmprc = LlmPrc(nummax, nmodel)
appweb = web.Application()
appweb.add_routes([web.post(adr001, res001)])
web.run_app(appweb, port=prttgt)
- ◯
- フロントエンド:クライアント
- ・
- appllm/bin/llmclt.py
import sys
import asyncio
import aiohttp
async def req001(dicreq):
async with aiohttp.ClientSession() as sssweb:
async with sssweb.post(adr001, json=dicreq) as datres:
dicres = await datres.json()
return dicres
numout = int(sys.argv[1])
numtmp = float(sys.argv[2])
prmsys = sys.argv[3]
txtreq = sys.argv[4]
adr001 = "http://localhost:8080/"
dicreq = {"txtreq": txtreq, "numout": numout, "numtmp": numtmp, "prmsys": prmsys}
dicres = asyncio.run(req001(dicreq))
print(dicres["txtres"])
利用
コンテナ上で、サーバを起動し〜クライアントを実行します:
# サーバ $ python /system/vol104/bin/llmsrv.py & # クライアント $ python /system/vol104/bin/llmclt.py 34 0.75 'あなたは優れた助言者です' 'おはよう'
