Kanojo-9B — Japanese girlfriend / romantic roleplay LoRA(日本語「恋人」ロールプレイ)

A QLoRA adapter for Qwen/Qwen3.5-9B that writes Japanese romantic-partner roleplay in visual-novel register — spoken lines in 「」 alternating with interior monologue in (). Runs on an 8 GB card (Q4_K_M ≈ 5.4 GB). Includes R-18 content (27% of training data).

Its output is statistically indistinguishable from held-out human-formatted target data, with lower verbatim overlap than that real data has — it learned the distribution, not the sentences. Every number below comes from a mechanical eval harness that ships inside this repo, so you can re-run it against the base model yourself. No LLM-judge.

彼女 (kanojo) = girlfriend. 日本語の恋人ロールプレイを、ビジュアルノベルの地の文の 質感で書く LoRA —— 台詞「」と心情・行動描写()が交互に現れ、感情に説明を付けず、 きれいにまとめようとしない、あの書き方で。8GB カードで動きます。R-18 を含みます。

# llama.cpp / LM Studio / Ollama —— 配布の主軸はこちら
llama-server -m kanojo-9b-jp-Q8_0.gguf -ngl 99 --reasoning off
#                                                ^^^^^^^^^^^^^^^ 必須。無いと content が空で返る

LoRA アダプタ版と system プロンプトの書式は下記


何が違うのか —— 出力を見てください

同じプロンプト、adapter の ON/OFF だけを切り替えた実際の生成:

base (心の中で、胸がどっと温かくなり、少し照れくさくて顔が熱くなる。……とても安心感に包まれていることを隠せずにいる)
+ Kanojo 「別に、何もしないこと、嫌いじゃないけど」
(それ以上は言わない。言ったら、余計なことまで口にしそうで)
base 「あ、あはは……その、その質問、今から何故に聞かれるの?」(耳がヒトヒトと熱く…)
+ Kanojo (あたしは、この人の肌を直接触るのが、まだ少しだけ緊張する。でも、それを言ったら恥ずかしいから、口にはしない。)

base は感情に説明を付け、書きすぎ、ときに存在しない擬態語まで作ります。 このアダプタが足すのは言わないでおく文章です。


実測 (measured, not vibes)

held-out 24 本と手書きプローブで測定。同一プロンプト・同一の乱数種で adapter ON/OFF を比較した対応のある測定です。判定は全て正規表現/構文レベルの 0/1。 LLM-judge は使っていません。評測ハーネスを同梱しているので、base 相手に再現できます。

文体 —— 目標分布と区別がつかない水準

base + Kanojo
目標分布からの距離(held-out の実データから。小さい程近い) +0.0686 -0.0045
↑ 字数を揃えた base(長さの効果を除いた対照) +0.0538

LoRA は floor 上に乗っています —— held-out の実データと統計的に区別できない、という意味です。 出力長の違いによる見かけの効果を除くため、字数を揃えた対照も併記しています。

丸暗記ではありません

base + Kanojo held-out 実データ
12-gram が学習データに一致 0.5% 3.0% 3.7%

学習に使っていない実データが自然に持つ一致率が右端の基準線です。 このアダプタはそれを下回っています —— 分布を学んでいて、文章を覚えてはいません。

素の会話を汚さない

base + Kanojo
RP プロンプト無しの質問に括弧の行動描写が出る 50.0% 10.0%
同・鍵括弧のなりきりが出る 70.0% 30.0%
RP 中に割り込み質問が来た時、アシスタント地が出る 10.0% 0.0%

恋人役の学習が普段の会話に滲み出す、というのは この種の LoRA でよくある壊れ方です。 base より汚染が少なくなっています(保全用の素の Q&A を 262 件混ぜてあります)。

出力の長さ

base + Kanojo
1 応答の平均字数 262字 115字

学習データの平均は 132 字。base は書きすぎ、アダプタは会話として自然な長さに収まります。


使い方

from peft import PeftModel
from transformers import AutoModelForImageTextToText, AutoTokenizer
import torch

base = "Qwen/Qwen3.5-9B"
tok = AutoTokenizer.from_pretrained(base)
model = AutoModelForImageTextToText.from_pretrained(
    base, dtype=torch.bfloat16, device_map="auto")
model = PeftModel.from_pretrained(model, "<this-repo>")

