漢字と英語が苦手な Irodori-TTS のために漢字と英語の読み仮名への変換を実装してみました。
GPUは Irodori-TTS で使うため、GPUオフロードはオフにしてあります。そのため生成には軽量なモデルであるgemma-4-E4B-it-UD-japanese-imatrixを採用しました。
https://huggingface.co/dahara1/gemma-4-E4B-it-UD-japanese-imatrix
yomigana.py
# License: Public Domain
import sys
import llama_cpp
import ctypes
llama_cpp.llama_backend_init(False) # Numa OFF
cparams = llama_cpp.llama_context_default_params()
cparams.n_ctx = 16384 # コンテキストサイズ
lparams = llama_cpp.llama_model_default_params()
lparams.n_gpu_layers = 0 # disable GPU
sparams = llama_cpp.llama_sampler_chain_default_params()
csmpl = llama_cpp.llama_sampler_chain_init(sparams)
if lparams.n_gpu_layers == 0:
cparams.op_offload = False
# https://huggingface.co/dahara1/gemma-4-E4B-it-UD-japanese-imatrix/blob/main/gemma-4-E4B-it-UD-Q8_K_XL.gguf
model = llama_cpp.llama_load_model_from_file("gemma-4-E4B-it-UD-Q8_K_XL.gguf".encode("utf8"), lparams)
if model is None:
raise RuntimeError("Failed to load the model")
ctx = llama_cpp.llama_new_context_with_model(model, cparams)
if not ctx:
raise RuntimeError(f"Failed to create the context")
n_ctx = llama_cpp.llama_n_ctx(ctx)
max_tokens = cparams.n_ctx
vocab = llama_cpp.llama_model_get_vocab(model)
def str_to_token(vocab, s):
pbytes = s.encode("utf-8")
tokens = (llama_cpp.llama_token * max_tokens)()
n_tokens = llama_cpp.llama_tokenize(vocab, pbytes, len(pbytes), tokens, len(tokens), ctypes.c_bool(False), ctypes.c_bool(True)) # PYTHONFAULTHANDLER=1 # flag: add_bos, special
return (tokens, n_tokens)
def str_to_token_arr(vocab, s):
tok, l = str_to_token(vocab, s)
return tok[:l]
def str_to_token_print(vocab, s):
toks = str_to_token_arr(vocab, s)
print("tokens of ", end="")
print(s, end=": ")
for tok in toks:
buf = (ctypes.c_char * 128)()
l = llama_cpp.llama_token_to_piece(vocab, tok, buf, len(buf), 0, True)
print(buf[:l], end="=")
print(tok, end=", ")
print("")
# https://www.aozora.gr.jp/cards/001065/files/18361_32671.html
text = """Basic English(基本英語)は如何なる英語であるか簡單に言ふならば、元來多くの點で最も簡易化の可能性に富む英語の持つ用辭上の特異性を利用して、
複雜なる思想をも、人間の生活意識に最も必要なる要素的な語彙のみで言ひ表はし得るやうに、大英語を合理的に壓縮整理して得た、
それ自體有機的な一つの纒つた組織を成す簡易化された小英語とも言ふべきものである。
而して、英語が世界に於いて通用範圍が最も大きいと言ふ點から、極めて短期間に習得しうる國際補助語とする意圖を以て、
英國ケムブリッヂの言語心理學者オグデン(C.K. Ogden, 1889- ――)氏によつて、多年苦心研究の結果案出されたものである。"""# + "解説"
import re
# 漢字の後ろと英数字記号の後ろで分割
prompt_arr = re.split(r'(?<=[一-龯々])(?=[^一-龯々])|(?<=[!-~])(?=[^!-~])', text)
#print(prompt_arr)
if len(prompt_arr) > 0 and re.search(r"[一-龯々!-~]$", prompt_arr[-1]):
# print(text_arr[-1])
prompt_arr.append("")
#print(prompt_arr)
sys_prompt = """<bos><|turn>system
全ての漢字と英数字の後ろにカタカナで読みがなを付けてプロンプトを返してください。<turn|>
<|turn>user
%s<turn|>
<|turn>model
"""%(text)
# prompt_arr = [x + "(" for x in prompt_arr[:-1]] + [prompt_arr[-1]]
# print(prompt_arr)
# tokens, n_tokens = str_to_token(vocab, prompt)
if len(prompt_arr) == 1: # ふりがな不要
print(text)
exit(0)
prompt_arr = [prompt_arr[0] + "("] + \
[")" + x + "(" for x in prompt_arr[1:-1]] + \
[")" + prompt_arr[-1]]
#print(prompt_arr)
token_arr = [str_to_token(vocab, sys_prompt + prompt_arr[0])] + \
[str_to_token(vocab, x) for x in prompt_arr[1:]]
# print(token_arr[0][0][:token_arr[0][1]])
#if n_tokens == 0:
# raise RuntimeError(f"Failed to tokenize")
#print(tokens[:n_tokens])
#print(llama_cpp.llama_token_bos(vocab))
# llama.cpp's default:
# penalties;dry;top_n_sigma;top_k;typ_p;top_p;min_p;xtc;temperature -> not true
# logits -> penalties -> dry -> top-n-sigma -> top-k -> typical -> top-p -> min-p -> xtc -> temp-ext -> dist
# https://github.com/ggml-org/llama.cpp/blob/cef1d23c5a33156c44a206c1f4bc146f4db729f9/common/common.h#L164
# 1. penalties (デフォルトでは無効)
#sampler_penalties = llama_cpp.llama_sampler_init_penalties(
# 64, # penalty_last_n, verified
# 1.0, # penalty_repeat, verified
# 0.0, # penalty_freq, verified
# 0.0, # penalty_present, verified
#)
#llama_cpp.llama_sampler_chain_add(csmpl, sampler_penalties)
# 2. dry (デフォルトでは無効)
#sampler_dry = llama_cpp.llama_sampler_init_dry(
# vocab, llama_cpp.llama_n_ctx_train(model),
# 0.0, # dry_multiplier, verified
# 1.75, # dry_base, verified
# 2, # dry_allowed_length, verified
# -1, # dry_penalty_last_n, verified
# None, # seq_breakers, ???
# 0 # num_breakers, ???
#)
#llama_cpp.llama_sampler_chain_add(csmpl, sampler_dry)
# 3. top_n_sigma (デフォルトでは無効)
#sampler_top_n_sigma = llama_cpp.llama_sampler_init_top_n_sigma(-1.0) # verified
#llama_cpp.llama_sampler_chain_add(csmpl, sampler_top_n_sigma)
# 4. top_k
sampler_top_k = llama_cpp.llama_sampler_init_top_k(40) # verified
llama_cpp.llama_sampler_chain_add(csmpl, sampler_top_k)
# 5. typ_p (デフォルトでは無効)
#sampler_typ_p = llama_cpp.llama_sampler_init_typical(1.0, 0) # verified
#llama_cpp.llama_sampler_chain_add(csmpl, sampler_typ_p)
# 6. top_p
sampler_top_p = llama_cpp.llama_sampler_init_top_p(0.95, 0) # verified
llama_cpp.llama_sampler_chain_add(csmpl, sampler_top_p)
# 7. min_p
sampler_min_p = llama_cpp.llama_sampler_init_min_p(0.05, 0) # verified
llama_cpp.llama_sampler_chain_add(csmpl, sampler_min_p)
# 8. xtc (デフォルトでは無効)
#sampler_xtc = llama_cpp.llama_sampler_init_xtc(
# 0.0, # p, verified
# 0.1, # t, verified
# 0,
# llama_cpp.LLAMA_DEFAULT_SEED
#)
#llama_cpp.llama_sampler_chain_add(csmpl, sampler_xtc)
# 9. temperature
#sampler_temp = llama_cpp.llama_sampler_init_temp(0.0) # default = 0.8 (verified)
#llama_cpp.llama_sampler_chain_add(csmpl, sampler_temp)
# Actually this is used by llama-server
sampler_temp_ext = llama_cpp.llama_sampler_init_temp_ext(0.8, 0.0, 1.0) # default = 0.8, 0.0, 1.0 (verified)
llama_cpp.llama_sampler_chain_add(csmpl, sampler_temp_ext)
#sampler_logit_bias = llama_cpp.llama_sampler_init_logit_bias(
# llama_cpp.llama_vocab_n_tokens(vocab),
# logit_bias_len,
# logit_bias)
#llama_cpp.llama_sampler_chain_add(csmpl, sampler_logit_bias)
sampler_dist = llama_cpp.llama_sampler_init_dist(llama_cpp.LLAMA_DEFAULT_SEED)
llama_cpp.llama_sampler_chain_add(csmpl, sampler_dist);
#gsmpl = llama_cpp.llama_sampler_chain_init(sparams) # 微妙
#llama_cpp.llama_sampler_chain_add(gsmpl, llama_cpp.llama_sampler_init_greedy())
# ")"、")、", ...などの全角閉じ括弧パターンを作る
ep_token_strs = []
ep_tokens = []
n_vocab = llama_cpp.llama_vocab_n_tokens(vocab)
for token_id in range(n_vocab):
piece = llama_cpp.llama_vocab_get_text(vocab, token_id)
# bytes -> str
if piece is not None:
piece = piece.decode("utf-8", errors="replace")
if (")" in piece):
ep_token_strs.append(piece)
ep_tokens.append(token_id)
# print(ep_token_strs)
# print(ep_tokens)
import re
skip_batch = False
i = 0
for idx,token in enumerate(token_arr):
batch = llama_cpp.llama_batch_get_one(*token)
# for tok in token[0][:token[1]]:
# llama_cpp.llama_sampler_accept(csmpl, tok)
# sys.stdout.write("[" + prompt_arr[idx] + "]")
sys.stdout.write(prompt_arr[idx])
sys.stdout.flush()
while i < cparams.n_ctx:
if llama_cpp.llama_decode(ctx, batch) != 0:
print("batch error")
break
i += batch.n_tokens
# new_token_id = ctypes.c_int(llama_cpp.llama_sampler_sample(gsmpl, ctx, -1));
new_token_id = ctypes.c_int(llama_cpp.llama_sampler_sample(csmpl, ctx, -1));
# print(new_token_id)
# print(ep_tokens)
if new_token_id.value in ep_tokens:
break
if llama_cpp.llama_vocab_is_eog(vocab, new_token_id):
# print(new_token_id)
print("")
exit(0)
buf = (ctypes.c_char * 128)()
l = llama_cpp.llama_token_to_piece(vocab, new_token_id, buf, len(buf), 0, True)
sys.stdout.buffer.write(buf[:l])
sys.stdout.buffer.flush()
# i += 1
if not skip_batch:
batch = llama_cpp.llama_batch_get_one(ctypes.byref(new_token_id), 1)
else:
skip_batch = False
llama_cpp.llama_free(ctx)
実行結果は以下です。
元テキスト
Basic English(基本英語)は如何なる英語であるか簡單に言ふならば、元來多くの點で最も簡易化の可能性に富む英語の持つ用辭上の特異性を利用して、
複雜なる思想をも、人間の生活意識に最も必要なる要素的な語彙のみで言ひ表はし得るやうに、大英語を合理的に壓縮整理して得た、
それ自體有機的な一つの纒つた組織を成す簡易化された小英語とも言ふべきものである。
而して、英語が世界に於いて通用範圍が最も大きいと言ふ點から、極めて短期間に習得しうる國際補助語とする意圖を以て、
英國ケムブリッヂの言語心理學者オグデン(C.K. Ogden, 1889- ――)氏によつて、多年苦心研究の結果案出されたものである。
変換後
Basic(ベーシック) English(イングリッシュ)(基本英語(きほんえいご))は如何(いか)なる英語(えいご)であるか簡單(かんたん)に言(い)ふならば、元來多(もと)くの點(てん)で最(もっと)も簡易化(かんいか)の可能性(かのうせい)に富(と)む英語(えいご)の持(も)つ用辭上(ようじじょう)の特異性(とくいせい)を利用(りよう)して、
複雜(ふくざつ)なる思想(しそう)をも、人間(にんげん)の生活意識(せいかついしき)に最(もっと)も必要(ひつよう)なる要素的(ようそてき)な語彙(ごい)のみで言(い)ひ表(あらわ)はし得(え)るやうに、大英語(だいえいご)を合理的(ごうりてき)に壓縮整理(きゃくそうせいり)して得(え)た、
それ自體有機的(じたいゆうきてき)な一(ひと)つの纒(つな)つた組織(そしき)を成(な)す簡易化(かんいか)された小英語(しょうえいご)とも言(い)ふべきものである。
而(しか)して、英語(えいご)が世界(せかい)に於(お)いて通用範圍(つうようはんい)が最(もっと)も大(おお)きいと言(い)ふ點(てん)から、極(きわ)めて短期間(たんきかん)に習得(しゅうとく)しうる國際補助語(こくさいほじょご)とする意圖(いと)を以(もっ)て、
英國(えいこく)ケムブリッヂの言語心理學者(げんごしんりがくしゃ)オグデン(C.K.(シー・ケイ) Ogden,(オグデン) 1889-(せんはっぴゃくはちじゅうきゅう-) ――)氏(し)によつて、多年苦心研究(たねんくしんけんきゅう)の結果案出(けっかあんしゅつ)されたものである。
間違いもまだありますね…。どうやらLLMモデル由来のようです。
元來多く→がんらいおおく
壓縮整理→あっしゅくせいり
纒つた→まとった
2026年5月30日追記。
gemma-4-26B-A4B-it-UD-Q8_K_XLで変換をテストしてみました。
https://huggingface.co/unsloth/gemma-4-26B-A4B-it-GGUF/resolve/main/gemma-4-26B-A4B-it-UD-Q8_K_XL.gguf
Basic(ベーシック) English(イングリッシュ)(基本英語(きほんえいご))は如何(いかなる)なる英語(えいご)であるか簡單(かんたん)に言(い)ふならば、元來多(もともと)くの點(てん)で最(もともと)も簡易化(かんいか)の可能性(かのうせい)に富(と)む英語(えいご)の持(も)つ用辭上(ようじじょう)の特異性(とくいせい)を利用(りよう)して、
複雜(ふくざつ)なる思想(しそう)をも、人間(にんげん)の生活意識(せいかついしき)に最(もっと)も必要(ひつよう)なる要素的(ようそてき)な語彙(ごい)のみで言(い)ひ表(あらわ)はし得(え)るやうに、大英語(たいえいご)を合理的(ごうりてき)に壓縮整理(あっしゅくせいり)して得(え)た、
それ自體有機的(それじたいゆうきてき)な一(ひと)つの纒(まと)つた組織(そしき)を成(な)す簡易化(かんいか)された小英語(しょうえいご)とも言(い)ふべきものである。
而(しか)して、英語(えいご)が世界(せかい)に於(お)いて通用範圍(つうようはんい)が最(もっと)も大(おお)きいと言(い)ふ點(てん)から、極(きわ)めて短期間(たんきかん)に習得(しゅうとく)しうる國際補助語(こくさいほじょご)とする意圖(いと)を以(も)て、
英國(えいこく)ケムブリッヂの言語心理學者(げんごしんりがくしゃ)オグデン(C.K.(シー・ケー) Ogden,(オグデン) 1889-(1889) ――)氏(し)によつて、多年苦心研究(たねんくしんけんきゅう)の結果案出(けっかあんしゅつ)されたものである
「元來多く」はまだ間違ってますが他の2つは治ってますね。