prose-rewriter-1.7b-v1.6

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-1.7B-Base with a rank-32 LoRA merged in at strength 1.15.

Successor to prose-rewriter-1.7b-v1.5.

Better data: Cleaned up badly formatted, annotated, unbalanced quotes, miscapitalization, etc.

Added some contemporary novels to the training pool and a gate to reduce invention.

See Evaluation.

Variants

Path Format Use with
/ safetensors bf16, qwen3 arch transformers
GGUF/prose-rewriter-1.7b-v1.6-Q8_0.gguf GGUF Q8_0, 2.16 GB llama.cpp / llama-cpp-python

The quant carries the chat template and stops 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|>edit
match<|im_end|>
<|im_start|>rewrite

The chat template in this repo builds exactly that string, byte for byte, from two roles:

messages = [
    {"role": "source", "content": paragraph},
    {"role": "edit",   "content": "match"},
]
tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)

It is not a chat model. The template rejects an edit value outside the three modes rather than quietly building a prompt the weights have never seen. Any other role is treated as the source paragraph, so a runtime that probes the template with a user message still gets a valid prompt.

The edit block is mandatory

edit names which of three length transforms is being asked for. The values describe the input, not the instruction. They say what kind of text you are handing over:

edit what it says about the input what the model does
match the source is about the length it should be rewrite in place
inflate the source is padded relative to what it should be cut
compress the source is flattened and too short open it back out

match is the setting for "rewrite it, do not trim it". It's strongly recommended you use this mode.

Sending no block is the worst thing you can do to this checkpoint. It was trained with the block, so omitting it collapses the model onto its deletion-heaviest mode.

Serving recipe

Sampled at temperature=0.9, top_p=0.9. Temperature 0.9 has been tested internally to be the most optimal value. It's recommended you use this.

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

repo = "chartreuse-verte/prose-rewriter-1.7b-v1.6"
tok = AutoTokenizer.from_pretrained(repo)
model = AutoModelForCausalLM.from_pretrained(repo, dtype=torch.bfloat16, device_map="cuda").eval()

def rewrite(paragraph, mode="match"):
    text = tok.apply_chat_template(
        [{"role": "source", "content": paragraph},
         {"role": "edit",   "content": mode}],
        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 roles are source and edit, which no chat API models, so build the string yourself; <|im_end|> stops it:

llama-cli -m GGUF/prose-rewriter-1.7b-v1.6-Q8_0.gguf -no-cnv -n 512 --temp 0.9 --top-p 0.9 \
  -p '<|im_start|>source
{paragraph}<|im_end|>
<|im_start|>edit
match<|im_end|>
<|im_start|>rewrite
'

Input length

The training pool's median input is 42 words and most 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 and fabrication rather than gibberish: the model stretches the line toward its learned length and adds material the input never supported. Below 80 bytes, pass the text through unchanged.

Evaluation

Both releases measured as they ship -- v1.5 baked at strength 1.20, v1.6 at 1.15 -- on 365 held-out paragraphs of LLM-written prose that neither model saw in training, both sent the identical prompt at temperature=0.9, top_p=0.9, three swipes each.

The release is a fidelity release. What the rewriter does did not move:

v1.5 v1.6
words changed 37.3% 37.5%
words kept from the input 0.680 0.674
passed through unchanged 5.9% 4.0%
below the training edit floor 51.5% 50.6%
near-verbatim outputs 2.4% 2.1%
sentence count moved 77.1% 76.6%
sentence-length variety vs input +0.122 +0.126
length preserved 0.884 0.875
truncated below 0.75x 16.4% 17.1%
repeated 3-grams 0.006 0.007

Paired over the 303 inputs of 51-90 words, no column above separates the two models: the largest effect is the no-op rate at t = -1.77, and every other paired t falls between -1.2 and +0.6. Treat this table as evidence of sameness, which is what makes the next one readable.

What moved is unsupported content -- a sentence in the rewrite that the input does not entail, scored by NLI with the input alone as the premise:

v1.5 v1.6
rewrites with an unsupported sentence 12.4% 9.0%
unsupported sentences, all rewrites 11.7% 8.4%
coverage of the input (reverse entailment) 0.522 0.520

Less invention at the same coverage. The distinction matters: a model can always cut its unsupported rate by saying less, and that would show up as coverage falling. It did not move, and neither did truncation.

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

input v1.5 v1.6 human corpus
banned constructions /1k 7.52 3.35 3.18 0.00
slop lexicon density 0.081 0.050 0.049 0.019
purple score 0.498 0.324 0.323 --

Deslop is unchanged. v1.6 buys its fidelity without giving any of it back.

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.30 here), and the metrics move with it: read at the adapter's natural 1.0 this same checkpoint is a materially different artifact.

The strength itself was chosen by reading a blind three-arm sheet, not by the metric sweep. Across three independent 80-paragraph sweeps the columns could not separate 1.15 from 1.20 -- truncation at 1.20 read 12.5%, 22.5% and 26.2% on three samples of the same checkpoint -- so the tie was broken by hand on coherence.

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: 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).

New in v1.6: professionally edited fiction. Every source above is amateur archive prose. Published contemporary literary fiction now supplies about a fifth of the target side -- the first professionally edited register in the pool, added because it is the one class that clears the corruptor's own output by a margin wide enough to register on vocabulary breadth.

The input side is manufactured from those targets, weighted and share-capped so that no single generator's tics dominate:

pool axis composition
rows 28,985 over 22,751 distinct target paragraphs
corruption band medium 36%, heavy 33%, light 28%, identity/no-op 3%
len_mode match 56%, inflate 34%, compress 7%
kind prose 94%, dialogue 6%, structural no-ops 1%
target source r/WritingPrompts 37%, AO3 29%, published fiction 20%, roleplay forum 13%, fanfiction.net 1%

Pairs pass invariant gates before they reach the GPU: POV, tense, who is in the scene, grammatical correctness 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 39,792,640 params (2.26%)
schedule 2 epochs, lr 1e-4 cosine, batch 4 × accum 8, seq 2048
steps 1,774 on one RTX 3090

The merge

Merged at strength 1.15. Rank 32 with alpha 64 is a LoRA scaling of 2.0, so the effective scaling is 2.30: W + (B @ A) * 2.30. Merged in float32, stored bfloat16.

lm_head is adapted, and Qwen3-1.7B-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.
  • 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 and invents. The floor is about 15 words, and below it the failure is fabrication rather than gibberish. See Input length.
  • Still invents on about one rewrite in eleven. 9.0% is an improvement on v1.5's 12.4%, not a solution. If the semantics must be exact, check the output.
  • 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-1.7B-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
514
Safetensors
Model size
2B 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-1.7b-v1.6

Quantized
(43)
this model
Quantizations
1 model