大模型「会写」和「写得像你」是两回事。通用模型能讲故事,但很难稳定复现某一本书的文风、句式、叙事节奏和人物口吻。
这篇文章用 Qwen-1.8B-Chat + LLaMA Factory + LoRA,在消费级 GPU 上微调一个小模型,让它学会你指定小说的写作风格,实现同款风格续写。
我们要让模型学会什么
小说风格不是单一维度,训练数据要覆盖这些层面:
| 维度 |
说明 |
数据体现 |
| 文风 |
用词习惯、修辞密度、叙述视角 |
原文段落本身 |
| 句式 |
长短句交替、断句节奏、对话与描写比例 |
连续上下文 |
| 叙事节奏 |
铺垫—冲突—转折的信息密度 |
多轮续写样本 |
| 人物口吻 |
不同角色说话方式、语气词、口头禅 |
带角色标注的对话 |
LoRA 只更新少量参数,适合在 1.8B 小模型上注入风格,而不是重新学一遍中文。
方案概览
1 2 3 4 5 6 7
| 小说原文 (.txt) ↓ 切分 + 构造指令样本 训练集 (alpaca/sharegpt json) ↓ LLaMA Factory LoRA SFT Qwen-1.8B-Chat + LoRA 适配器 ↓ 加载推理 同款风格续写 / 对话写作
|
为什么选 Qwen-1.8B-Chat?
- 体量小,6GB 显存即可 LoRA 微调
- 中文能力强,适合小说语料
- 与 LLaMA Factory 的
qwen 模板原生兼容
为什么选 LoRA?
- 只训练低秩矩阵,显存占用低
- 适配器文件通常几十到几百 MB,方便切换不同「文风」
- 不破坏基座模型的通用能力
环境准备
1 2 3 4 5 6 7
| git clone --depth 1 https://github.com/hiyouga/LLaMA-Factory.git cd LLaMA-Factory pip install -e ".[torch,metrics]"
pip install bitsandbytes
|
硬件建议:
| 配置 |
全参数 |
LoRA |
QLoRA (4bit) |
| 6GB 显存 |
❌ |
✅ |
✅ 推荐 |
| 12GB+ 显存 |
❌ |
✅ 舒适 |
✅ |
第一步:把小说变成训练数据
核心思路:不要只丢原文,要构造「指令 → 风格化输出」的监督样本。
1. 清洗与切分原文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import json import re from pathlib import Path
def load_novel(path: str) -> str: text = Path(path).read_text(encoding="utf-8") text = re.sub(r"\s+", "\n", text.strip()) return text
def split_chunks(text: str, chunk_size: int = 800, overlap: int = 120): """按字数切分,保留上下文重叠,利于学习叙事节奏。""" chunks = [] start = 0 while start < len(text): end = min(start + chunk_size, len(text)) chunks.append(text[start:end]) if end == len(text): break start = end - overlap return chunks
|
2. 构造三类训练样本
A. 文风续写(主样本)
1 2 3 4 5 6
| { "instruction": "请用以下小说的文风续写故事,保持叙述视角、句式节奏和用词习惯一致。", "input": "前情:\n{context}", "output": "{continuation}", "system": "你是一位擅长模仿指定小说文风的写作助手,注重叙事节奏与人物口吻。" }
|
B. 人物口吻对话
1 2 3 4 5 6
| { "instruction": "请以【{character}】的口吻回应对话,保持该角色一贯的说话方式。", "input": "场景:{scene}\n对方说:{line}", "output": "{reply}", "system": "你正在扮演小说中的角色,回答必须符合人物性格和语言习惯。" }
|
C. 风格改写(强化句式)
1 2 3 4 5 6
| { "instruction": "将下面这段文字改写成目标小说的文风,不改变情节,只调整措辞、节奏和修辞。", "input": "{neutral_text}", "output": "{styled_text}", "system": "你是文风迁移助手,输出应贴近目标小说的叙述风格。" }
|
3. 生成数据集脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| def build_style_samples(chunks: list[str]) -> list[dict]: samples = [] for i in range(len(chunks) - 1): context = chunks[i] continuation = chunks[i + 1][:500] samples.append({ "instruction": "请用以下小说的文风续写故事,保持叙述视角、句式节奏和用词习惯一致。", "input": f"前情:\n{context}", "output": continuation, "system": "你是一位擅长模仿指定小说文风的写作助手,注重叙事节奏与人物口吻。" }) return samples
chunks = split_chunks(load_novel("novel.txt")) dataset = build_style_samples(chunks)
Path("data/novel_style.json").write_text( json.dumps(dataset, ensure_ascii=False, indent=2), encoding="utf-8" ) print(f"生成 {len(dataset)} 条样本")
|
数据量建议:
- 最少:500 条高质量样本(约 3~5 万字小说)
- 推荐:2000~5000 条(多章节、多场景)
- 样本要覆盖:叙述段、对话段、高潮段、日常段
第二步:注册数据集
在 LLaMA-Factory/data/dataset_info.json 中添加:
1 2 3 4 5 6 7 8 9
| "novel_style": { "file_name": "novel_style.json", "columns": { "prompt": "instruction", "query": "input", "response": "output", "system": "system" } }
|
第三步:LoRA 微调配置
新建 examples/train_lora/qwen1_8b_novel_lora.yaml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| model_name_or_path: Qwen/Qwen-1_8B-Chat trust_remote_code: true
stage: sft do_train: true finetuning_type: lora lora_rank: 16 lora_alpha: 32 lora_dropout: 0.05 lora_target: all
dataset: novel_style template: qwen cutoff_len: 2048 max_samples: 10000 overwrite_cache: true preprocessing_num_workers: 4
output_dir: saves/qwen1_8b_novel_lora logging_steps: 10 save_steps: 200 plot_loss: true overwrite_output_dir: true
per_device_train_batch_size: 2 gradient_accumulation_steps: 8 learning_rate: 2.0e-4 num_train_epochs: 3.0 lr_scheduler_type: cosine warmup_ratio: 0.05 bf16: true ddp_timeout: 180000000
val_size: 0.05 per_device_eval_batch_size: 1 eval_strategy: steps eval_steps: 200
|
参数说明:
| 参数 |
建议值 |
作用 |
lora_rank |
8~32 |
越大表达能力越强,显存越高 |
cutoff_len |
2048 |
覆盖更长上下文,利于学叙事节奏 |
learning_rate |
1e-4 ~ 3e-4 |
风格学习常用偏高学习率 |
num_train_epochs |
2~5 |
样本少可多轮,注意过拟合 |
显存不足时,改用 QLoRA:
1 2
| finetuning_type: lora quantization_bit: 4
|
第四步:启动训练
1 2 3 4 5 6 7
| cd LLaMA-Factory
CUDA_VISIBLE_DEVICES=0 llamafactory-cli train examples/train_lora/qwen1_8b_novel_lora.yaml
llamafactory-cli webui
|
训练时关注:
- loss 稳定下降:通常在 0.5~1.5 区间收敛
- 验证集 loss 不再下降时停止:避免只会「背诵」训练集
- 抽查生成:用同一 prompt 对比第 1 epoch 和第 3 epoch 输出
第五步:推理与同款风格写作
命令行推理
1 2 3 4 5
| CUDA_VISIBLE_DEVICES=0 llamafactory-cli chat \ --model_name_or_path Qwen/Qwen-1_8B-Chat \ --adapter_name_or_path saves/qwen1_8b_novel_lora \ --template qwen \ --finetuning_type lora
|
Python 加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| from transformers import AutoModelForCausalLM, AutoTokenizer from peft import PeftModel
base = "Qwen/Qwen-1_8B-Chat" lora = "saves/qwen1_8b_novel_lora"
tokenizer = AutoTokenizer.from_pretrained(base, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( base, device_map="auto", trust_remote_code=True ) model = PeftModel.from_pretrained(model, lora) model.eval()
prompt = """请用训练小说的文风续写以下内容,保持叙事节奏和人物口吻:
前情: 雨停之后,巷口的青石板还泛着潮气。她站在门廊下,没有回头。"""
messages = [ {"role": "system", "content": "你是一位擅长模仿指定小说文风的写作助手。"}, {"role": "user", "content": prompt}, ] text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) inputs = tokenizer([text], return_tensors="pt").to(model.device)
outputs = model.generate(**inputs, max_new_tokens=512, temperature=0.8, top_p=0.9) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True))
|
导出合并模型(可选)
1 2 3 4 5 6 7 8
| CUDA_VISIBLE_DEVICES=0 llamafactory-cli export \ --model_name_or_path Qwen/Qwen-1_8B-Chat \ --adapter_name_or_path saves/qwen1_8b_novel_lora \ --template qwen \ --finetuning_type lora \ --export_dir exports/qwen1_8b_novel_merged \ --export_size 2 \ --export_legacy_format false
|
效果调优:四个实战技巧
1. 用「多粒度」样本,不要只续写
只喂「上文 → 下文」容易学会情节复述,学不会口吻。建议三类样本比例为:
- 文风续写 60%
- 角色对话 25%
- 风格改写 15%
2. 控制续写目标长度
output 建议 200~600 字。太长会稀释风格信号,太短学不到叙事节奏。
3. 推理温度按任务切换
| 任务 |
temperature |
top_p |
| 仿写/续写 |
0.7~0.9 |
0.85~0.95 |
| 角色对话 |
0.8~1.0 |
0.9 |
| 风格改写 |
0.5~0.7 |
0.8 |
4. 多适配器管理不同文风
1 2 3 4
| saves/ ├── qwen1_8b_wuxia_lora/ # 武侠风 ├── qwen1_8b_romance_lora/ # 言情风 └── qwen1_8b_scifi_lora/ # 科幻风
|
推理时切换 adapter_name_or_path 即可,基座模型不用重复加载。
常见问题
Q:生成内容像训练集原文拼凑?
过拟合。减少 epoch、增大 lora_dropout、扩充样本多样性。
Q:风格像了,但逻辑不通?
1.8B 容量有限,复杂长篇规划能力弱。建议用于段落级续写,而非整章大纲生成。
Q:对话口吻不像某个角色?
增加带角色名的对话样本,并在 instruction 中显式写出角色身份。
Q:显存不够?
启用 quantization_bit: 4,或将 per_device_train_batch_size 设为 1、gradient_accumulation_steps 调大。
总结
用 Qwen-1.8B-Chat + LLaMA Factory 做 LoRA 微调,是在有限算力下实现「同款文风写作」的务实路线:
- 把小说切成风格监督样本,而不只是原文堆砌
- 覆盖文风、句式、节奏、口吻四个维度
- LoRA 低成本注入风格,基座能力保留
- 推理时通过 prompt + 温度控制输出质量
小模型不会替代顶尖大模型的全局构思能力,但在风格化续写、角色对话、章节仿写这类任务上,微调后的 1.8B 往往比未微调的 7B 更「像你」。
如果你要针对某本具体小说做数据集,核心就一句话:让模型看到的每一条样本,都在回答「如何用这种风格写」。