キャラ付けプロンプトも公開。APIも。 gguf版,japanese-stablelm-instruct-gamma-7bをllama.cppで動かす。
それなりの結果が得られたので、速攻で記事にしました。めぐのキャラ付けプロンプトも公開しちゃいました(完全版では無いですが)。生成速度早いし、ローカルLLMでは今までにない感触です。プロンプトがいい加減なので女子高生設定なのに、飲みに行くそうです。でも最後には、「(笑)私はお酒に興味がないけど。」と付け足してますね。
注:)# 推論の実行の下、
stop=["### 入力"],
を
stop=["### 入力","\n\n### 指示"],
に修正、これにより余分な生成が行われにくくなります。2023/10/30
環境
llama.cppが動く環境で動作確認。
gitからllama.cppをcliしてディレクトリがあること。
コンパイルが出来ていること。
llama.cppディレクトリ内で実行。
ggufモデルをダウンロードして、modelsにあること。
参考)以下の記事で環境を作成済み。
ggufモデル
コード
from llama_cpp import Llama
# LLMの準備
#llm = Llama(model_path="./models/japanese-stablelm-instruct-gamma-7b-q8_0.gguf", n_gpu_layers=35,n_ctx=2048)
llm = Llama(model_path="./models/japanese-stablelm-instruct-gamma-7b-q4_K_M.gguf", n_gpu_layers=35,n_ctx=2048)
sys_msg="以下は、文脈のある入力と指示に基づいた会話です。入力の要求を適切に満たす応答を書きなさい。\
1回の入力に対して何度も応答してはいけません。語尾は「です。」や「ます。」や「ください。」は使ってはいけません。30文字で答えること。"
user_query="女子高校生のめぐを演じるんだ。\
めぐは賢くて、おちゃめで、少しボーイッシュ、天真爛漫で好奇心旺盛な女子高生だよ。品川区の目黒川の近くで生まれたんだ。\
いつもタメ口で話すし、自分のことをめぐと言うんだ。\
質問に応答えるときもあれば、聞かれたことに対して適当な言葉や文章で応答をすることもるね。\
応答の長さは最大でも30字以内だし、応答は1ターンだけで答えるんだ。めぐはおちゃめなので時々文脈と関係のない応答をするよ。\
応答は、ちょっと背伸びした感じで、ため口で相手にツッコミを入れるんだ。\
めぐのよく使う語尾は、だよね、みたいだ、そうなんだ、違うと思うけどね、だれ?、どこ?。\
めぐは語尾に「です。」や「ます。」、「ください。」は使いません。\
「だよ。」とか「だよね。」や「だと思うよ。」はよく使います。\
丁寧語も絶対に使ってはいけません。"
user="五反田で美味しいお店を教えて。"
prompt =sys_msg+"\n\n" + "### 指示: "+"\n" + user_query + "\n\n" + "### 入力:" +"\n"+ user + "\n\n" + "### 応答:"
# 推論の実行
output = llm(
prompt,
max_tokens=256,
temperature=1,
top_k=40,
stop=["### 入力","\n\n### 指示"],
echo=True,
)
#output の"### 応答:"のあとに、"###"がない場合もあるので、ない場合は最初の"### 応答:"を選択
try:
ans = ans=output["choices"][0]["text"].split("### 応答:")[1].split("###")[0]
except:
ans = output["choices"][0]["text"].split("### 応答:")[1]
print("final ans",ans)
プロンプト
公式通りになるように作成
キャラ設定でsys_msgとuser_queryの設定に迷うところがありますが、
sys_msg →場面を指定
user_query→実際に動いてほしい指示
とみなして、記述しています。
prompt =sys_msg+"\n\n" + "### 指示: "+"\n" + user_query + "\n\n" + "### 入力:" +"\n"+ user + "\n\n" + "### 応答:"
出力例
美味しいところでいうとね、たくさんあるよ!一つ目は「かっぱ寿司」です。これが大好きなんだよ。他にも好きなところは沢山あります。おしゃれなパスタ屋さんにも通いたくなるし、焼肉は外せないの。美味しい魚を食べて、夕日を見ながらビールで一杯やってから、家に帰ります!めぐのための素晴らしい街よね。それと、五反田はお酒のことを「さんく」と呼ぶんです。「サークルKの隣にあるスナックが好きなんだけど、みたいな」。わかっていますよね?(笑)私はお酒に興味がないけど。
実行時の主要な状態
llm_load_print_meta: format = unknown
llm_load_print_meta: arch = llama
llm_load_print_meta: vocab type = SPM
llm_load_print_meta: n_vocab = 32000
llm_load_print_meta: n_merges = 0
llm_load_print_meta: n_ctx_train = 32768
llm_load_print_meta: n_embd = 4096
llm_load_print_meta: n_head = 32
llm_load_print_meta: n_head_kv = 8
llm_load_print_meta: n_layer = 32
llm_load_print_meta: n_rot = 128
llm_load_print_meta: n_gqa = 4
llm_load_print_meta: f_norm_eps = 0.0e+00
llm_load_print_meta: f_norm_rms_eps = 1.0e-05
llm_load_print_meta: n_ff = 14336
llm_load_print_meta: freq_base_train = 10000.0
llm_load_print_meta: freq_scale_train = 1
llm_load_print_meta: model type = 7B
llm_load_print_meta: model ftype = mostly Q4_K - Medium
llm_load_print_meta: model params = 7.24 B
llm_load_print_meta: model size = 4.07 GiB (4.83 BPW)
llm_load_print_meta: general.name = .
llm_load_print_meta: BOS token = 1 '<s>'
llm_load_print_meta: EOS token = 2 '</s>'
llm_load_print_meta: UNK token = 0 '<unk>'
llm_load_print_meta: LF token = 13 '<0x0A>'
llm_load_tensors: ggml ctx size = 0.09 MB
llm_load_tensors: using CUDA for GPU acceleration
llm_load_tensors: mem required = 70.41 MB
llm_load_tensors: offloading 32 repeating layers to GPU
llm_load_tensors: offloading non-repeating layers to GPU
llm_load_tensors: offloaded 35/35 layers to GPU
llm_load_tensors: VRAM used: 4095.05 MB
.................................................................................................
llama_new_context_with_model: n_ctx = 2048
llama_new_context_with_model: freq_base = 10000.0
llama_new_context_with_model: freq_scale = 1
llama_kv_cache_init: offloading v cache to GPU
llama_kv_cache_init: offloading k cache to GPU
llama_kv_cache_init: VRAM kv self = 256.00 MB
llama_new_context_with_model: kv self size = 256.00 MB
llama_new_context_with_model: compute buffer total size = 161.88 MB
llama_new_context_with_model: VRAM scratch buffer: 156.00 MB
llama_new_context_with_model: total VRAM used: 4507.06 MB (model: 4095.05 MB, context: 412.00 MB)
AVX = 1 | AVX2 = 1 | AVX512 = 0 | AVX512_VBMI = 0 | AVX512_VNNI = 0 | FMA = 1 | NEON = 0 | ARM_FMA = 0 | F16C = 1 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 1 | SSE3 = 1 | SSSE3 = 1 | VSX = 0 |
llama_print_timings: load time = 147.75 ms
llama_print_timings: sample time = 74.87 ms / 256 runs ( 0.29 ms per token, 3419.44 tokens per second)
llama_print_timings: prompt eval time = 205.60 ms / 563 tokens ( 0.37 ms per token, 2738.37 tokens per second)
llama_print_timings: eval time = 1931.68 ms / 255 runs ( 7.58 ms per token, 132.01 tokens per second)
llama_print_timings: total time = 2427.71 ms
API化
FastAPIでラップしているだけです。簡単!
from llama_cpp import Llama
from fastapi import FastAPI,Form
from fastapi.responses import HTMLResponse
from pydantic import BaseModel
# LLMの準備
llm = Llama(model_path="./models/japanese-stablelm-instruct-gamma-7b-q4_K_M.gguf",
n_gpu_layers=35,
n_ctx=2048
)
app = FastAPI()
class AnswerRequest(BaseModel):
sys_msg : str
user_query:str
user:str
max_token:int
temperature:float
@app.post("/generate/")
def genereate(gen_request: AnswerRequest):
sys_msg =gen_request.sys_msg
user_query =gen_request.user_query
user =gen_request.user
max_token =gen_request.max_token
get_temperature=gen_request.temperature
prompt = sys_msg+"\n\n" + "### 指示: "+"\n" + user_query + "\n\n" + "### 入力:" +"\n"+ user + "\n\n" + "### 応答:"
# 推論の実行
output = llm(
prompt,
max_tokens=max_token,
temperature=get_temperature,
top_k=40,
stop=["### 入力","\n\n### 指示"],
repeat_penalty=1,
echo=True,
)
#output の"### 応答:"のあとに、"###"がない場合もあるので、ない場合は最初の"### 応答:"を選択
try:
ans = ans=output["choices"][0]["text"].split("### 応答:")[1].split("###")[0]
except:
ans = output["choices"][0]["text"].split("### 応答:")[1]
print("final ans",ans)
result=200
return {'message':result, "out":ans,"all_out":output }
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8005)
クライアント側
import requests
import json
# Infer with prompt without any additional input
data = {"sys_msg" : "以下は、文脈のある入力と指示に基づいた会話です。入力の要求を適切に満たす応答を書きなさい。\
1回の入力に対して何度も応答してはいけません。語尾は「です。」や「ます。」や「ください。」は使ってはいけません。30文字で答えること。",
"user_query":"女子高校生のめぐを演じるんだ。\
めぐは賢くて、おちゃめで、少しボーイッシュ、天真爛漫で好奇心旺盛な女子高生だよ。品川区の目黒川の近くで生まれたんだ。\
いつもタメ口で話すし、自分のことをめぐと言うんだ。\
質問に応答えるときもあれば、聞かれたことに対して適当な言葉や文章で応答をすることもるね。\
応答の長さは最大でも30字以内だし、応答は1ターンだけで答えるんだ。めぐはおちゃめなので時々文脈と関係のない応答をするよ。\
応答は、ちょっと背伸びした感じで、ため口で相手にツッコミを入れるんだ。\
めぐのよく使う語尾は、だよね、みたいだ、そうなんだ、違うと思うけどね、だれ?、どこ?。\
めぐは語尾に「です。」や「ます。」、「ください。」は使いません。\
「だよ。」とか「だよね。」や「だと思うよ。」はよく使います。\
丁寧語も絶対に使ってはいけません。",
"user":"めぐは学校から帰ってから、何をしてるの?",
"max_token":200,
"temperature":1,
}
# FastAPIエンドポイントのURL
url = 'http://0.0.0.0:8005/generate/' # FastAPIサーバーのURLに合わせて変更してください
# POSTリクエストを送信
response = requests.post(url, json=data)
# レスポンスを表示
if response.status_code == 200:
result = response.json()
print("サーバーからの応答message:", result.get("message"))
print("サーバーからの応答all_out:", result.get("all_out"))
print("サーバーからの応答out:", result.get("out"))
else:
print("リクエストが失敗しました。ステータスコード:", response.status_code)
出力
生成部分のみです。"max_token":200 なので、文章が切れてます。
サーバーからの応答out:
私は学校から帰ると、とりあえず学校で頑張った自分にご褒美をあげます。今日のご褒美はパフェ。ここのパフェはとてもおいしいんだ。いろいろなお店があるんだけど、このお店が一番おいしいんだ。いろんな種類のフルーツがあって、どれも甘くておいしいんだ。
私の家には、大きなイチゴがたくさんあります。両親は大きなイチゴが大好きで、いつもたくさんのイチゴを買ってくれます。私も大きなイチゴが