Files
prose-rewriter-4b-v2.2/README.md
ModelHub XC 766376a6bd 初始化项目,由ModelHub XC社区提供模型
Model: chartreuse-verte/prose-rewriter-4b-v2.2
Source: Original Platform
2026-10-22 14:50:23 +08:00

288 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
license: agpl-3.0
base_model: Qwen/Qwen3-4B-Base
pipeline_tag: text-generation
language:
- en
tags:
- prose
- rewriting
- style-transfer
- creative-writing
- deslop
- qwen3
library_name: transformers
---
# prose-rewriter-4b-v2.2
A paragraph-level **prose rewriter**: it takes prose written by a large model and
re-renders it to be more human, preserving the semantics it was given.
`Qwen/Qwen3-4B-Base` with a rank-32 LoRA merged in at strength 1.20.
**A third less repetition.** Every training target that repeats itself -- the
same sentence twice, a phrase said again, three sentences opening on the same
words, `happy, happy` -- is now rejected, so the pool only ever shows the model
the repetition going away. Rewrites that repeat themselves fall from 13.5% to
8.7%.
**Less unsupported content, less slop vocabulary**, at the same strength as
v2.1. See [Evaluation](#evaluation).
## Variants
| Path | Format | Use with |
|---|---|---|
| `/` | safetensors bf16, `qwen3` arch | transformers |
| `GGUF/prose-rewriter-4b-v2.2-Q8_0.gguf` | GGUF Q8_0, 4.69 GB | llama.cpp / llama-cpp-python |
| `GGUF/prose-rewriter-4b-v2.2-Q4_K_M.gguf` | GGUF Q4_K_M, 2.72 GB | llama.cpp / llama-cpp-python |
The quants carry the chat template and stop on `<|im_end|>`, and the adapted
output head is kept separate from the token embeddings, stored at Q8_0.
## Prompt format
```
<|im_start|>source
{paragraph}<|im_end|>
<|im_start|>rewrite
```
The chat template in this repo builds exactly that string, byte for byte, from
one message:
```python
messages = [{"role": "source", "content": paragraph}]
tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
```
It is not a chat model. Every message is rendered as the source paragraph, so a
runtime that probes the template with a `user` message still gets a valid
prompt. Send one message per call.
## Serving recipe
Sampled at `temperature=0.9, top_p=0.9`. However, temperature 0.9 hallucinates harder
because this release edits harder than v2. It's recommended you use 0.8-0.85 instead.
```python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
repo = "chartreuse-verte/prose-rewriter-4b-v2.2"
tok = AutoTokenizer.from_pretrained(repo)
model = AutoModelForCausalLM.from_pretrained(repo, dtype=torch.bfloat16, device_map="cuda").eval()
def rewrite(paragraph):
text = tok.apply_chat_template(
[{"role": "source", "content": paragraph}],
tokenize=False, add_generation_prompt=True,
)
ids = tok(text, return_tensors="pt", add_special_tokens=False).input_ids.to(model.device)
out = model.generate(ids, max_new_tokens=512, do_sample=True, temperature=0.9, top_p=0.9)
return tok.decode(out[0, ids.shape[1]:], skip_special_tokens=True).strip()
```
Same thing under llama.cpp. The role is `source`, which no chat API models, so
build the string yourself; `<|im_end|>` stops it:
```bash
llama-cli -m GGUF/prose-rewriter-4b-v2.2-Q8_0.gguf -no-cnv -n 512 --temp 0.9 --top-p 0.9 \
-p '<|im_start|>source
{paragraph}<|im_end|>
<|im_start|>rewrite
'
```
## Input length
The training pool's median input is 37 words and 80% of it is under 70, so serve
it on anything from a full sentence up.
The practical floor is about **15 words**. Below it the failure mode is padding,
cutting and fabrication rather than gibberish. Below 80 bytes, pass the text
through unchanged.
## Evaluation
Both releases measured **as they ship**, baked at strength 1.20, on 365 held-out
paragraphs of LLM-written prose that neither model saw in training, at
`temperature=0.9, top_p=0.95`, three swipes each. 1,095 generations an arm.
Pronoun invention numbers for this release, on 400 single-gender roleplay
paragraphs:
| | v2.1 | v2.2 |
|---|---|---|
| a gendered pronoun the input never licensed | 0.8% | 0.8% |
| ... belonging to the other gender | 0.8% | 0.8% |
| a proper noun the input does not have | 0.3% | 0.3% |
Paired over the 400 inputs, none of these separates.
Repetition on the 365 paragraphs -- a rewrite that repeats a sentence, a phrase,
or a sentence opening:
| | input | v2.1 | v2.2 |
|---|---|---|---|
| any repetition | 2.2% | 13.5% | **8.7%** |
| ... a whole sentence | | 0.4% | 0.3% |
| ... a phrase | | 7.3% | **4.6%** |
| ... a sentence opening (anaphora) | | 4.5% | **2.6%** |
Paired over the 365 inputs, any repetition falls (t = -3.6), repeated phrases
fall (t = -2.8) and anaphora falls (t = -2.5).
Structural, on the same paragraphs:
| | v2.1 | v2.2 |
|---|---|---|
| words changed | 44.0% | 43.7% |
| passed through unchanged | 3.1% | 2.5% |
| near-verbatim outputs | 1.4% | 0.9% |
| below the training edit floor | 35.4% | 35.9% |
| sentence count moved | 75.5% | 79.2% |
| sentence-length variety vs input | +0.115 | +0.116 |
| words kept from the input | 0.616 | 0.620 |
| length preserved | 0.851 | 0.857 |
| truncated below 0.75x | 23.7% | 21.4% |
| repeated 3-grams | 0.005 | 0.004 |
Paired over the 365 inputs, only sentence count moved separates (t = +2.2). Over
the 303 inputs of 51-90 words, no column does.
Unsupported content -- a sentence in the rewrite the input does not entail,
scored by NLI with the input alone as the premise:
| | v2.1 | v2.2 |
|---|---|---|
| unsupported sentences, mean per rewrite | 12.7% | **11.1%** |
| unsupported sentences, all rewrites | 11.5% | **10.2%** |
| coverage of the input (reverse entailment) | 0.487 | 0.484 |
Paired over the 365 inputs, the unsupported rate falls (t = -2.3); coverage
does not move.
The register numbers on the same paragraphs, against the input:
| | input | v2.1 | v2.2 | human corpus |
|---|---|---|---|---|
| banned constructions /1k | 7.52 | 2.51 | 2.50 | 0.00 |
| slop lexicon density | 0.081 | 0.048 | **0.044** | 0.019 |
| purple score | 0.498 | 0.314 | 0.316 | -- |
Paired over all 365 inputs, lexicon density falls (t = -2.6). Banned
constructions and purple do not separate.
Dialogue: the number of spoken lines changes on 1.6% of rewrites against v2.1's
0.8% (t = +1.7, does not separate), and of the rewrites whose input carried
quoted speech, 1.0% lost the quotes entirely -- the same for both.
Markup round-trip on 365 tagged paragraphs -- every marker handed back in order,
with the same counts: 79.5% for v2.1, 78.1% here.
### A note on strength
Every number above is measured at the strength the release is baked at, which is
not the same as measuring the adapter. LoRA strength is spent at merge time
(`W + (B @ A) * 2.40` here), and the metrics move with it: read at the adapter's
natural 1.0 this same checkpoint is a materially different artifact.
Both releases ship at 1.20, so every difference above is the training pool and
not the strength. 1.20 is where banned constructions bottomed out on the
strength sweep: 1.15 carries a third more of them.
## Training
**Corrupt forward, train backward.** The human paragraph is the target; an
on-policy LLM manufactures the input by slop-ifying it.
The target side is human prose: published contemporary literary fiction,
r/WritingPrompts
([`Mollymo/Human-to-AI-writing`](https://huggingface.co/datasets/Mollymo/Human-to-AI-writing)),
AO3
([`midwestern-simulation-active/ao3_random_subset`](https://huggingface.co/datasets/midwestern-simulation-active/ao3_random_subset)),
a scrape of `bluemoonroleplaying.com` -- the only human writing in the pool
already in the deployment's own register -- and a sliver of fanfiction.net
([`atom-in-the-universe/fanfics-10k-10k`](https://huggingface.co/datasets/atom-in-the-universe/fanfics-10k-10k)).
The input side is manufactured from those targets, weighted and share-capped so
that no single generator's tics dominate:
| pool axis | composition |
|---|---|
| rows | 15,290 over 10,982 distinct target paragraphs |
| corruption band | medium 35%, heavy 31%, light 30%, identity/no-op 3%, curated real slop 1% |
| `len_mode` | match only |
| markup | 15% of rows, identical on both sides |
| kind | prose 91%, dialogue 8%, structural no-ops 1% |
| target source | published fiction 39%, r/WritingPrompts 29%, AO3 20%, roleplay forum 8%, other 4% |
Pairs pass invariant gates before they reach the GPU: POV, tense, who is in the
scene, the gender of who is in the scene, the number of spoken lines,
grammatical correctness on the target side, no repetition on the target side,
content recall stratified by target length, and NLI entailment both ways. Two further screens shape what reaches
training -- a floor on how much a pair actually changes, and a floor on the
sentence-length variety of the target, applied at a higher threshold for long
paragraphs than short ones.
**Loss on the target paragraph only.** Everything before `rewrite` is masked.
| | |
|---|---|
| LoRA | r=32, alpha=64, dropout 0.05 |
| target modules | q, k, v, o, gate, up, down, **and `lm_head`** |
| trainable | 71,004,160 params (1.73%) |
| schedule | 2 epochs, lr 1e-4 cosine, batch 8 × accum 4, seq 2048 |
| steps | 920 on one RTX 3090 |
## The merge
Merged at strength 1.20. Rank 32 with alpha 64 is a LoRA scaling of 2.0, so the
effective scaling is **2.40**: `W + (B @ A) * 2.40`. Merged in float32, stored
bfloat16.
`lm_head` is adapted, and `Qwen3-4B-Base` ties `lm_head.weight` to
`embed_tokens.weight`. **This checkpoint is untied**: the merged output head is
stored separately and the input embeddings are bit-identical to the base
model's, which is what training assumed. `config.json` says
`tie_word_embeddings: false` and it means it. Do not re-tie it, and if you
convert to another format, check that the head survived.
## Limitations
- **Not an instruct model.** It has one job and one prompt. There is nothing to
ask it.
- **Works on fictional prose only.** May not work on technical documentation.
- **One paragraph per call.** Longer input degrades; split it.
- **Still invents people occasionally.** A phantom pronoun appears in 0.8% of
rewrites. Check the output when the cast matters.
- **Truncates.** It drops below 0.75x the input's length on 21.4% of paragraphs.
Usually that is the slop going; check the output when length matters.
- **Still repeats itself.** 8.7% of rewrites repeat a sentence, a phrase or a
sentence opening the input did not. Check the output for loops.
- **Will not pass AI detectors.** Pangram and such will still know because this
model preserves word choices and certain sentence structures.
- **English only**, narrative register (third and first person fiction, dialogue
with quoted speech).
- **Short input pads, cuts and invents.** The floor is about 15 words. See
[Input length](#input-length).
- **Repeats a noun sooner than an LLM would.** Human prose reuses a plain noun
where generated prose uses a synonym, and this model has learned that habit.
## License
The weights in this repository are released under the **GNU Affero General
Public License, version 3**. The full text is in `LICENSE`.
This is a derivative of [`Qwen/Qwen3-4B-Base`](https://huggingface.co/Qwen/Qwen3-4B-Base),
which is licensed under **Apache License 2.0**. That license is preserved and
its terms continue to apply to the base weights this model was built from; the
AGPL covers the combined work as distributed here. Apache-2.0 is one-way
compatible with AGPLv3, which is what makes this combination possible.
If you run a modified version of this model as a network service, AGPL section
13 requires you to offer the corresponding source of your modifications to its
users.