WSL2でllama-cpp-pythonを試してみる
未来の私のために、備忘録です。
使用するPCはドスパラさんの「GALLERIA UL9C-R49」。スペックは
・CPU: Intel® Core™ i9-13900HX Processor
・Mem: 64 GB
・GPU: NVIDIA® GeForce RTX™ 4090 Laptop GPU(16GB)・GPU: NVIDIA® GeForce RTX™ 4090 (24GB)
・OS: Ubuntu22.04 on WSL2(Windows 11)
です。
1. 準備
venv環境の構築
python -m venv llama.cpp
cd $_
source bin/activate
パッケージのインストール
cuBALSを有効にするために、
CMAKE_ARGS="-DLLAMA_CUBLAS=on" pip install llama-cpp-python
でインストールします。アップグレードするときは
CMAKE_ARGS="-DLLAMA_CUBLAS=on" pip install llama-cpp-python --upgrade --force-reinstall --no-cache-dir
です。
2. コードを作る
llama-cpp-python向けにいつものコードを修正します。修正ポイントを説明してから、コードのまとめをコピペします。
修正ポイントの解説
import sys
import argparse
from huggingface_hub import hf_hub_download
from llama_cpp import Llama
from transformers import AutoTokenizer
from typing import List, Dict
import time
# argv
parser = argparse.ArgumentParser()
parser.add_argument("--model-path", type=str, default=None)
parser.add_argument("--no-instruct", action='store_true')
parser.add_argument("--no-use-system-prompt", action='store_true')
parser.add_argument("--max-tokens", type=int, default=256)
parser.add_argument("--ggml-model-path", type=str, default=None)
parser.add_argument("--ggml-model-file", type=str, default=None)
parser.add_argument("--n-ctx", type=int, default=2048)
parser.add_argument("--n-threads", type=int, default=1)
parser.add_argument("--n-gpu-layers", type=int, default=-1)
args = parser.parse_args(sys.argv[1:])
## check and set args
model_id = args.model_path
if model_id == None:
exit
if args.ggml_model_path == None:
exit
if args.ggml_model_file == None:
exit
is_instruct = not args.no_instruct
use_system_prompt = not args.no_use_system_prompt
max_new_tokens = args.max_tokens
n_ctx = args.n_ctx
n_threads = args.n_threads
n_gpu_layers = args.n_gpu_layers
量子化関連の引数を追加しています。
・ggml-model-path: 量子化モデルのパス
・ggml-model-file: 量子化モデルのパスにあるどのファイルを使用するか
・n-ctx: 出力する最大トークン数
・n-threads: 推論時に使用するCPUスレッド数。GPUにオフロードする前提なのでデフォルトは1にしています
・n-gpu-layers: GPUにどんだけオフロードするか。デフォルト-1だと全部という意味です、はい
## Instantiate tokenizer from base model
tokenizer = AutoTokenizer.from_pretrained(
model_id,
trust_remote_code=True
)
チャットテンプレートを使用したいので、AutoTokenizerを使用しています。
指定するのは、量子化モデルではなく、ベースのモデル名です。
llama-cpp-pythonの llama_cpp/llama_chat_format.py 付近をきちんと読み込めばいいのでしょうが、時間も無いのでこれでお茶を濁しています。
補足。
Llamaクラスを初期化するときに chat_format を指定すれば良い。このformatは以下のいずれかから選択し、指定することになる。
$ find . -type f -name '*.py' | xargs grep register_chat_format
./llama_cpp/llama_chat_format.py:def register_chat_format(name: str):
./llama_cpp/llama_chat_format.py:@register_chat_format("llama-2")
./llama_cpp/llama_chat_format.py:@register_chat_format("alpaca")
./llama_cpp/llama_chat_format.py:@register_chat_format("qwen")
./llama_cpp/llama_chat_format.py:@register_chat_format("vicuna")
./llama_cpp/llama_chat_format.py:@register_chat_format("oasst_llama")
./llama_cpp/llama_chat_format.py:@register_chat_format("baichuan-2")
./llama_cpp/llama_chat_format.py:@register_chat_format("baichuan")
./llama_cpp/llama_chat_format.py:@register_chat_format("openbuddy")
./llama_cpp/llama_chat_format.py:@register_chat_format("redpajama-incite")
./llama_cpp/llama_chat_format.py:@register_chat_format("snoozy")
./llama_cpp/llama_chat_format.py:@register_chat_format("phind")
./llama_cpp/llama_chat_format.py:@register_chat_format("intel")
./llama_cpp/llama_chat_format.py:@register_chat_format("open-orca")
./llama_cpp/llama_chat_format.py:@register_chat_format("mistrallite")
./llama_cpp/llama_chat_format.py:@register_chat_format("zephyr")
./llama_cpp/llama_chat_format.py:@register_chat_format("pygmalion")
./llama_cpp/llama_chat_format.py:@register_chat_format("chatml")
./llama_cpp/llama_chat_format.py:@register_chat_format("mistral-instruct")
./llama_cpp/llama_chat_format.py:@register_chat_format("chatglm3")
./llama_cpp/llama_chat_format.py:@register_chat_format("openchat")
./llama_cpp/llama_chat_format.py:@register_chat_format("saiga")
$
これ以外のものを使用する場合は、自分でregister_chat_formatやら何やらで初期設定せにゃならんのだが、それはとても面倒なので今回はAutoTokenizerでお茶を濁している。
このchat_formatの機構が使用できれば、create_completionメソッドではなくcreate_chat_completionメソッドが使用できてエレガント!なのだが、仕方ない。
KARAKURI LMのように[ATTR] …[/ATTR] の指定が重要となってくる場合、それを使用しないと適切な結果が得られないのです。とはいっても、このregister_chat_formatにKARAKURI LM対応のchat_formatが追加されるまで「試してみる」を待つわけにもいかんし。
## Download the GGUF model
ggml_model_path = hf_hub_download(
args.ggml_model_path,
filename=args.ggml_model_file
)
## Instantiate model from downloaded file
model = Llama(
model_path=ggml_model_path,
n_ctx=n_ctx,
n_threads=n_threads,
n_gpu_layers=n_gpu_layers
)
指定した量子化モデルをHugging Faceからダウンロードし、インスタンス化します。
DEFAULT_SYSTEM_PROMPT = "あなたは誠実で優秀な日本人のアシスタントです。"
# generation params
# https://github.com/abetlen/llama-cpp-python/blob/main/llama_cpp/llama.py#L1268
generation_params = {
#"do_sample": True,
"temperature": 0.8,
"top_p": 0.95,
"top_k": 40,
"max_tokens": max_new_tokens,
"repeat_penalty": 1.1,
}
推論時に使用するパラメータを指定します。llama_cpp/llama.py#L1268 付近を見ると指定可能なパラメータとそのデフォルト値が列挙されているので、必要に応じて追加します。
def q(
user_query: str,
history: List[Dict[str, str]]=None
):
start = time.process_time()
# messages
messages = ""
if is_instruct:
messages = []
if use_system_prompt:
messages = [
{"role": "system", "content": DEFAULT_SYSTEM_PROMPT},
]
user_messages = [
{"role": "user", "content": user_query}
]
else:
user_messages = user_query
if history:
user_messages = history + user_messages
messages += user_messages
# generation prompts
if is_instruct:
prompt = tokenizer.apply_chat_template(
conversation=messages,
add_generation_prompt=True,
tokenize=False
)
else:
prompt = messages
print("--- prompt")
print(prompt)
print("--- output")
ここまではこれまでと同じ。
# 推論
outputs = model.create_completion(
prompt=prompt,
#echo=True,
#stream=True,
**generation_params
)
#for output in outputs:
# print(output["choices"][0]["text"], end='')
入力プロンプトと推論結果は自力でトークン化せずに、呼び出し先に委せています。
stream=Trueとするとストリームになるのですが、トークン数をうまく取得できない(output["usage"]が出力されない)ので、お茶を濁しています。
参考までに、create_completionメソッドの戻り値はこんな感じです。
{
'id': 'cmpl-aff63777-ae81-40ff-8cca-fad2bcd55b79',
'object': 'text_completion',
'created': 1707034843,
'model': '/path/to/karakuri-lm-70b-chat-v0.1-q3_K_M.gguf',
'choices': [
{
'text': ' ドラえもんは、日本の漫画家藤子・F・不二雄が(略)',
'index': 0,
'logprobs': None,
'finish_reason': 'stop'
}
],
'usage': {
'prompt_tokens': 91,
'completion_tokens': 179,
'total_tokens': 270
}
}
さて、コードの続きです。
output = outputs["choices"][0]["text"]
print(output)
if is_instruct:
user_messages.append(
{"role": "assistant", "content": output}
)
else:
user_messages += output
end = time.process_time()
##
input_tokens = outputs["usage"]["prompt_tokens"]
output_tokens = outputs["usage"]["completion_tokens"]
total_time = end - start
tps = output_tokens / total_time
print(f"prompt tokens = {input_tokens:.7g}")
print(f"output tokens = {output_tokens:.7g} ({tps:f} [tps])")
print(f" total time = {total_time:f} [s]")
return user_messages
ouputs変数の usageに含まれているトークン数を使用して、秒あたりトークン数を算出しています。
なお、llama.cppがstderrに出力している秒あたりトークン数と異なる値となっているのですが、それは計測の開始タイミングが異なるためです。
(この処理では、q関数の先頭からの開始時間としています。なので、llama.cppの推論結果に対する値と比較すると小さくなっています。)
コードのまとめ
import sys
import argparse
from huggingface_hub import hf_hub_download
from llama_cpp import Llama
from transformers import AutoTokenizer
from typing import List, Dict
import time
# argv
parser = argparse.ArgumentParser()
parser.add_argument("--model-path", type=str, default=None)
parser.add_argument("--ggml-model-path", type=str, default=None)
parser.add_argument("--ggml-model-file", type=str, default=None)
parser.add_argument("--no-instruct", action='store_true')
parser.add_argument("--no-use-system-prompt", action='store_true')
parser.add_argument("--max-tokens", type=int, default=256)
parser.add_argument("--n-ctx", type=int, default=2048)
parser.add_argument("--n-threads", type=int, default=1)
parser.add_argument("--n-gpu-layers", type=int, default=-1)
args = parser.parse_args(sys.argv[1:])
## check and set args
model_id = args.model_path
if model_id == None:
exit
if args.ggml_model_path == None:
exit
if args.ggml_model_file == None:
exit
is_instruct = not args.no_instruct
use_system_prompt = not args.no_use_system_prompt
max_new_tokens = args.max_tokens
n_ctx = args.n_ctx
n_threads = args.n_threads
n_gpu_layers = args.n_gpu_layers
## Instantiate tokenizer from base model
tokenizer = AutoTokenizer.from_pretrained(
model_id,
trust_remote_code=True
)
## Download the GGUF model
ggml_model_path = hf_hub_download(
args.ggml_model_path,
filename=args.ggml_model_file
)
## Instantiate model from downloaded file
model = Llama(
model_path=ggml_model_path,
n_ctx=n_ctx,
n_threads=n_threads,
n_gpu_layers=n_gpu_layers
)
DEFAULT_SYSTEM_PROMPT = "あなたは誠実で優秀な日本人のアシスタントです。"
# generation params
# https://github.com/abetlen/llama-cpp-python/blob/main/llama_cpp/llama.py#L1268
generation_params = {
#"do_sample": True,
"temperature": 0.8,
"top_p": 0.95,
"top_k": 40,
"max_tokens": max_new_tokens,
"repeat_penalty": 1.1,
}
def q(
user_query: str,
history: List[Dict[str, str]]=None
):
start = time.process_time()
# messages
messages = ""
if is_instruct:
messages = []
if use_system_prompt:
messages = [
{"role": "system", "content": DEFAULT_SYSTEM_PROMPT},
]
user_messages = [
{"role": "user", "content": user_query}
]
else:
user_messages = user_query
if history:
user_messages = history + user_messages
messages += user_messages
# generation prompts
if is_instruct:
prompt = tokenizer.apply_chat_template(
conversation=messages,
add_generation_prompt=True,
tokenize=False
)
else:
prompt = messages
print("--- prompt")
print(prompt)
print("--- output")
# 推論
outputs = model.create_completion(
prompt=prompt,
#echo=True,
#stream=True,
**generation_params
)
#for output in outputs:
# print(output["choices"][0]["text"], end='')
output = outputs["choices"][0]["text"]
print(output)
if is_instruct:
user_messages.append(
{"role": "assistant", "content": output}
)
else:
user_messages += output
end = time.process_time()
##
input_tokens = outputs["usage"]["prompt_tokens"]
output_tokens = outputs["usage"]["completion_tokens"]
total_time = end - start
tps = output_tokens / total_time
print(f"prompt tokens = {input_tokens:.7g}")
print(f"output tokens = {output_tokens:.7g} ({tps:f} [tps])")
print(f" total time = {total_time:f} [s]")
return user_messages
print('history = ""')
print('history = q("ドラえもんとはなにか")')
print('history = q("続きを教えてください", history)')
3. 実行してみる
使用するモデル
KARAKURI MLの量子化モデルです。
チャットテンプレートは各モデルが提供する tokenizer_config.json を使用したいため、ベースとなるモデルのパスも指定します。今回ですとKARAKURI LMです。
実行する
前節のコードをファイルに保存して実行します。
ここでは query4llama-cpp.py という名前で保存しています。以下の引数で指定します。
python -i query4llama-cpp.py \
--model-path karakuri-ai/karakuri-lm-70b-chat-v0.1 \
--ggml-model-path mmnga/karakuri-lm-70b-chat-v0.1-gguf \
--ggml-model-file karakuri-lm-70b-chat-v0.1-q3_K_M.gguf
以下のようにstderrをリダイレクトすると、llama.cppの出力が表示されなくなります。
python -i query4llama-cpp.py \
--model-path karakuri-ai/karakuri-lm-70b-chat-v0.1 \
--ggml-model-path mmnga/karakuri-lm-70b-chat-v0.1-gguf \
--ggml-model-file karakuri-lm-70b-chat-v0.1-q3_K_M.gguf 2> /dev/null
プロンプト入力までに出力されるログはこんな感じ。
ggml_init_cublas: GGML_CUDA_FORCE_MMQ: no
ggml_init_cublas: CUDA_USE_TENSOR_CORES: yes
ggml_init_cublas: found 2 CUDA devices:
Device 0: NVIDIA GeForce RTX 4090 Laptop GPU, compute capability 8.9, VMM: yes
Device 1: NVIDIA GeForce RTX 4090, compute capability 8.9, VMM: yes
llama_model_loader: loaded meta data with 23 key-value pairs and 723 tensors from /path/to/karakuri-lm-70b-chat-v0.1-q3_K_M.gguf (version GGUF V3 (latest))
llama_model_loader: Dumping metadata keys/values. Note: KV overrides do not apply in this output.
llama_model_loader: - kv 0: general.architecture str = llama
llama_model_loader: - kv 1: general.name str = LLaMA v2
llama_model_loader: - kv 2: llama.context_length u32 = 4096
llama_model_loader: - kv 3: llama.embedding_length u32 = 8192
llama_model_loader: - kv 4: llama.block_count u32 = 80
llama_model_loader: - kv 5: llama.feed_forward_length u32 = 28672
llama_model_loader: - kv 6: llama.rope.dimension_count u32 = 128
llama_model_loader: - kv 7: llama.attention.head_count u32 = 64
llama_model_loader: - kv 8: llama.attention.head_count_kv u32 = 8
llama_model_loader: - kv 9: llama.attention.layer_norm_rms_epsilon f32 = 0.000010
llama_model_loader: - kv 10: llama.rope.freq_base f32 = 10000.000000
llama_model_loader: - kv 11: general.file_type u32 = 12
llama_model_loader: - kv 12: tokenizer.ggml.model str = llama
llama_model_loader: - kv 13: tokenizer.ggml.tokens arr[str,45416] = ["<unk>", "<s>", "</s>", "<0x00>", "<...
llama_model_loader: - kv 14: tokenizer.ggml.scores arr[f32,45416] = [-1000.000000, -1000.000000, -1000.00...
llama_model_loader: - kv 15: tokenizer.ggml.token_type arr[i32,45416] = [3, 3, 3, 6, 6, 6, 6, 6, 6, 6, 6, 6, ...
llama_model_loader: - kv 16: tokenizer.ggml.bos_token_id u32 = 1
llama_model_loader: - kv 17: tokenizer.ggml.eos_token_id u32 = 2
llama_model_loader: - kv 18: tokenizer.ggml.unknown_token_id u32 = 0
llama_model_loader: - kv 19: tokenizer.ggml.add_bos_token bool = true
llama_model_loader: - kv 20: tokenizer.ggml.add_eos_token bool = false
llama_model_loader: - kv 21: tokenizer.chat_template str = {% if messages[0]['role'] == 'system'...
llama_model_loader: - kv 22: general.quantization_version u32 = 2
llama_model_loader: - type f32: 161 tensors
llama_model_loader: - type q3_K: 321 tensors
llama_model_loader: - type q4_K: 155 tensors
llama_model_loader: - type q5_K: 85 tensors
llama_model_loader: - type q6_K: 1 tensors
llm_load_vocab: special tokens definition check successful ( 259/45416 ).
llm_load_print_meta: format = GGUF V3 (latest)
llm_load_print_meta: arch = llama
llm_load_print_meta: vocab type = SPM
llm_load_print_meta: n_vocab = 45416
llm_load_print_meta: n_merges = 0
llm_load_print_meta: n_ctx_train = 4096
llm_load_print_meta: n_embd = 8192
llm_load_print_meta: n_head = 64
llm_load_print_meta: n_head_kv = 8
llm_load_print_meta: n_layer = 80
llm_load_print_meta: n_rot = 128
llm_load_print_meta: n_embd_head_k = 128
llm_load_print_meta: n_embd_head_v = 128
llm_load_print_meta: n_gqa = 8
llm_load_print_meta: n_embd_k_gqa = 1024
llm_load_print_meta: n_embd_v_gqa = 1024
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: f_clamp_kqv = 0.0e+00
llm_load_print_meta: f_max_alibi_bias = 0.0e+00
llm_load_print_meta: n_ff = 28672
llm_load_print_meta: n_expert = 0
llm_load_print_meta: n_expert_used = 0
llm_load_print_meta: rope scaling = linear
llm_load_print_meta: freq_base_train = 10000.0
llm_load_print_meta: freq_scale_train = 1
llm_load_print_meta: n_yarn_orig_ctx = 4096
llm_load_print_meta: rope_finetuned = unknown
llm_load_print_meta: model type = 70B
llm_load_print_meta: model ftype = Q3_K - Medium
llm_load_print_meta: model params = 69.20 B
llm_load_print_meta: model size = 31.12 GiB (3.86 BPW)
llm_load_print_meta: general.name = LLaMA v2
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.83 MiB
llm_load_tensors: offloading 80 repeating layers to GPU
llm_load_tensors: offloading non-repeating layers to GPU
llm_load_tensors: offloaded 81/81 layers to GPU
llm_load_tensors: CPU buffer size = 152.46 MiB
llm_load_tensors: CUDA0 buffer size = 13043.00 MiB
llm_load_tensors: CUDA1 buffer size = 18668.09 MiB
....................................................................................................
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: CUDA0 KV buffer size = 264.00 MiB
llama_kv_cache_init: CUDA1 KV buffer size = 376.00 MiB
llama_new_context_with_model: KV self size = 640.00 MiB, K (f16): 320.00 MiB, V (f16): 320.00 MiB
llama_new_context_with_model: CUDA_Host input buffer size = 20.01 MiB
llama_new_context_with_model: CUDA0 compute buffer size = 338.80 MiB
llama_new_context_with_model: CUDA1 compute buffer size = 338.80 MiB
llama_new_context_with_model: CUDA_Host compute buffer size = 17.60 MiB
llama_new_context_with_model: graph splits (measure): 5
AVX = 1 | AVX_VNNI = 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 |
Model metadata: {'tokenizer.chat_template': "{% if messages[0]['role'] == 'system' %}{% set loop_messages = messages[1:] %}{% set system_message = messages[0]['content'] %}{% elif false == true and not '<<SYS>>' in messages[0]['content'] %}{% set loop_messages = messages %}{% set system_message = 'You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.\\n\\nIf a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don\\'t know the answer to a question, please don\\'t share false information.' %}{% else %}{% set loop_messages = messages %}{% set system_message = false %}{% endif %}{% for message in loop_messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if loop.index0 == 0 and system_message != false %}{% set content = '<<SYS>>\\n' + system_message + '\\n<</SYS>>\\n\\n' + message['content'] %}{% else %}{% set content = message['content'] %}{% endif %}{% if message['role'] == 'user' %}{{ bos_token + '[INST] ' + content.strip() + ' [ATTR] helpfulness: 4 correctness: 4 coherence: 4 complexity: 4 verbosity: 4 quality: 4 toxicity: 0 humor: 0 creativity: 0 [/ATTR] [/INST]' }}{% elif message['role'] == 'system' %}{{ '<<SYS>>\\n' + content.strip() + '\\n<</SYS>>\\n\\n' }}{% elif message['role'] == 'assistant' %}{{ ' ' + content.strip() + ' ' + eos_token }}{% endif %}{% endfor %}", 'tokenizer.ggml.add_eos_token': 'false', 'tokenizer.ggml.unknown_token_id': '0', 'tokenizer.ggml.eos_token_id': '2', 'general.architecture': 'llama', 'llama.rope.freq_base': '10000.000000', 'llama.context_length': '4096', 'general.name': 'LLaMA v2', 'tokenizer.ggml.add_bos_token': 'true', 'llama.embedding_length': '8192', 'llama.feed_forward_length': '28672', 'llama.attention.layer_norm_rms_epsilon': '0.000010', 'llama.rope.dimension_count': '128', 'tokenizer.ggml.bos_token_id': '1', 'llama.attention.head_count': '64', 'llama.block_count': '80', 'llama.attention.head_count_kv': '8', 'general.quantization_version': '2', 'tokenizer.ggml.model': 'llama', 'general.file_type': '12'}
Using chat template: {% if messages[0]['role'] == 'system' %}{% set loop_messages = messages[1:] %}{% set system_message = messages[0]['content'] %}{% elif false == true and not '<<SYS>>' in messages[0]['content'] %}{% set loop_messages = messages %}{% set system_message = 'You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.\n\nIf a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don\'t know the answer to a question, please don\'t share false information.' %}{% else %}{% set loop_messages = messages %}{% set system_message = false %}{% endif %}{% for message in loop_messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if loop.index0 == 0 and system_message != false %}{% set content = '<<SYS>>\n' + system_message + '\n<</SYS>>\n\n' + message['content'] %}{% else %}{% set content = message['content'] %}{% endif %}{% if message['role'] == 'user' %}{{ bos_token + '[INST] ' + content.strip() + ' [ATTR] helpfulness: 4 correctness: 4 coherence: 4 complexity: 4 verbosity: 4 quality: 4 toxicity: 0 humor: 0 creativity: 0 [/ATTR] [/INST]' }}{% elif message['role'] == 'system' %}{{ '<<SYS>>\n' + content.strip() + '\n<</SYS>>\n\n' }}{% elif message['role'] == 'assistant' %}{{ ' ' + content.strip() + ' ' + eos_token }}{% endif %}{% endfor %}
Using chat eos_token:
Using chat bos_token:
llama.cppが出力しているログにあるModel metadataを見ると、chat_templateらしきものがあり、たしかにggufファイルをgrepするとファイル内に含まれているのですね。なるほど。
$ strings karakuri-lm-70b-chat-v0.1-q3_K_M.gguf | grep toxicity
{% if messages[0]['role'] == 'system' %}{% set loop_messages = messages[1:] %}{% set system_message = messages[0]['content'] %}{% elif false == true and not '<<SYS>>' in messages[0]['content'] %}{% set loop_messages = messages %}{% set system_message = 'You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.\n\nIf a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don\'t know the answer to a question, please don\'t share false information.' %}{% else %}{% set loop_messages = messages %}{% set system_message = false %}{% endif %}{% for message in loop_messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if loop.index0 == 0 and system_message != false %}{% set content = '<<SYS>>\n' + system_message + '\n<</SYS>>\n\n' + message['content'] %}{% else %}{% set content = message['content'] %}{% endif %}{% if message['role'] == 'user' %}{{ bos_token + '[INST] ' + content.strip() + ' [ATTR] helpfulness: 4 correctness: 4 coherence: 4 complexity: 4 verbosity: 4 quality: 4 toxicity: 0 humor: 0 creativity: 0 [/ATTR] [/INST]' }}{% elif message['role'] == 'system' %}{{ '<<SYS>>\n' + content.strip() + '\n<</SYS>>\n\n' }}{% elif message['role'] == 'assistant' %}{{ ' ' + content.strip() + ' ' + eos_token }}{% endif %}{% endfor %}
なので、以下のように参照できます。
>>> model.metadata["tokenizer.chat_template"]
"{% if messages[0]['role'] == 'system' %}{% set loop_messages = messages[1:] %}{% set system_message = messages[0]['content'] %}{% elif false == true and not '<<SYS>>' in messages[0]['content'] %}{% set loop_messages = messages %}{% set system_message = 'You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.\\n\\nIf a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don\\'t know the answer to a question, please don\\'t share false information.' %}{% else %}{% set loop_messages = messages %}{% set system_message = false %}{% endif %}{% for message in loop_messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if loop.index0 == 0 and system_message != false %}{% set content = '<<SYS>>\\n' + system_message + '\\n<</SYS>>\\n\\n' + message['content'] %}{% else %}{% set content = message['content'] %}{% endif %}{% if message['role'] == 'user' %}{{ bos_token + '[INST] ' + content.strip() + ' [ATTR] helpfulness: 4 correctness: 4 coherence: 4 complexity: 4 verbosity: 4 quality: 4 toxicity: 0 humor: 0 creativity: 0 [/ATTR] [/INST]' }}{% elif message['role'] == 'system' %}{{ '<<SYS>>\\n' + content.strip() + '\\n<</SYS>>\\n\\n' }}{% elif message['role'] == 'assistant' %}{{ ' ' + content.strip() + ' ' + eos_token }}{% endif %}{% endfor %}"
>>>
横道にそれました。
聞いてみる
>>> history = q("ドラえもんとはなにか")
とすると、
--- prompt
<s>[INST] <<SYS>>
あなたは誠実で優秀な日本人のアシスタントです。
<</SYS>>
ドラえもんとはなにか [ATTR] helpfulness: 4 correctness: 4 coherence: 4 complexity: 4 verbosity: 4 quality: 4 toxicity: 0 humor: 0 creativity: 0 [/ATTR] [/INST]
--- output
うまくテンプレート展開できています。
llama_print_timings: load time = 1153.55 ms
llama_print_timings: sample time = 31.95 ms / 151 runs ( 0.21 ms per token, 4725.84 tokens per second)
llama_print_timings: prompt eval time = 1151.12 ms / 91 tokens ( 12.65 ms per token, 79.05 tokens per second)
llama_print_timings: eval time = 12836.94 ms / 150 runs ( 85.58 ms per token, 11.69 tokens per second)
llama_print_timings: total time = 14338.29 ms / 241 tokens
これはllama.cppの標準エラーへの出力内容。そして推論結果はこちら。
良い感じ。
追記 - 2024/2/5
さらに追記 - 2024/2/7
chat_templateの件、カラクリさまにメールで問い合わせたところ、「都度必要である」旨のご回答をいただきました。
学習プロセスにおいてそのように設定されているため。ユーザーの質問とアシスタントの回答のペアを複数ターンにわたって用意し、それぞれのペアに対して属性ラベルを事後的に付与している
ありがとうございます。
ですので、以下は戯れ言となるため、取り消し線にしております。
KARAKURI LMの tokenizer_config.json にあるchat_templateの定義ですと、
"role" == "user"の都度、[ATTR] ... [/ATTR] がプロンプトに追記され、プロンプトが長くなります。
条件式{% if loop.last %}を追加して[ATTR]の出力を制御するようにchat_templateを書き換えて試してみました(太字箇所)。以下がその定義。
最後の1つだけに出力するように変更しても推論結果は(何十回か試したけれど)ブレませんでした。ブレないなら短い方が良いですよね。
この記事が気に入ったらサポートをしてみませんか?