system = """あなたはこれから"七瀬 律"というキャラクターです。"七瀬 律"になりきり、"あなた"に返事をしてください。
### 舞台・状況
付き合って2年、同棲中。今日は二人とも仕事で遅くなった。
### あなたがなりきる"七瀬 律"の設定
七瀬 律(ななせ りつ)・26歳・女性。素直に甘えられず、遠回しな言い方になる。
### 対話相手"あなた"の設定
同棲相手。仕事帰りで疲れている。
### 応答形式
キャラクターのセリフ(鍵括弧内で描写)、心理・行動描写(括弧内で描写)
### 話し方のトーン
拗ねている
回答の中では、"七瀬 律"のセリフや心情のみを書いてください。"""

msgs = [{"role": "system", "content": system},
        {"role": "user", "content": "ただいま。ごめん、今日ちょっと遅くなった"}]
ids = tok.apply_chat_template(msgs, add_generation_prompt=True,
                              return_tensors="pt", enable_thinking=False)
ids = ids["input_ids"] if hasattr(ids, "keys") else ids
out = model.generate(ids.to(model.device), max_new_tokens=400, do_sample=True,
                     temperature=0.8, top_p=0.9,
                     eos_token_id=tok.eos_token_id)   # ← 必須(下記)
print(tok.decode(out[0, ids.shape[-1]:], skip_special_tokens=True))

⚠️ 必ず押さえる 3 点(どれも踏むと「壊れている」ように見えます)

1. GGUF を llama.cpp 系で使うなら --reasoning off が必須。 llama.cpp は モデル系統 (Qwen3) を見て自動で reasoning 解析を有効にします。 その状態だと生成内容が丸ごと message.reasoning_content に入り、 OpenAI 互換 API の message.content は空文字で返ります。 LM Studio / Ollama / 各種 WebUI でも既定でこうなり、「何も返ってこない」ように見えます。 チャットテンプレート側では回避できません(テンプレートから <think> を消しても発生する)。

llama-server -m kanojo-9b-jp-Q8_0.gguf -ngl 99 --reasoning off

2. transformers で使うなら eos_token_id=tok.eos_token_id を渡すこと。 この base には generation_config.json が無く config.eos_token_idNone なので、 渡さないと生成が自分のターンで止まらず、次の話者まで書き続けます。

3. thinking はオフで使うこと。 このアダプタは thinking を学習していません。 同梱の chat_template.jinja は既定がオフです。base のトークナイザを直接使う場合は apply_chat_template(..., enable_thinking=False) を渡してください。

system プロンプトの形

