prose-rewriter-4b-v2.4

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.

Longer sentences, less choppy prose. A rewrite's sentences average 16.9 words, up from 13.4 (input: 18.2).

Fewer invented facts and changed meanings, at the same edit depth as v2.3. An LLM judge finds an added fact in 7.5% of rewrites, down from 10.7%, and a changed meaning in 18.9%, down from 23.7%.

Less truncation, less repetition. Rewrites cut below 0.75x of the input fall from 16.6% to 5.9%, and rewrites that repeat themselves from 6.9% to 3.8%.

Variants

Path Format Use with
/ safetensors bf16, qwen3 arch transformers
GGUF/prose-rewriter-4b-v2.4-Q8_0.gguf GGUF Q8_0, 4.69 GB llama.cpp / llama-cpp-python
GGUF/prose-rewriter-4b-v2.4-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:

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

Sample at temperature=0.85, top_p=0.9. Temperature 0.85 is the recommended value.

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

repo = "chartreuse-verte/prose-rewriter-4b-v2.4"
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.85, 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:

llama-cli -m GGUF/prose-rewriter-4b-v2.4-Q8_0.gguf -no-cnv -n 512 --temp 0.85 --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.

Fidelity, judged by an LLM against the input. A rewrite counts when the judge gives it a probability of 0.5 or more:

v2.3 v2.4
adds a fact the input does not support 10.7% 7.5%
changes the meaning of something the input says 23.7% 18.9%
either 26.9% 21.4%
a sentence that is not well-formed (input: 0.3%) 3.9% 2.3%

Paired over the 365 inputs, all four fall (t = -2.5, -2.9, -3.3, -2.5).

Invention of people, on the same paragraphs:

v2.3 v2.4
a gendered pronoun the input never licensed 0.9% 0.5%
... belonging to the other gender 0.1% 0.0%
a proper noun the input does not have 0.5% 0.4%

Repetition -- a rewrite that repeats a sentence, a phrase, or a sentence opening:

input v2.3 v2.4
any repetition 2.2% 6.9% 3.8%
... a whole sentence 0.0% 0.0%
... a phrase 3.4% 1.6%
... a sentence opening (anaphora) 2.7% 1.8%

Paired over the 365 inputs, any repetition falls (t = -3.4) and repeated phrases fall (t = -2.7). Anaphora does not separate.

Structural, on the same paragraphs:

v2.3 v2.4
words changed 42.9% 42.3%
passed through unchanged 0.6% 0.0%
near-verbatim outputs 0.0% 0.0%
below the training edit floor 34.1% 36.7%
sentence count moved 75.4% 58.1%
sentence-length variety vs input +0.098 +0.024
words kept from the input 0.626 0.637
length preserved 0.872 0.921
truncated below 0.75x 16.6% 5.9%
repeated 3-grams 0.002 0.002

Paired over the 365 inputs, v2.4 never passes the input through (t = -2.5), holds more of its length (t = +12.7) and truncates a third as often (t = -8.6). It moves the sentence count less (t = -8.3) and varies sentence length less (t = -10.6). Words changed does not move.

Unsupported content -- a sentence in the rewrite the input does not entail, scored by NLI with the input alone as the premise:

v2.3 v2.4
unsupported sentences, mean per rewrite 6.3% 4.3%
unsupported sentences, all rewrites 6.1% 4.4%
coverage of the input (reverse entailment) 0.485 0.582

Paired over the 365 inputs, the unsupported rate falls (t = -4.6) and coverage rises (t = +13.5).

The register numbers on the same paragraphs, against the input:

input v2.3 v2.4 human corpus
words per sentence 18.2 13.4 16.9 17.8
banned constructions /1k 7.52 2.00 1.90 0.00
slop lexicon density 0.081 0.038 0.036 0.019
purple score 0.498 0.278 0.278 --
negated narration shapes per rewrite 0.118 0.132 0.080 --

Paired over all 365 inputs, sentences lengthen (t = +20.5) and negated narration falls (t = -4.5). Banned constructions, lexicon density and purple do not move.

Dialogue: the number of spoken lines changes on 0.3% of rewrites against v2.3's 0.9% (t = -2.7). No rewrite whose input carried quoted speech lost the quotes, in either release.

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 the same strength, so every difference above is training and not the strength.

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), AO3 (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).

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

Then reinforcement learning against a fidelity judge. Starting from the SFT adapter, GRPO samples eight rewrites of each roleplay paragraph and moves toward the ones that score best. The reward is an LLM judge's verdict that the rewrite adds no fact and changes no meaning, plus the drop in a purple-prose classifier's score with punctuation ignored, minus penalties for broken grammar the input did not have, sentences shorter on average than the input's, choppy runs of short sentences, added negated narration, added repetition, truncation below 0.75x and near-verbatim output.

prompts LLM-written roleplay paragraphs with a purple score of 0.5 or more
schedule 120 steps, 16 paragraphs ร— 8 rewrites, lr 1e-5, KL 0.04 to the SFT adapter
sampling temperature=0.9, top_p=0.95, at strength 1.20

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 occasionally. The judge finds an added fact in 7.5% of rewrites and a changed meaning in 18.9%. Check the output when the facts matter.
  • Truncates. It drops below 0.75x the input's length on 5.9% of paragraphs. Usually that is the slop going; check the output when length matters.
  • Still repeats itself. 3.8% 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.
  • 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, 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.

Downloads last month
378
Safetensors
Model size
4B params
Tensor type
BF16
ยท
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support

Model tree for chartreuse-verte/prose-rewriter-4b-v2.4

Quantized
(56)
this model
Quantizations
1 model