チャットのエンジンと、そのサーバ〜クライアントを作ってみます。
関連
- ◯
- サーバ証明書を取得〜更新する(ワイルドカード証明書、ネームサーバ経由):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 'あなたは優れた助言者です' 'おはよう'