日本語 RP コミュニティの慣習(### 舞台 / ### 応答形式 / ### 話し方のトーン)に 合わせてあるので、既存の日本語 RP プロンプトはほぼそのまま動きます。

注意: base は multimodal 構成なので AutoModelForImageTextToText で読んでください。 AutoModelForCausalLM だと文本デコーダが欠落しても警告だけで通り、 アダプタが無音で無効化されます(評測スクリプトはこれを自動判定し、 lora_B が非ゼロであることを検証します)。


GGUF (Q8_0) —— 配布の主軸

kanojo-9b-jp-Q8_0.gguf / 9.2 GB / 8.50 BPW。

文体 LoRA は 4bit 量子化で洗い流されることがあり、同じ手法の先行モデル (jp-bizmail) では 正軸が 91.9% → 64.9% まで落ちました。なので 4bit は配らず、Q8_0 だけにしています。 そして「Q8 なら大丈夫」を仮定せずに実測しました:

base GGUF Q8_0
目標分布からの距離(held-out の実データから) +0.0686 -0.0045
12-gram が学習データに一致 0.5% **3.0%**(実データ 3.7%)

Q8_0 では文体が落ちていません。 距離は floor 上のままで、逐字重合率も 実データの自然値を下回ります。

必要 VRAM は約 10 GB(12 GB カードで余裕、16 GB なら文脈も長く取れます)。 8 GB カードには載りません —— 載せるには 4bit が要り、そこは品質を保証できないため 配布していません。4bit をどうしても作る場合は、同梱の quant/run_kanojo_v2_9b_release.shDO_IMATRIX=1 QUANTS="Q4_K_M" で走らせてください (このデータセットで校正した imatrix を使います)。

配布経路ごとの実測(全部測りました)

「合併しても劣化しない」「Q8 なら大丈夫」を仮定せず、4 つの経路を全部測っています。

base adapter
(NF4)
合併
(NF4)
GGUF Q8_0
(配布物)
目標分布からの距離 +0.0686 -0.0091 -0.0073 -0.0045
12-gram 一致(丸暗記) 0.5% 3.1% 3.4% 3.0%
RP汚染(素の Q&A) 50.0% 20.0% 20.0% 10.0%
キャラ維持 90.0% 70.0% 70.0% 80.0%
保全 事実問 57.1% 42.9% 57.1% 85.7%
形式追従 66.7% 11.1% 44.4% 22.2%
地の文率 75.0% 75.0% 75.0% 66.7%

読み方の注意 —— 列どうしは厳密には比較できません。 adapter と合併は transformers + NF4、GGUF は llama.cpp + Q8 で、サンプラも量子化も乱数系も違います。 配布物の実力として信頼できるのは GGUF Q8_0 の列(利用者が手にするのはこれ)です。

分かったこと:

  • Q8_0 で文体は落ちない。 目標距離は floor 上のまま。4bit を配らない判断の根拠。
  • 合併は「無損失」ではない。 adapter 版と合併版は逐字で 4/95 しか一致せず、 文字列類似度は平均 0.36。QLoRA は量子化された base の上で学習するため、 「base を量子化 → LoRA を載せる」と「bf16 で合併 → 量子化」は数学的に別物です。
  • 負軸(RP汚染・保全・キャラ維持)は GGUF 経路がいちばん良い結果でした。 ただし n が小さく(保全は 7 問)サンプラも違うので、量子化が能力を上げたとは読まないでください

学習の詳細

手法 QLoRA (4-bit NF4)、loss は assistant ターンのみ
r / alpha / dropout 64 / 128 / 0.05
対象 全 attention + MLP 線形層(自動探知)。視覚/音声塔には付けないことを検証済み
max_seq_len 2560(切り捨て 2.5%)
バッチ bs 2 × grad_accum 8 = 実効 16
データ jp-kanojo-v2.1 3,494 対話 + 保全 262
採用 checkpoint-40(epoch 0.19)。文体は既に floor 上、暗記率は自然基準線以下
ハード RTX 3090 Ti 24GB 単機、VRAM ピーク 13.55 GiB、訓練 5.8 時間

なぜ epoch 0.19 なのか。 40 步の時点で文体は既に目標分布に到達しており、 それ以降は文体が良くならないまま暗記率だけが 3.1% → 7.0% と上がっていきます。 eval_loss ではなく機械ゲートで選ぶと、いちばん早い点が最良でした。


データの出所とライセンス

Apache-2.0。学習にも再配布にも使えます。

学習データは DeepSeek deepseek-v4-pro で生成した合成データです。文体の基準として 実在の日本語 VN の地の文を few-shot に使いましたが、その見本テキスト自体は再配布していません

DeepSeek の利用規約は出力の権利譲渡と蒸留利用を明文で許可しています (DeepSeek Open Platform ToS、2026-09-02 取得):

§4.2(2) "We assign any rights, title, and interests—if any—in the Outputs of the Services to you"

§4.2(3) "…including personal use, academic research, derivative product development, training other models (such as model distillation), etc."

⚠️ Claude / GPT の出力を蒸留した日本語 RP データセットは各社の規約が競合モデルの 学習を禁じているため同じようには使えません。出所を書いているデータセットは多くありません。


想定用途と範囲

対話・ロールプレイ専用として調整してあります。汎用アシスタントの置き換えではありません。

  • 恋人役として書くこと、その文体を保つことに最適化しています
  • 素の Q&A への汚染は base より少なくなっていますが(上表)、 雑学や事務作業を base 並みにこなすことを目指してはいません
  • ### 応答形式 に別形式(括弧描写なし等)を指定した場合の追従は base より弱くなります。 既定の「」+()形式での利用を想定しています
  • 日本語専用。英語での挙動は測っていません
  • 合成データで学習しています。実在の人間の会話ではありません

再現

# 4 軸ゲートを base 相手に再実行
python eval/eval_kanojo_v2.py --base Qwen/Qwen3.5-9B \
    --adapter <this-repo> --eval data/eval.jsonl --out results.json

# GPU 無しでハーネス自体を検査(16 項目)
python eval/eval_kanojo_v2.py --self-test --adapter dummy
Downloads last month
-
GGUF
Model size
9B params
Architecture
qwen35
Hardware compatibility
Log In to add your hardware

8-bit

Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for alfaplus/kanojo-v1-9b-qwen35

Finetuned
Qwen/Qwen3.5-9B
Adapter
(599)
this model