Background: AI slop
Current LLMs write fluent imitations of good prose that match the surface: tweets
that read like a brand account, essays that resolve into generic motivational endings, "mini-paper"
abstracts that over-cover and dump numbers. Standard pairwise and single-score LLM judges fail to catch
this — they often prefer model writing to real expert writing. Following FAIR's unslop work, the fix is
rubrics that separate expert from model writing, used as an RL reward.
Method: calibrated expert-rubric reward + GRPO
Our take on RL-XAR, on a text-only prose slice:
- Rubrics. For each expert excerpt (tweets from the Broad Twitter Corpus, scientific abstracts from
arXiv, blog essays from PG / Sivers / sama), a frozen per-item rubric is generated and kept fixed for
the whole run — no rubric drift during RL.
- Length-blind judge. A calibrated Claude Opus judge scores a candidate against its rubric on a
0–10 quality scale
q, with a length-blind system prompt so it rewards craft.
- Calibrated reward.
reward = L(words, target) · σ(a_d · (q − μ_d)), where L is a length factor
against the item's target length and (μ_d, a_d) are a per-domain calibration fit on the
Qwen3.8-27B reference distribution. This keeps the signal about quality
- RL. GRPO on
Qwen/Qwen3.8-27B via verl, batch 128 (16 prompts × 8 samples), 100 steps,
lr 2e-5, kl_loss_coef 0.002, thinking disabled. Generation is served by vLLM colocated with FSDP.
Over 100 steps the mean reward rose ~0.35 → 0.55 while entropy settled from 1.5 to ~0.43 (convergence on
a consistent voice).
Results
Held-out prose, graded by the same calibrated length-blind Opus judge on the same frozen rubrics. We
compare the base model, this model (antislop), and Altworld/Hemmingway-1 (a creative-writing
finetune of the same base).
Table with columns: model, mean q (0–10) ↑, tweets, articles, blogs| model | mean q (0–10) ↑ | tweets | articles | blogs |
|---|
| base Qwen3.8-27B | 6.31 | 7.18 | 5.75 | 5.53 |
| Qwen3.8-27B-antislop | 8.28 | 8.76 | 6.94 | 8.03 |
| Hemmingway-1 | 7.10 | 7.66 | 7.06 | 6.56 |
Pairwise win-rate (per-item, higher q):
- antislop vs base: 50–0 (2 ties)
- antislop vs Hemmingway-1: 43–6 (3 ties) → 82.7%
EQ-Bench 4
Official leaderboard config (same personas, judge panel and anchors), folded into the anchor Elo table.
Table with columns: rank, model, Elo (95% CI)| rank | model | Elo (95% CI) |
|---|
| 1 | claude-opus-5 | 1382.9 (1366–1401) |
| 2 | moonshotai/kimi-k3 | 1334.6 (1321–1349) |
| 3 | claude-fable-5 | 1330.9 (1320–1342) |
| 4 | Qwen3.8-27B-antislop | 1320.1 (1309–1332) |
| 5 | openai/gpt-5.5 | 1314.4 (1304–1323) |
| 6 | claude-opus-4-7 |
Measuring it on EQ-Bench 4
Qwen's chat template opens <think> inside the prompt, so the reply carries only a closing </think>.
EQ-Bench strips <think>…</think> pairs and misses the orphan, and the persona and judges then read the
reasoning. Serve the model with thinking off and a reasoning parser:
vllm serve AlexWortega/Qwen3.8-27B-antislop --served-model-name antislop \ --reasoning-parser qwen3 --default-chat-template-kwargs '{"enable_thinking": false}' \ --max-model-len 32768
Check that content has no </think> before you start:
curl -s localhost:8000/v1/chat/completions -H 'Content-Type: application/json' \ -d '{"model":"antislop","messages":[{"role":"user","content":"hi"}]}' | grep -c '</think>' # expect 0
Then run EQ-bench/eqbench4 with the leaderboard panel, pointing
antislop at http://localhost:8000/v1/chat/completions in config.yaml, and fold the result into the anchor table:
uv run python eqbench4.py --evaluated-model antislop \ --persona-models google/gemini-3.1-pro-preview \ --pairwise-judges google/gemini-3.1-pro-preview,openai/gpt-5.5,claude-opus-4-6 \ --pointwise-judges claude-opus-4-8 \ --run-id antislop --results-dir results --out results/antislop.jsonuv run eqbench --redo-elo <the same arguments> # rebuilds Elo against the leaderboard anchors
Examples — baseline vs antislop
Freshly generated from this model vs the base, same prompt, same decoding (enable_thinking=False,
temp 0.8). The baseline reaches for abstract, generic phrasing and tidy clichés; the antislop model stays
concrete and in-voice.
Continue an essay — "The problem with productivity advice is that it treats attention as a resource
to be spent, when really"
base: …it's more like a weather pattern. You don't "manage" the rain… Stop trying to hoard every
minute like it's gold. The quiet gaps aren't waste; they're where the actual thinking happens.
antislop: …it's more like a mood. You just end up staring at it for forty minutes because the cursor
is blinking and you're too tired to close the tab.
Continue a product memo — "We looked at why users churn in week one, and the honest answer is"
base: …they can't find value fast enough. The onboarding flow is too long… If we don't make the
first ten seconds count, they'll leave before they even understand what the product does.
antislop: …they don't know what to do next. The dashboard is empty, the onboarding tooltip
disappears after three seconds, and there's no clear button saying "invite your team". They close the
tab, assume it's broken, and never come back.
Finish a casual tweet — prefix "first coffee of the day and"
base: first coffee of the day and i'm finally functional. save me, caffeine.
antislop: first coffee of the day and i already know i'm not functioning before 10am
What's in this repo
Table with columns: path, what, notes| path | what | notes |
|---|
./ (root) | full bf16 model | base Qwen3_5ForConditionalGeneration with vision tower + MTP head intact; GRPO-updated LLM. 1199 tensors. |
mxfp4-a16/ | MXFP4 weight-only quant | OCP microscaling FP4 (group 32, E8M0), text-only, for compact inference. Real 4-bit speed needs Blackwell (B200). |
Usage
from transformers import AutoModelForCausalLM, AutoTokenizertok = AutoTokenizer.from_pretrained("AlexWortega/Qwen3.8-27B-antislop")model = AutoModelForCausalLM.from_pretrained("AlexWortega/Qwen3.8-27B-antislop", dtype="bfloat16", device_map="auto", trust_remote_code=True)msgs = [{"role": "user", "content": "Finish this tweet naturally: Monday mornings are"}]text = tok.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True, enable_thinking=False)ids = tok(text, return_tensors="pt").to(model.device)print(tok.decode(model.generate(**ids, max_new_tokens=64, do_sample=True, temperature=0.8)[0][ids.input_ids.shape[1]:]))
Training details & versions
Table | |
|---|
| base model | Qwen/Qwen3.8-27B @ 1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0 |
| RL algo | GRPO (verl @ 76e837e) |
| steps / batch | 100 / 128 (16×8) |
| lr / kl_loss_coef | 2e-5 / 0.002 |
| reward judge | Claude Opus (length-blind, frozen per-item rubrics) |
| domains | tweets, scientific abstracts, blog essays (EN) |
| hardware | 2× H200 (SECURE) |
Training and eval scripts are in training_code/; REPRODUCE.md runs them end to end.
Acknowledgements & citation
This is an independent reimplementation of Meta FAIR's RL-XAR / unslop idea on a different base model and a
different judge/calibration stack. Please cite the original work:
@misc{fair_unslop, title = {Unslop: RL from eXpert Aligned Rubrics (RL-XAR)}, author = {Meta FAIR}, url = {https://facebookresearch.github.io/RAM/blogs/unslop/}, note = {Research blog}}