Text Generation
GGUF
Safetensors
Transformers
English
Chinese
gemma4_unified
image-text-to-text
humanizer
text-rewriting
rewriting
paraphrase
style-transfer
llama.cpp
gemma4
conversational
Instructions to use hardcoremoore/humanizer with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use hardcoremoore/humanizer with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="hardcoremoore/humanizer")# pip install -U transformers accelerate # Load model directly from transformers import AutoProcessor, AutoModelForMultimodalLM processor = AutoProcessor.from_pretrained("hardcoremoore/humanizer") model = AutoModelForMultimodalLM.from_pretrained("hardcoremoore/humanizer", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use hardcoremoore/humanizer with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf hardcoremoore/humanizer:Q4_K_M # Run inference directly in the terminal: llama cli -hf hardcoremoore/humanizer:Q4_K_M
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf hardcoremoore/humanizer:Q4_K_M # Run inference directly in the terminal: llama cli -hf hardcoremoore/humanizer:Q4_K_M
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf hardcoremoore/humanizer:Q4_K_M # Run inference directly in the terminal: ./llama-cli -hf hardcoremoore/humanizer:Q4_K_M
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf hardcoremoore/humanizer:Q4_K_M # Run inference directly in the terminal: ./build/bin/llama-cli -hf hardcoremoore/humanizer:Q4_K_M
Use Docker
docker model run hf.co/hardcoremoore/humanizer:Q4_K_M
- LM Studio
- Jan
- vLLM
How to use hardcoremoore/humanizer with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "hardcoremoore/humanizer" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "hardcoremoore/humanizer", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/hardcoremoore/humanizer:Q4_K_M
- SGLang
How to use hardcoremoore/humanizer with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "hardcoremoore/humanizer" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "hardcoremoore/humanizer", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "hardcoremoore/humanizer" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "hardcoremoore/humanizer", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Ollama
How to use hardcoremoore/humanizer with Ollama:
ollama run hf.co/hardcoremoore/humanizer:Q4_K_M
- Unsloth Desktop
- Docker Model Runner
How to use hardcoremoore/humanizer with Docker Model Runner:
docker model run hf.co/hardcoremoore/humanizer:Q4_K_M
- Lemonade
How to use hardcoremoore/humanizer with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull hardcoremoore/humanizer:Q4_K_M
Run and chat with the model
lemonade run user.humanizer-Q4_K_M
List all available models
lemonade list
- Atomic Chat
Commit ·
56f85a6
0
Parent(s):
Duplicate from jialinyyzz/humanizer
Browse filesCo-authored-by: Stephen Yu <jialinyyzz@users.noreply.huggingface.co>
- .gitattributes +70 -0
- AGENTS.md +280 -0
- LICENSE +202 -0
- NOTICE +10 -0
- README.md +349 -0
- USAGE.md +732 -0
- USAGE.zh.md +732 -0
- assets/app-en.png +3 -0
- assets/app-showcase-en.png +3 -0
- assets/app-showcase-zh.png +3 -0
- assets/app-zh.png +3 -0
- assets/banner-en.png +3 -0
- assets/banner-zh.png +3 -0
- assets/compare-en-email.png +3 -0
- assets/compare-en-forum.png +3 -0
- assets/compare-zh-email.png +3 -0
- assets/compare-zh-zhihu.png +3 -0
- assets/eval-en.png +3 -0
- assets/eval-zh.png +3 -0
- assets/quant-top1-en.png +3 -0
- assets/results-detector-en.png +3 -0
- assets/results-detector-zh.png +3 -0
- assets/results-fidelity-en.png +3 -0
- assets/results-fidelity-zh.png +3 -0
- assets/training-en.png +3 -0
- assets/training-zh.png +3 -0
- config.json +165 -0
- generation_config.json +14 -0
- humanizer-12b-IQ2_XS-QAT.gguf +3 -0
- humanizer-12b-Q3-QAT.gguf +3 -0
- humanizer-12b-Q4_K_M.gguf +3 -0
- humanizer-12b-Q6_K.gguf +3 -0
- humanizer-12b-Q8_0.gguf +3 -0
- humanizer-12b-bf16.gguf +3 -0
- model.safetensors +3 -0
- prompt_format.json +8 -0
- tokenizer.json +3 -0
- tokenizer_config.json +55 -0
.gitattributes
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
gguf/humanizer-gemma-4-e4b-bf16.gguf filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
gguf/humanizer-gemma-4-e4b-Q4_K_M.gguf filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
gguf/humanizer-gemma-4-e4b-Q5_K_M.gguf filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
gguf/humanizer-gemma-4-e4b-Q8_0.gguf filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
gguf/humanizer-gemma-4-e4b-Q6_K.gguf filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
lite/humanizer-lite-Q8_0.gguf filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
lite/humanizer-lite-Q6_K.gguf filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
lite/humanizer-lite-bf16.gguf filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
humanizer-12b-Q8_0.gguf filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
humanizer-12b-Q6_K.gguf filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
assets/results-detector-en.png filter=lfs diff=lfs merge=lfs -text
|
| 48 |
+
assets/training-zh.png filter=lfs diff=lfs merge=lfs -text
|
| 49 |
+
assets/app-showcase-zh.png filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
assets/banner-zh.png filter=lfs diff=lfs merge=lfs -text
|
| 51 |
+
assets/compare-en-email.png filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
assets/compare-zh-zhihu.png filter=lfs diff=lfs merge=lfs -text
|
| 53 |
+
assets/training-en.png filter=lfs diff=lfs merge=lfs -text
|
| 54 |
+
assets/results-detector-zh.png filter=lfs diff=lfs merge=lfs -text
|
| 55 |
+
assets/banner-en.png filter=lfs diff=lfs merge=lfs -text
|
| 56 |
+
assets/app-showcase-en.png filter=lfs diff=lfs merge=lfs -text
|
| 57 |
+
assets/compare-en-reddit.png filter=lfs diff=lfs merge=lfs -text
|
| 58 |
+
assets/results-fidelity-en.png filter=lfs diff=lfs merge=lfs -text
|
| 59 |
+
assets/compare-zh-email.png filter=lfs diff=lfs merge=lfs -text
|
| 60 |
+
assets/app-en.png filter=lfs diff=lfs merge=lfs -text
|
| 61 |
+
assets/app-zh.png filter=lfs diff=lfs merge=lfs -text
|
| 62 |
+
assets/results-fidelity-zh.png filter=lfs diff=lfs merge=lfs -text
|
| 63 |
+
humanizer-12b-Q4_K_M.gguf filter=lfs diff=lfs merge=lfs -text
|
| 64 |
+
assets/compare-en-forum.png filter=lfs diff=lfs merge=lfs -text
|
| 65 |
+
humanizer-12b-bf16.gguf filter=lfs diff=lfs merge=lfs -text
|
| 66 |
+
humanizer-12b-IQ2_XS-QAT.gguf filter=lfs diff=lfs merge=lfs -text
|
| 67 |
+
humanizer-12b-Q3-QAT.gguf filter=lfs diff=lfs merge=lfs -text
|
| 68 |
+
assets/quant-top1-en.png filter=lfs diff=lfs merge=lfs -text
|
| 69 |
+
assets/eval-en.png filter=lfs diff=lfs merge=lfs -text
|
| 70 |
+
assets/eval-zh.png filter=lfs diff=lfs merge=lfs -text
|
AGENTS.md
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AGENTS.md: install and call humanizer
|
| 2 |
+
|
| 3 |
+
Instructions for AI agents (Claude Code, Codex, Cursor, etc.) setting up **humanizer** for a user. Follow the steps in order. Every command can be pasted as is. Human-oriented docs: [README.md](https://github.com/sgaofen/humanizer-local-model/blob/main/README.md), [USAGE.md: usage without the app](https://github.com/sgaofen/humanizer-local-model/blob/main/docs/USAGE.md) (every runtime, batch script, long documents), [INSTALL.md](https://github.com/sgaofen/humanizer-local-model/blob/main/docs/INSTALL.md). This file is also in the root of the Hugging Face repo, next to `USAGE.md`.
|
| 4 |
+
|
| 5 |
+
## 0. Facts you need
|
| 6 |
+
|
| 7 |
+
- **What it does:** rewrites one AI-written draft (English or Chinese) so it reads like a person wrote it. It is trained to keep every number, unit, date, name and quote, and to add nothing. Output = the rewritten text only.
|
| 8 |
+
- **Kind of model:** 12B **text-completion** model (fine-tuned from `google/gemma-4-12B`). **Not a chat model.** Call the completion endpoint with the exact prompt from step 6: no system prompt, no chat turns. (The GGUF files uploaded on or after 2026-10-04 also carry a chat template that builds the same prompt from the last user message, so chat front ends such as LM Studio should work; the authors have not tested them. In code, use the completion endpoint: it works with every download.)
|
| 9 |
+
- **Weights:** Hugging Face repo `jialinyyzz/humanizer`. Recommended file: `humanizer-12b-Q8_0.gguf` (12,669,630,368 bytes, about 12.7 GB); `humanizer-12b-Q6_K.gguf` (10,029,799,584 bytes, about 10.0 GB) for 16 GB machines; `humanizer-12b-Q4_K_M.gguf` (7,625,160,864 bytes, about 7.6 GB) for about 14 GB or when disk is tight; `humanizer-12b-Q3-QAT.gguf` (5,587,794,816 bytes, about 5.6 GB) for 12 GB; the smallest, `humanizer-12b-IQ2_XS-QAT.gguf` (3,893,632,896 bytes, about 3.9 GB, 2-bit), for 8 GB. Q3 and 2-bit make a few more fact slips. You also need `prompt_format.json` from the same repo.
|
| 10 |
+
- **Runtime:** `llama-server` from llama.cpp (macOS, Windows, Linux). Alternatives in section 10.
|
| 11 |
+
- **Sampling:** temperature 1.0, top_p 0.95, and nothing else: top_k 0, min_p 0, repeat_penalty 1.0. Stop on EOS only. No stop strings.
|
| 12 |
+
- **Language check:** occasionally, on short informal English drafts with technical jargon, the model writes the whole rewrite in Chinese. If the draft is English and the rewrite has more than a few Chinese characters, sample again with the same settings (up to 3 times). The app (0.3.1 and later) and `hz` do this themselves.
|
| 13 |
+
- **License:** Apache 2.0.
|
| 14 |
+
|
| 15 |
+
## 1. Pick the route
|
| 16 |
+
|
| 17 |
+
- **The user wants an app, not code:** send them to <https://github.com/sgaofen/humanizer-local-model/releases/latest> and have them download `Humanizer-<version>-macos-arm64.dmg` (Mac with Apple silicon) or `Humanizer-<version>-windows-x64-setup.exe` (Windows x64). The app is unsigned; the first-launch fix is in [INSTALL.md](https://github.com/sgaofen/humanizer-local-model/blob/main/docs/INSTALL.md#first-launch-warnings). The app downloads the model itself. **You are done.**
|
| 18 |
+
- **The user wants a long or structured document rewritten** (Markdown with headings, lists, code or tables; a `.docx`; anything over a few paragraphs): use the `hz` command, [section 12](#12-long-and-complex-documents-use-hz). It needs the app or a llama-server running the model (steps 2 to 5), and does the splitting, the prompt, the sampling and the checks itself.
|
| 19 |
+
- **Otherwise** (scripts, pipelines, a local API): continue with step 2.
|
| 20 |
+
|
| 21 |
+
## 2. Check the machine
|
| 22 |
+
|
| 23 |
+
```bash
|
| 24 |
+
# memory in GB
|
| 25 |
+
sysctl -n hw.memsize | awk '{print $1/1073741824}' # macOS
|
| 26 |
+
free -g | awk '/Mem:/{print $2}' # Linux
|
| 27 |
+
powershell -c "(Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory/1GB" # Windows
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
| Memory | Use |
|
| 31 |
+
|---|---|
|
| 32 |
+
| 32 GB or more | `humanizer-12b-Q8_0.gguf` (12,669,630,368 bytes) |
|
| 33 |
+
| 16 GB | `humanizer-12b-Q6_K.gguf` (10,029,799,584 bytes); `humanizer-12b-Q4_K_M.gguf` (7,625,160,864 bytes) if disk is tight |
|
| 34 |
+
| About 14 GB | `humanizer-12b-Q4_K_M.gguf` (7,625,160,864 bytes) |
|
| 35 |
+
| 12 GB | `humanizer-12b-Q3-QAT.gguf` (5,587,794,816 bytes). A few more fact slips in English; tell the user to proofread numbers and names. |
|
| 36 |
+
| 8 GB | `humanizer-12b-IQ2_XS-QAT.gguf` (3,893,632,896 bytes, 2-bit, the smallest). More fact slips; tell the user to proofread numbers and names. |
|
| 37 |
+
| Less than 8 GB | Not enough for the 12B model; tell the user. |
|
| 38 |
+
|
| 39 |
+
You also need free disk space for the file you pick. In the commands below, replace the Q8_0 file name if you picked another file.
|
| 40 |
+
|
| 41 |
+
## 3. Install llama.cpp
|
| 42 |
+
|
| 43 |
+
```bash
|
| 44 |
+
brew install llama.cpp # macOS
|
| 45 |
+
winget install llama.cpp # Windows
|
| 46 |
+
# Linux, or any OS: prebuilt zips at https://github.com/ggml-org/llama.cpp/releases
|
| 47 |
+
# (Windows + NVIDIA: the CUDA build; other GPUs: the Vulkan build), or build from source:
|
| 48 |
+
# cmake -B build -DGGML_CUDA=ON && cmake --build build --config Release -j
|
| 49 |
+
llama-server --version # must print a version
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
The app ships llama.cpp build `b11335`; use that build or a newer one.
|
| 53 |
+
|
| 54 |
+
## 4. Download the model
|
| 55 |
+
|
| 56 |
+
```bash
|
| 57 |
+
pip install -U "huggingface_hub[cli]"
|
| 58 |
+
hf download jialinyyzz/humanizer humanizer-12b-Q8_0.gguf prompt_format.json --local-dir ./humanizer-model
|
| 59 |
+
# Slow from mainland China? Prefix the command with HF_ENDPOINT=https://hf-mirror.com
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
Check the download:
|
| 63 |
+
|
| 64 |
+
```bash
|
| 65 |
+
head -c 4 ./humanizer-model/humanizer-12b-Q8_0.gguf; echo # must print GGUF
|
| 66 |
+
wc -c ./humanizer-model/*.gguf # Q8_0: 12669630368 bytes; Q6_K: 10029799584 bytes; Q4_K_M: 7625160864 bytes;
|
| 67 |
+
# Q3-QAT: 5587794816 bytes; IQ2_XS-QAT: 3893632896 bytes
|
| 68 |
+
shasum -a 256 ./humanizer-model/*.gguf # macOS (Linux: sha256sum)
|
| 69 |
+
# Q8_0: 8d7a457b56de6530eaaf0151ccfa7550a4b20dab979737e259da9c63e960e0b0
|
| 70 |
+
# Q6_K: c98f03bb9e71456181f99b0e1d3391e07ce1afc357f9db4c33d6d379b8dd9f0d
|
| 71 |
+
# Q4_K_M: 2229574dec5178629575ee4a153dfee7d9e924ab997d0ad9d2ac622b67e44834
|
| 72 |
+
# Q3-QAT: 307bbfdf66fb22bf98aaa93fe7d54a713bf167e646073dc0cf10870b1525eb94
|
| 73 |
+
# IQ2_XS-QAT: 383e5ca8f1f48ab5f65013adbc1965fa70d1d1afa6c45d932c34b854e38edbb5
|
| 74 |
+
# bf16 (humanizer-12b-bf16.gguf, 23832049568 bytes, optional): 47d79b44c3e15ea2540f4edb63556f2b7e456067d7dd252a42ff103a26a51be9
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
## 5. Start the server
|
| 78 |
+
|
| 79 |
+
```bash
|
| 80 |
+
llama-server -m ./humanizer-model/humanizer-12b-Q8_0.gguf -c 8192 -np 1 -ngl 99 --host 127.0.0.1 --port 8080
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
Wait until it is ready (loading takes a while the first time):
|
| 84 |
+
|
| 85 |
+
```bash
|
| 86 |
+
curl -s http://127.0.0.1:8080/health # ready when it returns {"status":"ok"}
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
`-np 1` gives the whole 8192-token context to one request (newer builds otherwise split it across parallel slots). `-ngl 99` puts all layers on the GPU (Metal, CUDA or Vulkan). If it runs out of GPU memory, lower `-ngl`. Check the log for `offloaded N/N layers`; if no layers are offloaded it runs on the CPU and will be slow.
|
| 90 |
+
|
| 91 |
+
## 6. Build the prompt, byte for byte
|
| 92 |
+
|
| 93 |
+
```python
|
| 94 |
+
import hashlib, json
|
| 95 |
+
|
| 96 |
+
PF = json.load(open("humanizer-model/prompt_format.json", encoding="utf-8"))
|
| 97 |
+
INSTR, SEP = PF["instr"], PF["sep"] # SEP == "\n\n### Rewritten:\n\n"
|
| 98 |
+
|
| 99 |
+
def build_prompt(draft: str) -> str:
|
| 100 |
+
return INSTR + "\n\n" + draft.strip() + SEP
|
| 101 |
+
|
| 102 |
+
# Must hold. If not, the prompt is wrong and the model will be worse.
|
| 103 |
+
assert hashlib.sha256(build_prompt("X").encode("utf-8")).hexdigest()[:16] == "cc51d66b4c593fbe"
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
If you cannot read `prompt_format.json`, `INSTR` is exactly this text (lines joined with `\n`, blank lines included, no trailing newline):
|
| 107 |
+
|
| 108 |
+
```
|
| 109 |
+
Rewrite the text below so it reads like a person wrote it, not a language model.
|
| 110 |
+
|
| 111 |
+
Reorganize it as you see fit. Vary sentence length on purpose. Cut hedging,
|
| 112 |
+
throat-clearing, and any sentence that only announces what comes next.
|
| 113 |
+
Prefer the concrete word over the abstract one. It is fine to sound uneven.
|
| 114 |
+
|
| 115 |
+
Every fact, number, unit, date, name and quotation must survive unchanged.
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
Do **not** reword it, translate it, add a system prompt, or wrap it in chat turns.
|
| 119 |
+
|
| 120 |
+
## 7. Call it
|
| 121 |
+
|
| 122 |
+
`POST http://127.0.0.1:8080/completion` with:
|
| 123 |
+
|
| 124 |
+
```json
|
| 125 |
+
{
|
| 126 |
+
"prompt": "<build_prompt(draft)>",
|
| 127 |
+
"temperature": 1.0,
|
| 128 |
+
"top_p": 0.95,
|
| 129 |
+
"top_k": 0,
|
| 130 |
+
"min_p": 0,
|
| 131 |
+
"repeat_penalty": 1.0,
|
| 132 |
+
"n_predict": 2048
|
| 133 |
+
}
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
- Do not send `stop`. Generation ends at EOS.
|
| 137 |
+
- `top_k`, `min_p` and `repeat_penalty` must be set explicitly: llama-server's defaults (top_k 40, min_p 0.05) differ from how the model was evaluated.
|
| 138 |
+
- The rewrite is in the `content` field. Strip leading and trailing whitespace.
|
| 139 |
+
- `n_predict`: about 2.5× the draft's token count, between 256 and 2048 (the app's rule). 2048 is a safe fixed value.
|
| 140 |
+
|
| 141 |
+
A reusable function, standard library only:
|
| 142 |
+
|
| 143 |
+
```python
|
| 144 |
+
import json, urllib.request
|
| 145 |
+
|
| 146 |
+
def humanize(draft: str, url: str = "http://127.0.0.1:8080/completion") -> str:
|
| 147 |
+
body = {"prompt": build_prompt(draft), "temperature": 1.0, "top_p": 0.95,
|
| 148 |
+
"top_k": 0, "min_p": 0, "repeat_penalty": 1.0, "n_predict": 2048}
|
| 149 |
+
req = urllib.request.Request(url, json.dumps(body).encode("utf-8"),
|
| 150 |
+
{"Content-Type": "application/json"})
|
| 151 |
+
with urllib.request.urlopen(req, timeout=600) as r:
|
| 152 |
+
return json.load(r)["content"].strip()
|
| 153 |
+
```
|
| 154 |
+
|
| 155 |
+
## 8. Self-test: verify the install
|
| 156 |
+
|
| 157 |
+
Save as `selftest.py` next to `humanizer-model/` and run `python3 selftest.py` while the server is up. Standard library only. The test draft is a real draft from the evaluation set.
|
| 158 |
+
|
| 159 |
+
```python
|
| 160 |
+
import hashlib, json, re, urllib.request
|
| 161 |
+
|
| 162 |
+
PF = json.load(open("humanizer-model/prompt_format.json", encoding="utf-8"))
|
| 163 |
+
build = lambda d: PF["instr"] + "\n\n" + d.strip() + PF["sep"]
|
| 164 |
+
assert hashlib.sha256(build("X").encode()).hexdigest()[:16] == "cc51d66b4c593fbe", "FAIL: prompt format"
|
| 165 |
+
|
| 166 |
+
DRAFT = """Hi Nadia,
|
| 167 |
+
|
| 168 |
+
I’m handing off the Meridian Bank loan audit to you. We’ve completed the review of 88 files, with 12 files remaining. The outstanding files are in the “Pending Review” folder in the Meridian Bank audit workspace, organized by borrower name.
|
| 169 |
+
|
| 170 |
+
The remaining items are primarily commercial real estate loans and include several files requiring confirmation of updated income statements, collateral valuations, and covenant documentation. I’ve flagged the missing items in the audit tracker and added notes for the files that may need follow-up with Meridian Bank’s loan operations team.
|
| 171 |
+
|
| 172 |
+
The deadline is Friday, March 14. Please prioritize the files marked “High” in the tracker, especially MB-2047, MB-2091, and MB-2110, since those are scheduled for management review first. I’ll be available through Wednesday afternoon to answer questions or provide context on any earlier decisions.
|
| 173 |
+
|
| 174 |
+
Thanks for taking this over."""
|
| 175 |
+
|
| 176 |
+
body = {"prompt": build(DRAFT), "temperature": 1.0, "top_p": 0.95, "top_k": 0, "min_p": 0,
|
| 177 |
+
"repeat_penalty": 1.0, "n_predict": 2048}
|
| 178 |
+
req = urllib.request.Request("http://127.0.0.1:8080/completion", json.dumps(body).encode(),
|
| 179 |
+
{"Content-Type": "application/json"})
|
| 180 |
+
out = json.load(urllib.request.urlopen(req, timeout=600))["content"].strip()
|
| 181 |
+
print(out, "\n" + "-" * 60)
|
| 182 |
+
|
| 183 |
+
problems = []
|
| 184 |
+
if len(out.split()) < 60: problems.append("output too short")
|
| 185 |
+
if "### Rewritten" in out or out.startswith("Rewrite the text below"): problems.append("prompt echoed: wrong endpoint or chat template")
|
| 186 |
+
if re.search(r"<start_of_turn>|<end_of_turn>|<\|turn", out): problems.append("chat markers in output: a chat template is being applied")
|
| 187 |
+
grams = lambda t: {tuple(t.lower().split()[i:i + 5]) for i in range(len(t.split()) - 4)}
|
| 188 |
+
if len(grams(DRAFT) & grams(out)) / max(1, len(grams(out))) > 0.5: problems.append("output copies the draft")
|
| 189 |
+
missing = [n for n in re.findall(r"\d+", DRAFT) if n not in re.findall(r"\d+", out)]
|
| 190 |
+
|
| 191 |
+
print("FAIL: " + "; ".join(problems) if problems else "PASS: install looks right")
|
| 192 |
+
if missing: print("NOTE: numbers not found in this sample:", missing,
|
| 193 |
+
"(the model can slip; rerun once. If it repeats, check sampling settings)")
|
| 194 |
+
```
|
| 195 |
+
|
| 196 |
+
`PASS` means the server, the file and the prompt are wired correctly. A `NOTE` about a number is a sampling slip, not an install problem, unless it happens every time.
|
| 197 |
+
|
| 198 |
+
## 9. Rules when you use it for a user
|
| 199 |
+
|
| 200 |
+
- Send **one draft per request**. Our setup uses an 8192-token context, so the draft plus the rewrite must fit. For long or structured documents, use `hz` ([section 12](#12-long-and-complex-documents-use-hz)); it splits them for you. For a folder of `.txt` files, [USAGE.md section 9](https://github.com/sgaofen/humanizer-local-model/blob/main/docs/USAGE.md#9-rewrite-a-whole-folder) has a batch script.
|
| 201 |
+
- **Never edit the output silently**, and never tell the user the result is guaranteed to pass an AI detector. Detection numbers in the README are one measurement on one date.
|
| 202 |
+
- **Ask the user to proofread** numbers, dates, names and the direction of each claim. On the evaluation set a strict judge found no factual problem in 376 of 420 English rewrites; where it found one, more than 9 in 10 fixes are a single word or phrase (for example "37 complaints" became "37% of complaints"). Compare the numbers in the draft and the output yourself and point out any that differ.
|
| 203 |
+
- If an output copies most of the draft, or a number differs, **sample again** (same prompt; sampling is random).
|
| 204 |
+
- Chinese is still catching up with English (no factual problem in 149 of 204 Chinese rewrites; about 9 in 10 fixes are a single word or phrase).
|
| 205 |
+
- Formatting can change: paragraph breaks, lists and headings are sometimes dropped or merged.
|
| 206 |
+
|
| 207 |
+
## 10. Other runtimes
|
| 208 |
+
|
| 209 |
+
All of them need the same prompt (step 6) and the same sampling (step 7). Complete code for each is in [USAGE.md](https://github.com/sgaofen/humanizer-local-model/blob/main/docs/USAGE.md).
|
| 210 |
+
|
| 211 |
+
- **MLX (Apple silicon):** `mlx_lm.convert --hf-path jialinyyzz/humanizer --mlx-path humanizer-mlx-8bit -q --q-bits 8 --q-group-size 64`, then `mlx_lm.generate`'s Python API with `make_sampler(temp=1.0, top_p=0.95)`. Pass the prompt string directly; don't apply a chat template.
|
| 212 |
+
- **transformers (CUDA):** `AutoModelForCausalLM.from_pretrained("jialinyyzz/humanizer", dtype=torch.bfloat16)`, `generate(do_sample=True, temperature=1.0, top_p=0.95, top_k=0)`. `top_k=0` overrides the top-k 64 in the bundled `generation_config.json`. The bf16 `model.safetensors` (about 24 GB) is at the repo root.
|
| 213 |
+
- **vLLM:** `LLM(model="jialinyyzz/humanizer", dtype="bfloat16", max_model_len=8192, limit_mm_per_prompt={"image": 0, "audio": 0, "video": 0})` and `SamplingParams(temperature=1.0, top_p=0.95, top_k=-1, min_p=0.0, repetition_penalty=1.0, max_tokens=2048)`. For `vllm serve`, add `--generation-config vllm` and use `/v1/completions`.
|
| 214 |
+
- **Ollama:** it ignores the GGUF's chat template. Use the Modelfile in [USAGE.md section 7](https://github.com/sgaofen/humanizer-local-model/blob/main/docs/USAGE.md#7-ollama), whose `TEMPLATE` builds the step 6 prompt from the last user message, or call `/api/generate` with `"raw": true` and the full prompt. Untested by the authors.
|
| 215 |
+
- **LM Studio:** the chat template in the GGUF (files uploaded on or after 2026-10-04) builds the step 6 prompt, so the Chat tab and `/v1/chat/completions` should work with one draft per message and an empty system prompt. The text-completion endpoint `/v1/completions` with the full prompt works with any download. Untested by the authors.
|
| 216 |
+
|
| 217 |
+
## 11. Troubleshooting
|
| 218 |
+
|
| 219 |
+
| Symptom | Cause | Fix |
|
| 220 |
+
|---|---|---|
|
| 221 |
+
| Output starts with "Sure", "Here is", repeats the instruction, or doesn't stop | A generic chat template is in use (a GGUF downloaded before 2026-10-04 has no built-in one) | Use `/completion` (or raw mode) and send the prompt from step 6 |
|
| 222 |
+
| Output is cut off at `###` | A stop string was set | Remove all stop strings; rely on EOS |
|
| 223 |
+
| Output is almost the same as the draft | Sampling bad luck, or temperature too low | Check temperature 1.0 and sample again |
|
| 224 |
+
| Rambling or repeated phrases | Wrong sampler settings | Set `top_k: 0, min_p: 0, repeat_penalty: 1.0` explicitly |
|
| 225 |
+
| Out of memory while loading | Not enough RAM or VRAM for Q8_0 | Use Q6_K (16 GB), Q4_K_M (about 14 GB), Q3-QAT (12 GB) or IQ2_XS-QAT (8 GB); lower `-ngl` |
|
| 226 |
+
| Very slow | Running on the CPU | Check `offloaded N/N layers` in the log; install the Metal, CUDA or Vulkan build |
|
| 227 |
+
| 404 when downloading a file | Wrong file name | Use the file names in step 2 |
|
| 228 |
+
|
| 229 |
+
## 12. Long and complex documents: use `hz`
|
| 230 |
+
|
| 231 |
+
For a document longer than a few paragraphs, or one with structure (Markdown headings, lists, code blocks, tables; `.docx`), call `hz` instead of building requests yourself. It keeps the structure, rewrites the prose in pieces, uses the exact prompt and sampling above, checks every piece and retries once when a check fails. Full reference: [USAGE.md, section 14](https://github.com/sgaofen/humanizer-local-model/blob/main/docs/USAGE.md#14-hz-command-line-tool).
|
| 232 |
+
|
| 233 |
+
**Install** (Python 3.8 or newer, no dependencies):
|
| 234 |
+
|
| 235 |
+
```bash
|
| 236 |
+
pipx install git+https://github.com/sgaofen/humanizer-local-model
|
| 237 |
+
pipx inject humanize-model python-docx # only for .docx
|
| 238 |
+
# without pipx: pip install git+https://github.com/sgaofen/humanizer-local-model
|
| 239 |
+
# pip install "humanize-model[docx] @ git+https://github.com/sgaofen/humanizer-local-model"
|
| 240 |
+
hz --version # must print: hz 0.1.0
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
**Backend.** `hz` uses the Humanizer app if it is running (or, on macOS, installed: it starts it and waits up to 3 minutes), otherwise a llama-server at `http://127.0.0.1:8080`. Pass `--server URL` for any other llama-server. If no model is available it exits with code 3 and prints setup steps; then do steps 2 to 5 (or install the app) and run it again. Don't try to work around it with a chat endpoint.
|
| 244 |
+
|
| 245 |
+
**Call:**
|
| 246 |
+
|
| 247 |
+
```bash
|
| 248 |
+
hz input.md -o output.md --json --quiet > report.json # Markdown or plain text
|
| 249 |
+
hz input.docx -o output.docx --json --quiet > report.json # .docx
|
| 250 |
+
hz draft.txt --json --quiet # no -o: the JSON has the whole result in "text"
|
| 251 |
+
hz input.md --dry-run # how it will be split; no model needed
|
| 252 |
+
```
|
| 253 |
+
|
| 254 |
+
**What is kept as is:** headings, fenced and indented code, tables, image or link-only lines, HTML, `$$` math, block quotes, front matter, a short "Key points:" line before a list/table/code, and everything under a References heading. Prose is rewritten in pieces of about 350 words (600 Chinese characters) that never cross a heading; list items keep their bullets and a leading `**Label:**`. In `.docx`, each rewritten paragraph's text goes into its first run, so bold or italic inside a paragraph is lost; paragraphs with links, images or fields are skipped.
|
| 255 |
+
|
| 256 |
+
**`--json` fields:**
|
| 257 |
+
|
| 258 |
+
| Field | Meaning |
|
| 259 |
+
|---|---|
|
| 260 |
+
| `summary.pieces`, `summary.retried`, `summary.flagged` | Pieces rewritten; how many were rewritten a second time; how many still have a problem |
|
| 261 |
+
| `summary.seconds`, `summary.copy_rate` | Total time; copy rate of all rewritten prose against its drafts |
|
| 262 |
+
| `backend.kind`, `backend.url`, `backend.model` | `app` or `server`, its address, and the model (app tier or GGUF file) |
|
| 263 |
+
| `kept` | Blocks kept as they were, by kind |
|
| 264 |
+
| `pieces[].line` (`.docx`: `paragraph`) | Where the piece starts in the input (1-based) |
|
| 265 |
+
| `pieces[].copy_rate` | Share of the rewrite's word 5-grams (Chinese: 6-character grams) found in the draft, 0 to 1 |
|
| 266 |
+
| `pieces[].missing_numbers` | Numbers in the draft not found in the rewrite (`1,250` = `1250`, `480k` = `480,000`, `31.7万` = `317,000`) |
|
| 267 |
+
| `pieces[].added_numbers` | Numbers in the rewrite not in the draft: arithmetic the model did, or an invented figure. Reported, not retried |
|
| 268 |
+
| `pieces[].missing_urls` | Links in the draft not found in the rewrite |
|
| 269 |
+
| `pieces[].retried`, `chosen`, `attempts` | Whether it was rewritten twice, which try was kept, each try's checks |
|
| 270 |
+
| `pieces[].seconds` | Model time for the piece |
|
| 271 |
+
| `pieces[].flagged`, `issues` | The kept version still has a problem: `missing_numbers`, `added_numbers`, `missing_urls`, `copy`, `too_long`, `too_short`, `repeated`, `markup`, `truncated`, `empty` |
|
| 272 |
+
| `text` | The whole result, only without `-o` and for text input |
|
| 273 |
+
|
| 274 |
+
Exit codes: 0 done (also when pieces are flagged), 1 error during the run (nothing written), 2 bad input or arguments, 3 no model found.
|
| 275 |
+
|
| 276 |
+
**After the run:**
|
| 277 |
+
|
| 278 |
+
- If `summary.flagged` is above 0, show the user each flagged piece: its line, and the numbers that are missing or added. Compare them with the draft yourself. Don't fix the text silently.
|
| 279 |
+
- The checks cover numbers, links, copying, length, repetition and stray HTML tags only. A changed word, name or claim is not caught (in a real Chinese test, "all 48 stores" became "48 stores in the province"). Ask the user to read the result and check names, dates and the direction of each claim.
|
| 280 |
+
- Never tell the user the result will pass an AI detector.
|
LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Apache License
|
| 3 |
+
Version 2.0, January 2004
|
| 4 |
+
http://www.apache.org/licenses/
|
| 5 |
+
|
| 6 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 7 |
+
|
| 8 |
+
1. Definitions.
|
| 9 |
+
|
| 10 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 11 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 12 |
+
|
| 13 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 14 |
+
the copyright owner that is granting the License.
|
| 15 |
+
|
| 16 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 17 |
+
other entities that control, are controlled by, or are under common
|
| 18 |
+
control with that entity. For the purposes of this definition,
|
| 19 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 20 |
+
direction or management of such entity, whether by contract or
|
| 21 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 22 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 23 |
+
|
| 24 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 25 |
+
exercising permissions granted by this License.
|
| 26 |
+
|
| 27 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
+
including but not limited to software source code, documentation
|
| 29 |
+
source, and configuration files.
|
| 30 |
+
|
| 31 |
+
"Object" form shall mean any form resulting from mechanical
|
| 32 |
+
transformation or translation of a Source form, including but
|
| 33 |
+
not limited to compiled object code, generated documentation,
|
| 34 |
+
and conversions to other media types.
|
| 35 |
+
|
| 36 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
+
Object form, made available under the License, as indicated by a
|
| 38 |
+
copyright notice that is included in or attached to the work
|
| 39 |
+
(an example is provided in the Appendix below).
|
| 40 |
+
|
| 41 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
+
form, that is based on (or derived from) the Work and for which the
|
| 43 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
+
of this License, Derivative Works shall not include works that remain
|
| 46 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
+
the Work and Derivative Works thereof.
|
| 48 |
+
|
| 49 |
+
"Contribution" shall mean any work of authorship, including
|
| 50 |
+
the original version of the Work and any modifications or additions
|
| 51 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
+
means any form of electronic, verbal, or written communication sent
|
| 56 |
+
to the Licensor or its representatives, including but not limited to
|
| 57 |
+
communication on electronic mailing lists, source code control systems,
|
| 58 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
+
excluding communication that is conspicuously marked or otherwise
|
| 61 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
+
|
| 63 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
+
subsequently incorporated within the Work.
|
| 66 |
+
|
| 67 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
+
Work and such Derivative Works in Source or Object form.
|
| 73 |
+
|
| 74 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
+
(except as stated in this section) patent license to make, have made,
|
| 78 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
+
where such license applies only to those patent claims licensable
|
| 80 |
+
by such Contributor that are necessarily infringed by their
|
| 81 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
+
institute patent litigation against any entity (including a
|
| 84 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
+
or contributory patent infringement, then any patent licenses
|
| 87 |
+
granted to You under this License for that Work shall terminate
|
| 88 |
+
as of the date such litigation is filed.
|
| 89 |
+
|
| 90 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
+
modifications, and in Source or Object form, provided that You
|
| 93 |
+
meet the following conditions:
|
| 94 |
+
|
| 95 |
+
(a) You must give any other recipients of the Work or
|
| 96 |
+
Derivative Works a copy of this License; and
|
| 97 |
+
|
| 98 |
+
(b) You must cause any modified files to carry prominent notices
|
| 99 |
+
stating that You changed the files; and
|
| 100 |
+
|
| 101 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
+
that You distribute, all copyright, patent, trademark, and
|
| 103 |
+
attribution notices from the Source form of the Work,
|
| 104 |
+
excluding those notices that do not pertain to any part of
|
| 105 |
+
the Derivative Works; and
|
| 106 |
+
|
| 107 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
+
distribution, then any Derivative Works that You distribute must
|
| 109 |
+
include a readable copy of the attribution notices contained
|
| 110 |
+
within such NOTICE file, excluding those notices that do not
|
| 111 |
+
pertain to any part of the Derivative Works, in at least one
|
| 112 |
+
of the following places: within a NOTICE text file distributed
|
| 113 |
+
as part of the Derivative Works; within the Source form or
|
| 114 |
+
documentation, if provided along with the Derivative Works; or,
|
| 115 |
+
within a display generated by the Derivative Works, if and
|
| 116 |
+
wherever such third-party notices normally appear. The contents
|
| 117 |
+
of the NOTICE file are for informational purposes only and
|
| 118 |
+
do not modify the License. You may add Your own attribution
|
| 119 |
+
notices within Derivative Works that You distribute, alongside
|
| 120 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
+
that such additional attribution notices cannot be construed
|
| 122 |
+
as modifying the License.
|
| 123 |
+
|
| 124 |
+
You may add Your own copyright statement to Your modifications and
|
| 125 |
+
may provide additional or different license terms and conditions
|
| 126 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
+
the conditions stated in this License.
|
| 130 |
+
|
| 131 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
+
this License, without any additional terms or conditions.
|
| 135 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
+
the terms of any separate license agreement you may have executed
|
| 137 |
+
with Licensor regarding such Contributions.
|
| 138 |
+
|
| 139 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
+
except as required for reasonable and customary use in describing the
|
| 142 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
+
|
| 144 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
+
implied, including, without limitation, any warranties or conditions
|
| 149 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
+
appropriateness of using or redistributing the Work and assume any
|
| 152 |
+
risks associated with Your exercise of permissions under this License.
|
| 153 |
+
|
| 154 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
+
unless required by applicable law (such as deliberate and grossly
|
| 157 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
+
liable to You for damages, including any direct, indirect, special,
|
| 159 |
+
incidental, or consequential damages of any character arising as a
|
| 160 |
+
result of this License or out of the use or inability to use the
|
| 161 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
+
other commercial damages or losses), even if such Contributor
|
| 164 |
+
has been advised of the possibility of such damages.
|
| 165 |
+
|
| 166 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
+
or other liability obligations and/or rights consistent with this
|
| 170 |
+
License. However, in accepting such obligations, You may act only
|
| 171 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
+
defend, and hold each Contributor harmless for any liability
|
| 174 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
+
of your accepting any such warranty or additional liability.
|
| 176 |
+
|
| 177 |
+
END OF TERMS AND CONDITIONS
|
| 178 |
+
|
| 179 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 180 |
+
|
| 181 |
+
To apply the Apache License to your work, attach the following
|
| 182 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 183 |
+
replaced with your own identifying information. (Don't include
|
| 184 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 185 |
+
comment syntax for the file format. We also recommend that a
|
| 186 |
+
file or class name and description of purpose be included on the
|
| 187 |
+
same "printed page" as the copyright notice for easier
|
| 188 |
+
identification within third-party archives.
|
| 189 |
+
|
| 190 |
+
Copyright [yyyy] [name of copyright owner]
|
| 191 |
+
|
| 192 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 193 |
+
you may not use this file except in compliance with the License.
|
| 194 |
+
You may obtain a copy of the License at
|
| 195 |
+
|
| 196 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 197 |
+
|
| 198 |
+
Unless required by applicable law or agreed to in writing, software
|
| 199 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 200 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 201 |
+
See the License for the specific language governing permissions and
|
| 202 |
+
limitations under the License.
|
NOTICE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
humanizer
|
| 2 |
+
Copyright 2026 Stephen Yu
|
| 3 |
+
|
| 4 |
+
This product is licensed under the Apache License, Version 2.0 (see LICENSE).
|
| 5 |
+
This applies to the code in this repository and to the released model weights.
|
| 6 |
+
|
| 7 |
+
The model weights are fine-tuned from google/gemma-4-12B
|
| 8 |
+
(https://huggingface.co/google/gemma-4-12B), which Google releases under the
|
| 9 |
+
Apache License, Version 2.0. The previous release in lite/ is fine-tuned from
|
| 10 |
+
google/gemma-4-E4B. This project is not affiliated with or endorsed by Google.
|
README.md
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
base_model: google/gemma-4-12B
|
| 4 |
+
base_model_relation: finetune
|
| 5 |
+
language:
|
| 6 |
+
- en
|
| 7 |
+
- zh
|
| 8 |
+
pipeline_tag: text-generation
|
| 9 |
+
library_name: gguf
|
| 10 |
+
tags:
|
| 11 |
+
- humanizer
|
| 12 |
+
- transformers
|
| 13 |
+
- safetensors
|
| 14 |
+
- text-rewriting
|
| 15 |
+
- rewriting
|
| 16 |
+
- paraphrase
|
| 17 |
+
- style-transfer
|
| 18 |
+
- gguf
|
| 19 |
+
- llama.cpp
|
| 20 |
+
- gemma4
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
<img src="assets/banner-en.png" alt="humanizer: rewrites AI drafts so they read like a person wrote them" width="100%">
|
| 24 |
+
|
| 25 |
+
<img src="assets/app-showcase-en.png" alt="The humanizer app running the 12B model locally: draft on the left, rewrite on the right, new wording highlighted" width="100%">
|
| 26 |
+
|
| 27 |
+
# humanizer
|
| 28 |
+
|
| 29 |
+
**A 12B model that rewrites AI-written drafts (emails, essays, reports, forum posts; English and Chinese) so they read like a person wrote them.** It is trained to keep every number, unit, date, name and quote and to add nothing, and it runs locally.
|
| 30 |
+
|
| 31 |
+
**Quick start:** get the app for **[Mac (Apple silicon)](https://github.com/sgaofen/humanizer-local-model/releases/download/app-v0.3.2/Humanizer-0.3.2-macos-arm64.dmg)** or **[Windows](https://github.com/sgaofen/humanizer-local-model/releases/download/app-v0.3.2/Humanizer-0.3.2-windows-x64-setup.exe)**, open it and paste a draft; it works offline. Without the app: [llama.cpp and other runtimes](#usage-without-the-app), or [AGENTS.md](AGENTS.md) for coding agents.
|
| 32 |
+
|
| 33 |
+
## Before and after
|
| 34 |
+
|
| 35 |
+
Drafts from the held-out evaluation set. For each we generated 8 samples with the Q8_0 file (llama.cpp, the app's settings) and picked the one that reads best among those the fact judge passed; it is shown unedited, and all 8 samples are [on GitHub](https://github.com/sgaofen/humanizer-local-model/blob/main/eval/outputs/examples-12b-Q8_0_x8.json). These are picks: across the whole set the judge flagged 44 of the 420 English rewrites, 135 problems in all, 125 of them a single word or phrase. Read the result before you send it, especially numbers, dates and names. Results over the whole set are below.
|
| 36 |
+
|
| 37 |
+
<img src="assets/compare-en-email.png" alt="Work email: draft and rewrite" width="100%">
|
| 38 |
+
|
| 39 |
+
<img src="assets/compare-zh-email.png" alt="Chinese work email: draft and rewrite" width="100%">
|
| 40 |
+
|
| 41 |
+
More examples (a forum answer, a Zhihu answer) are in the [GitHub README](https://github.com/sgaofen/humanizer-local-model#before-and-after).
|
| 42 |
+
|
| 43 |
+
## Results
|
| 44 |
+
|
| 45 |
+
<img src="assets/eval-en.png" alt="Evaluation card: 95% of English rewrites judged human by Originality.ai at its strictest setting (11 of 210 flagged; previous release 26); 376 of 420 English rewrites with no factual problem (previous release 369)" width="100%">
|
| 46 |
+
|
| 47 |
+
**95% judged human by Originality.ai** at its strictest setting (210 English drafts, bf16 weights, 2026-10-02): 11 of 210 rewrites were flagged as AI, against 26 for the previous release, and no AI detector was used anywhere in training. **376 of 420 English rewrites came back with no factual problem** from a strict LLM judge, measured on the `humanizer-12b-Q8_0.gguf` file you download; where it did find one, more than 9 in 10 fixes are a single word or phrase. Method, every genre and the Chinese results: [Evaluation details](#evaluation-details).
|
| 48 |
+
|
| 49 |
+
## Quantization: smaller files, closer to the full model
|
| 50 |
+
|
| 51 |
+
<img src="assets/quant-top1-en.png" alt="Top-1 agreement with bf16 against file size: our GGUF files (green) sit above standard llama.cpp quantization (gray) at 2-bit, Q3 and Q4_K_M; at 3.9 GB, 87% against 70%" width="100%">
|
| 52 |
+
|
| 53 |
+
Most GGUF files come from one `llama-quantize` pass that rounds every weight to the target type; our 2-bit, Q3 and Q4_K_M files go further. In the 2-bit and Q3 files, we measured how much each weight tensor hurts the model when it is squeezed and gave the sensitive tensors more bits and the robust ones fewer. All three were then trained layer by layer to reproduce the full model (quantization-aware training) and distilled from the full bf16 model, used as the teacher, on English and Chinese rewriting data. For the 2-bit file, a standard build of the same size picks the same next token as bf16 70% of the time; these steps take it to 81%, 85% and then 87%. The result is still an ordinary GGUF that any recent llama.cpp runs; Q6_K and Q8_0 are standard builds, because at those sizes there is almost nothing left to recover.
|
| 54 |
+
|
| 55 |
+
<sub>Numbers behind every point, KL, per-file fact checks and how each file was made: [docs/QUANTIZATION.md](https://github.com/sgaofen/humanizer-local-model/blob/main/docs/QUANTIZATION.md). All GGUF files also have their own repo: [jialinyyzz/humanizer-GGUF](https://huggingface.co/jialinyyzz/humanizer-GGUF).</sub>
|
| 56 |
+
|
| 57 |
+
**[Usage without the app](USAGE.md)** · [不用 App 怎么用](USAGE.zh.md) · [AGENTS.md](AGENTS.md) · [GitHub](https://github.com/sgaofen/humanizer-local-model) · [GGUF files](https://huggingface.co/jialinyyzz/humanizer-GGUF) · [Install guide](https://github.com/sgaofen/humanizer-local-model/blob/main/docs/INSTALL.md) · [中文说明](https://github.com/sgaofen/humanizer-local-model/blob/main/README.zh.md)
|
| 58 |
+
|
| 59 |
+
> **Setting this up with an AI agent?** Point it at [AGENTS.md](AGENTS.md): exact files, server command, prompt byte for byte, and a self-test.
|
| 60 |
+
|
| 61 |
+
## Quick start
|
| 62 |
+
|
| 63 |
+
**App:** download the `.dmg` (Mac with Apple silicon) or the Windows installer from [Releases](https://github.com/sgaofen/humanizer-local-model/releases/latest) (current: [app 0.3.2](https://github.com/sgaofen/humanizer-local-model/releases/tag/app-v0.3.2), which can import `.docx` and PDF files and keeps any passage you mark with **Create fact** word for word). On first run it suggests one of five sizes for your memory (Q8_0, Q6_K, Q4_K_M, Q3 or 2-bit, down to 8 GB machines; app 0.3.0 and later) and downloads it once; after that it works offline. Sizes and quality notes: [Files](#files).
|
| 64 |
+
|
| 65 |
+
**Command line, for long documents and agents:** `pipx install git+https://github.com/sgaofen/humanizer-local-model`, then `hz paper.md -o paper.out.md` (also `.txt` and `.docx`). It uses the app or a llama-server, keeps headings, code, tables and links, rewrites the prose piece by piece and flags any piece where a number went missing. See [USAGE.md, section 14](USAGE.md#14-hz-command-line-tool).
|
| 66 |
+
|
| 67 |
+
**Without the app:** pick a file below, then follow [Usage without the app](#usage-without-the-app). The full guide, with a batch script, long documents, Chinese and troubleshooting, is **[USAGE.md](USAGE.md)** ([中文](USAGE.zh.md)).
|
| 68 |
+
|
| 69 |
+
## Files
|
| 70 |
+
|
| 71 |
+
| File | Size | Peak memory (Mac) ¹ | Top-1 vs. bf16 ² | Fact check: English rewrites flagged ³ | Flagged as AI, same 60 drafts ⁴ | For |
|
| 72 |
+
|---|---|---|---|---|---|---|
|
| 73 |
+
| `humanizer-12b-Q8_0.gguf` | 12,669,630,368 bytes (12.7 GB) | 13.7 GB | 98.4% | 44 / 420 | 7 / 60 | 32 GB of memory or more. **Recommended**, best quality. |
|
| 74 |
+
| `humanizer-12b-Q6_K.gguf` | 10,029,799,584 bytes (10.0 GB) | about 11.0 GB | 97.7% | 56 / 420 | not measured | 16 GB. No measurable loss. |
|
| 75 |
+
| `humanizer-12b-Q4_K_M.gguf` | 7,625,160,864 bytes (7.6 GB) | 10.0 GB | 95.2% | 58 / 420 | not measured | About 14 GB, or when disk is tight. Slight loss; quantization-aware trained. |
|
| 76 |
+
| `humanizer-12b-Q3-QAT.gguf` | 5,587,794,816 bytes (5.6 GB) | 8.0 GB | 93.1% | 64 / 420 | 4 / 60 | 12 GB. Small loss; a few more fact slips in English. |
|
| 77 |
+
| `humanizer-12b-IQ2_XS-QAT.gguf` | 3,893,632,896 bytes (3.9 GB) | 6.2 GB | 87.4% | 70 / 420 | **0 / 60** | 8 GB; the smallest (2-bit). Lowest AI-detector score, a few more fact slips: check numbers and names. |
|
| 78 |
+
| `humanizer-12b-bf16.gguf` | 23,832,049,568 bytes (23.8 GB) | about 24.8 GB | 100% (reference) | 52 / 420 | 4 / 60 | Unquantised weights as one GGUF, for reference or for quantising yourself. |
|
| 79 |
+
| `model.safetensors` + `config.json`, `generation_config.json`, `tokenizer.json`, `tokenizer_config.json` | about 24 GB (bf16) | | | | | transformers, vLLM, converting to MLX (see [Usage without the app](#usage-without-the-app)). |
|
| 80 |
+
| `prompt_format.json` | tiny | | | | | The instruction and separator, verbatim. |
|
| 81 |
+
|
| 82 |
+
¹ `llama-server` (Metal) on an M5 Max with the app's settings, while rewriting; Q6_K and bf16 are estimates. Leave room for the system (the app keeps about 4 GB free).
|
| 83 |
+
|
| 84 |
+
² How often the file's most likely next token is the same as bf16's, English and Chinese averaged (llama.cpp `--kl-divergence`).
|
| 85 |
+
|
| 86 |
+
³ The strict fact judge from [Evaluation details](#evaluation-details) (GLM-5.3, one vote per rewrite), all 420 English rewrites, run on each file. For every file about 9 in 10 of the problems it lists are a single word, number or phrase. Q8_0, Q6_K and Q4_K_M are within noise of bf16; Q3 and 2-bit make a few more slips.
|
| 87 |
+
|
| 88 |
+
⁴ Originality.ai, strictest setting, the same 60 English drafts for every file. Detectors change; this is one measurement on one date.
|
| 89 |
+
|
| 90 |
+
### Quantized versions
|
| 91 |
+
|
| 92 |
+
The same GGUF files are also in **[jialinyyzz/humanizer-GGUF](https://huggingface.co/jialinyyzz/humanizer-GGUF)**; they stay here too, so existing download commands keep working. KL to bf16, the fact check and AI-detector results for every file, the full comparison with standard llama.cpp quantization and how each file was made: **[docs/QUANTIZATION.md](https://github.com/sgaofen/humanizer-local-model/blob/main/docs/QUANTIZATION.md)**. sha256 checksums are in [USAGE.md](USAGE.md#2-pick-a-file).
|
| 93 |
+
|
| 94 |
+
## Prompt format
|
| 95 |
+
|
| 96 |
+
**Text completion, not chat.** No system prompt, no turn markers. Send exactly this text and let the model continue:
|
| 97 |
+
|
| 98 |
+
```
|
| 99 |
+
Rewrite the text below so it reads like a person wrote it, not a language model.
|
| 100 |
+
|
| 101 |
+
Reorganize it as you see fit. Vary sentence length on purpose. Cut hedging,
|
| 102 |
+
throat-clearing, and any sentence that only announces what comes next.
|
| 103 |
+
Prefer the concrete word over the abstract one. It is fine to sound uneven.
|
| 104 |
+
|
| 105 |
+
Every fact, number, unit, date, name and quotation must survive unchanged.
|
| 106 |
+
|
| 107 |
+
<YOUR DRAFT, with leading and trailing whitespace removed>
|
| 108 |
+
|
| 109 |
+
### Rewritten:
|
| 110 |
+
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
- `prompt = instr + "\n\n" + draft.strip() + "\n\n### Rewritten:\n\n"`; both strings are in `prompt_format.json`. Reproduce it byte for byte: the first 16 hex characters of `sha256(prompt for the draft "X")` must be `cc51d66b4c593fbe`.
|
| 114 |
+
- Use the same English instruction for Chinese drafts.
|
| 115 |
+
- **Stop on EOS only.** No stop strings, especially not `"###"`.
|
| 116 |
+
- **Sampling: temperature 1.0, top-p 0.95, nothing else** (top-k 0, min-p 0, repetition penalty 1.0). llama.cpp defaults to top-k 40 and min-p 0.05, and the bundled `generation_config.json` sets top-k 64, so switch them off explicitly.
|
| 117 |
+
- Context 8192 tokens for instruction + draft + rewrite. Split long documents at paragraph breaks ([USAGE.md](USAGE.md#10-long-documents)).
|
| 118 |
+
- **Built-in defaults:** since 2026-10-04 every GGUF file also stores these sampling settings in its metadata, so llama.cpp and apps built on it use them when a request sets none.
|
| 119 |
+
- **Chat front ends:** since 2026-10-04 the GGUF files carry a chat template that builds exactly this prompt from the last user message (system prompts and earlier turns are ignored). llama-server's `/v1/chat/completions` (with `--jinja`, the default in recent builds) then works, one draft per message: with the same seed it gave the same rewrite as the completion endpoint. Chat apps that use the file's template, such as LM Studio's Chat tab, should work the same way (not tested by us). Files downloaded earlier have no template, and the safetensors weights have none either.
|
| 120 |
+
|
| 121 |
+
## Usage without the app
|
| 122 |
+
|
| 123 |
+
All snippets below build the prompt from `prompt_format.json` and use the sampling above. More detail for each runtime, a script that rewrites a whole folder, and a troubleshooting table: [USAGE.md](USAGE.md).
|
| 124 |
+
|
| 125 |
+
### llama.cpp (recommended; macOS, Windows, Linux)
|
| 126 |
+
|
| 127 |
+
```bash
|
| 128 |
+
brew install llama.cpp # or: winget install llama.cpp / a zip from github.com/ggml-org/llama.cpp/releases
|
| 129 |
+
pip install -U "huggingface_hub[cli]"
|
| 130 |
+
hf download jialinyyzz/humanizer humanizer-12b-Q8_0.gguf prompt_format.json --local-dir ./humanizer-model
|
| 131 |
+
# 16 GB machine: humanizer-12b-Q6_K.gguf instead; tight: humanizer-12b-Q4_K_M.gguf or humanizer-12b-Q3-QAT.gguf; smallest: humanizer-12b-IQ2_XS-QAT.gguf
|
| 132 |
+
llama-server -m ./humanizer-model/humanizer-12b-Q8_0.gguf -c 8192 -np 1 -ngl 99 --host 127.0.0.1 --port 8080
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
```python
|
| 136 |
+
import json, urllib.request
|
| 137 |
+
|
| 138 |
+
PF = json.load(open("humanizer-model/prompt_format.json", encoding="utf-8"))
|
| 139 |
+
|
| 140 |
+
def humanize(draft: str, url: str = "http://127.0.0.1:8080") -> str:
|
| 141 |
+
body = {"prompt": PF["instr"] + "\n\n" + draft.strip() + PF["sep"],
|
| 142 |
+
"temperature": 1.0, "top_p": 0.95, "top_k": 0, "min_p": 0, "repeat_penalty": 1.0,
|
| 143 |
+
"n_predict": 2048} # no "stop": the model ends at EOS
|
| 144 |
+
req = urllib.request.Request(url + "/completion", json.dumps(body).encode("utf-8"),
|
| 145 |
+
{"Content-Type": "application/json"})
|
| 146 |
+
with urllib.request.urlopen(req, timeout=900) as r:
|
| 147 |
+
return json.load(r)["content"].strip()
|
| 148 |
+
|
| 149 |
+
print(humanize(open("draft.txt", encoding="utf-8").read()))
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
`/completion` works with every download. `/v1/chat/completions` also works with the GGUF files uploaded on or after 2026-10-04 (built-in chat template, see above), one draft per request. With curl and jq:
|
| 153 |
+
|
| 154 |
+
```bash
|
| 155 |
+
jq -n --rawfile d draft.txt --slurpfile f humanizer-model/prompt_format.json \
|
| 156 |
+
'{prompt: ($f[0].instr + "\n\n" + ($d | sub("^\\s+"; "") | sub("\\s+$"; "")) + $f[0].sep),
|
| 157 |
+
temperature: 1.0, top_p: 0.95, top_k: 0, min_p: 0, repeat_penalty: 1.0, n_predict: 2048}' \
|
| 158 |
+
| curl -s http://127.0.0.1:8080/completion -d @- | jq -r .content
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
### MLX (Apple silicon, mlx-lm 0.32 or newer)
|
| 162 |
+
|
| 163 |
+
```bash
|
| 164 |
+
pip install -U "mlx-lm>=0.32" huggingface_hub
|
| 165 |
+
mlx_lm.convert --hf-path jialinyyzz/humanizer --mlx-path humanizer-mlx-8bit -q --q-bits 8 --q-group-size 64
|
| 166 |
+
```
|
| 167 |
+
|
| 168 |
+
```python
|
| 169 |
+
import json
|
| 170 |
+
from huggingface_hub import hf_hub_download
|
| 171 |
+
from mlx_lm import load, generate
|
| 172 |
+
from mlx_lm.sample_utils import make_sampler
|
| 173 |
+
|
| 174 |
+
PF = json.load(open(hf_hub_download("jialinyyzz/humanizer", "prompt_format.json"), encoding="utf-8"))
|
| 175 |
+
model, tok = load("humanizer-mlx-8bit")
|
| 176 |
+
draft = open("draft.txt", encoding="utf-8").read()
|
| 177 |
+
print(generate(model, tok, prompt=PF["instr"] + "\n\n" + draft.strip() + PF["sep"], max_tokens=2048,
|
| 178 |
+
sampler=make_sampler(temp=1.0, top_p=0.95)).strip())
|
| 179 |
+
```
|
| 180 |
+
|
| 181 |
+
Pass a plain string; never apply the chat template (the `mlx_lm.generate` CLI needs `--ignore-chat-template`).
|
| 182 |
+
|
| 183 |
+
### transformers (CUDA)
|
| 184 |
+
|
| 185 |
+
```python
|
| 186 |
+
import json, torch
|
| 187 |
+
from huggingface_hub import hf_hub_download
|
| 188 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 189 |
+
|
| 190 |
+
repo = "jialinyyzz/humanizer"
|
| 191 |
+
PF = json.load(open(hf_hub_download(repo, "prompt_format.json"), encoding="utf-8"))
|
| 192 |
+
tok = AutoTokenizer.from_pretrained(repo)
|
| 193 |
+
model = AutoModelForCausalLM.from_pretrained(repo, dtype=torch.bfloat16, device_map="auto")
|
| 194 |
+
|
| 195 |
+
draft = open("draft.txt", encoding="utf-8").read()
|
| 196 |
+
ids = tok(PF["instr"] + "\n\n" + draft.strip() + PF["sep"], return_tensors="pt").to(model.device)
|
| 197 |
+
out = model.generate(**ids, do_sample=True, temperature=1.0, top_p=0.95,
|
| 198 |
+
top_k=0, # switches off the top-k 64 in generation_config.json
|
| 199 |
+
max_new_tokens=2048)
|
| 200 |
+
print(tok.decode(out[0, ids["input_ids"].shape[1]:], skip_special_tokens=True).strip())
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
The bf16 weights are about 24 GB. Saved with transformers 5.14.1; on 4.x write `torch_dtype=` instead of `dtype=`.
|
| 204 |
+
|
| 205 |
+
### vLLM
|
| 206 |
+
|
| 207 |
+
```python
|
| 208 |
+
import json
|
| 209 |
+
from huggingface_hub import hf_hub_download
|
| 210 |
+
from vllm import LLM, SamplingParams
|
| 211 |
+
|
| 212 |
+
repo = "jialinyyzz/humanizer"
|
| 213 |
+
PF = json.load(open(hf_hub_download(repo, "prompt_format.json"), encoding="utf-8"))
|
| 214 |
+
llm = LLM(model=repo, dtype="bfloat16", max_model_len=8192,
|
| 215 |
+
limit_mm_per_prompt={"image": 0, "audio": 0, "video": 0}) # text only
|
| 216 |
+
params = SamplingParams(temperature=1.0, top_p=0.95, top_k=-1, min_p=0.0,
|
| 217 |
+
repetition_penalty=1.0, max_tokens=2048) # top_k=-1: off
|
| 218 |
+
|
| 219 |
+
drafts = [open(p, encoding="utf-8").read() for p in ["draft1.txt", "draft2.txt"]]
|
| 220 |
+
for r in llm.generate([PF["instr"] + "\n\n" + d.strip() + PF["sep"] for d in drafts], params):
|
| 221 |
+
print(r.outputs[0].text.strip(), "\n---")
|
| 222 |
+
```
|
| 223 |
+
|
| 224 |
+
This mirrors how our evaluation outputs were generated. As a server: `vllm serve jialinyyzz/humanizer --dtype bfloat16 --max-model-len 8192 --generation-config vllm`, then `/v1/completions` (never `/v1/chat/completions`) with the same parameters; `--generation-config vllm` keeps the top-k 64 from `generation_config.json` out of the defaults.
|
| 225 |
+
|
| 226 |
+
### Ollama
|
| 227 |
+
|
| 228 |
+
Ollama ignores the chat template stored in the GGUF and would apply its own Gemma template, which breaks this model. Use this `Modelfile`: its template takes the last user message as the draft and builds the prompt above (rendered with Go's `text/template`, byte for byte the same; not run in Ollama by us). Needs an Ollama version that supports Gemma 4 models.
|
| 229 |
+
|
| 230 |
+
```
|
| 231 |
+
FROM ./humanizer-12b-Q8_0.gguf
|
| 232 |
+
TEMPLATE """{{- $draft := "" }}{{- range .Messages }}{{- if eq .Role "user" }}{{- $draft = .Content }}{{- end }}{{- end }}Rewrite the text below so it reads like a person wrote it, not a language model.
|
| 233 |
+
|
| 234 |
+
Reorganize it as you see fit. Vary sentence length on purpose. Cut hedging,
|
| 235 |
+
throat-clearing, and any sentence that only announces what comes next.
|
| 236 |
+
Prefer the concrete word over the abstract one. It is fine to sound uneven.
|
| 237 |
+
|
| 238 |
+
Every fact, number, unit, date, name and quotation must survive unchanged.
|
| 239 |
+
|
| 240 |
+
{{ $draft }}
|
| 241 |
+
|
| 242 |
+
### Rewritten:{{ "\n\n" }}"""
|
| 243 |
+
PARAMETER temperature 1.0
|
| 244 |
+
PARAMETER top_p 0.95
|
| 245 |
+
PARAMETER top_k 0
|
| 246 |
+
PARAMETER min_p 0
|
| 247 |
+
PARAMETER repeat_penalty 1.0
|
| 248 |
+
PARAMETER num_ctx 8192
|
| 249 |
+
PARAMETER num_predict 2048
|
| 250 |
+
```
|
| 251 |
+
|
| 252 |
+
```bash
|
| 253 |
+
ollama create humanizer -f Modelfile
|
| 254 |
+
ollama run humanizer # one draft per message, no leading or trailing blank lines
|
| 255 |
+
# or raw mode, which skips the template:
|
| 256 |
+
jq -n --rawfile d draft.txt --slurpfile f humanizer-model/prompt_format.json \
|
| 257 |
+
'{model: "humanizer", raw: true, stream: false,
|
| 258 |
+
prompt: ($f[0].instr + "\n\n" + ($d | sub("^\\s+"; "") | sub("\\s+$"; "")) + $f[0].sep),
|
| 259 |
+
options: {temperature: 1.0, top_p: 0.95, top_k: 0, min_p: 0, repeat_penalty: 1.0,
|
| 260 |
+
num_ctx: 8192, num_predict: 2048}}' \
|
| 261 |
+
| curl -s http://127.0.0.1:11434/api/generate -d @- | jq -r .response
|
| 262 |
+
```
|
| 263 |
+
|
| 264 |
+
### LM Studio
|
| 265 |
+
|
| 266 |
+
Untested by us. Load the GGUF with an 8192-token context; in the model's sampling settings set Temperature 1.0, Top P 0.95, Top K 0, Min P 0, Repeat Penalty 1.0 and remove stop strings. With a file downloaded on or after 2026-10-04, the **Chat tab** should work: leave the system prompt empty and paste one draft per message (the built-in template ignores system prompts and earlier turns). With any download, start the local server and send the full prompt to the **text-completion endpoint `/v1/completions`**:
|
| 267 |
+
|
| 268 |
+
```python
|
| 269 |
+
import json, urllib.request
|
| 270 |
+
|
| 271 |
+
PF = json.load(open("humanizer-model/prompt_format.json", encoding="utf-8"))
|
| 272 |
+
draft = open("draft.txt", encoding="utf-8").read()
|
| 273 |
+
body = {"model": "humanizer-12b-q8_0", # replace with the model id shown in LM Studio
|
| 274 |
+
"prompt": PF["instr"] + "\n\n" + draft.strip() + PF["sep"],
|
| 275 |
+
"temperature": 1.0, "top_p": 0.95, "top_k": 0, "min_p": 0, "repeat_penalty": 1.0, "max_tokens": 2048}
|
| 276 |
+
req = urllib.request.Request("http://127.0.0.1:1234/v1/completions", json.dumps(body).encode("utf-8"),
|
| 277 |
+
{"Content-Type": "application/json"})
|
| 278 |
+
print(json.load(urllib.request.urlopen(req, timeout=900))["choices"][0]["text"].strip())
|
| 279 |
+
```
|
| 280 |
+
|
| 281 |
+
If a chat reply greets you, repeats the instruction or doesn't stop, the file has no built-in template (downloaded before 2026-10-04): download it again or use `/v1/completions`.
|
| 282 |
+
|
| 283 |
+
### More
|
| 284 |
+
|
| 285 |
+
[USAGE.md](USAGE.md) also covers: a one-shot `llama-completion` run, a script that rewrites a whole folder (splitting long files, resampling over-copied pieces, listing numbers to check), long documents, Chinese specifics, a quality checklist and troubleshooting. To verify a setup, run the self-test in [AGENTS.md](AGENTS.md#8-self-test-verify-the-install).
|
| 286 |
+
|
| 287 |
+
## Evaluation details
|
| 288 |
+
|
| 289 |
+
Evaluation set: 312 drafts (210 English, 102 Chinese), 18 genres, written from scratch by GLM-5.3, GPT-5.6 Luna and Claude Sonnet (about a third each), never used in training. Two samples per draft.
|
| 290 |
+
|
| 291 |
+
**AI detection (external check only): 95% judged human.** Originality.ai, API v3, AI Allowance 0% (strictest), 2026-10-02, 210 English drafts, first sample each, bf16 weights: 11 of 210 rewrites flagged as AI.
|
| 292 |
+
|
| 293 |
+
| Model | Flagged as AI | Judged human |
|
| 294 |
+
|---|---|---|
|
| 295 |
+
| **humanizer 12B v2, this release (bf16)** | **11 / 210 (5%)** | **95%** |
|
| 296 |
+
| humanizer 12B v1, previous release | 26 / 210 (12%) | 88% |
|
| 297 |
+
|
| 298 |
+
Flagged less than half as often as the previous release: on the same drafts, 20 were flagged only for the previous release and 5 only for this one (paired test, p = 0.004). The `humanizer-12b-Q8_0.gguf` file measured 15 / 210 (7%), within noise of bf16 (p = 0.48).
|
| 299 |
+
|
| 300 |
+
Public baseline: the `blader/humanizer` skill (v3.1.0, 53k GitHub stars), applied by Claude Sonnet to the same 60 drafts: 60 / 60 flagged as AI (median AI score 100%). This release (bf16) on those 60: **4 / 60** flagged. Flagged as AI by genre (all 210 drafts, bf16, same setting): social posts with emoji, hashtags or "1/ 2/" threads **3 / 16**, formal policy memos **2 / 13**, paper sections 2 / 22, essays 2 / 38, work reports 1 / 20, forum answers 1 / 18, blog posts 0 / 16, Reddit posts 0 / 18, emails 0 / 35, product reviews 0 / 14 (total 11 / 210). The most templated genres are still the hardest. Detectors change; this is one measurement on one date, not a promise.
|
| 301 |
+
|
| 302 |
+
**Fact fidelity.** **376 of 420 English rewrites came back with no factual problem** from a strict LLM judge (GLM-5.3, one vote per rewrite), measured on the `humanizer-12b-Q8_0.gguf` file you download. The previous release: 369 of 420. The chart is at the [top of this page](#results).
|
| 303 |
+
|
| 304 |
+
| | **v2, this release (Q8_0 file)** | v1, previous 12B |
|
| 305 |
+
|---|---|---|
|
| 306 |
+
| No factual problem found (higher is better) | **376 / 420** | 369 / 420 |
|
| 307 |
+
| Dropped a format element (lower is better) | **28 / 420** | 35 / 420 |
|
| 308 |
+
| Median reuse, overlap with the draft (lower is better) | **0.165** | 0.19 |
|
| 309 |
+
| Outputs with reuse > 0.5 (lower is better) | **0.2%** | 1.0% |
|
| 310 |
+
|
| 311 |
+
Where the judge did find a problem, the fix is usually small: a second pass re-read each flagged rewrite against its draft and listed every problem, down to small wording nuances, and more than 9 in 10 of those fixes (125 of 135) are a single word or phrase (for example, the draft's "The remaining 37 complaints" came out as "The other 37% of complaints"). Chinese is still catching up with English: no factual problem in 149 of 204 Chinese rewrites (previous release: 135); where there was one, about 9 in 10 fixes (212 of 236) are a single word or phrase ("本月20日前后", around the 20th of this month, became "20号以前", before the 20th). **Still, read the result before you send it, especially numbers, dates and names.**
|
| 312 |
+
|
| 313 |
+
**Note: the fact judge is deliberately strict.** In a separate audit of the same judge model, a second judge (Claude Opus) re-read 150 rewrites it had flagged as serious (drawn from training, not from this evaluation set). On the fact the first judge pointed to, Opus agreed it was a serious error in 99, rated it minor in 41 (for example "may reduce" became "will help decrease", or a long name was shortened), and found the fact unchanged in 10. So some flags are harmless rewording. Still read numbers, dates and names before you send.
|
| 314 |
+
|
| 315 |
+
## Training
|
| 316 |
+
|
| 317 |
+
**No AI detector was used anywhere in training**: not as a reward, not as a filter, not to pick a checkpoint.
|
| 318 |
+
|
| 319 |
+
1. **SFT, 28,598 pairs** of AI draft → real human original. The human side is always real human writing (paper abstracts, government reports, student essays, company and mailing-list email, Reddit, Hacker News, Zhihu…); the AI side is a draft a frontier model wrote back from the human text.
|
| 320 |
+
2. **DPO, 3,918 preference pairs**, chosen only on fact fidelity and copying (LLM judge GLM-5.3).
|
| 321 |
+
3. **GRPO in three rounds, 500 steps in total**: 200 steps with a strict single-vote fact judge, then two rounds of 150 steps (v1 was released after the first of these, v2 after the second) (16 drafts × 8 samples per step, temperature 1.0). Reward: an LLM judge reads the whole rewrite against the draft (severe errors, invented content, changed meaning and dropped formatting cost), plus a copy penalty on 5-gram and syntactic-skeleton reuse (free below .22, then linear). Round 3 drew its drafts from a genre-balanced pool of 8,268. In all, RL produced 41,600 rewrites, each scored by an LLM judge against its draft.
|
| 322 |
+
4. This release (v2) = the final checkpoint of the last round.
|
| 323 |
+
|
| 324 |
+
**Training data:** every row used to train v2 is in **[jialinyyzz/humanizer-data](https://huggingface.co/datasets/jialinyyzz/humanizer-data)** (one config per step; openly licensed text in full, other sources as IDs with a rebuild script). How it was built: [docs/DATA.md](https://github.com/sgaofen/humanizer-local-model/blob/main/docs/DATA.md).
|
| 325 |
+
|
| 326 |
+
<img src="assets/training-en.png" alt="Training pipeline" width="100%">
|
| 327 |
+
|
| 328 |
+
## Speed
|
| 329 |
+
|
| 330 |
+
Measured on an M5 Max. llama.cpp Q8_0 with Metal (what the app uses): about 36–38 tokens/s; a hundred-word email takes about 3.6 s, a Chinese email of about 300 characters about 8.5 s. MLX 8-bit: about 30 tokens/s in English and 38 tokens/s in Chinese; a hundred-word email takes about 9 s.
|
| 331 |
+
|
| 332 |
+
### Community: faster decoding on Apple Silicon
|
| 333 |
+
|
| 334 |
+
[jayyun98](https://huggingface.co/jayyun98) trained a speculative-decoding draft model for humanizer: [Humanizer-12B-Draftfit-DSpark](https://huggingface.co/jayyun98/Humanizer-12B-Draftfit-DSpark). On an M5 Pro with MLX it reports about 1.5× the speed (34.5 vs 22.8 tokens/s), and in their test the output was token-for-token identical to running humanizer alone. It runs alongside the main model and needs the MLX setup described in its model card. This is a community project; we have not benchmarked it ourselves.
|
| 335 |
+
|
| 336 |
+
## Limitations
|
| 337 |
+
|
| 338 |
+
- It can still change a detail: on our set the judge found no factual problem in 376 of 420 English rewrites, and more than 9 in 10 of the fixes it did list are a single word or phrase. Proofread numbers, dates and names.
|
| 339 |
+
- Chinese is still catching up with English (no factual problem in 149 of 204 Chinese rewrites; about 9 in 10 fixes are a single word or phrase).
|
| 340 |
+
- Templated genres are still the hardest for detectors: emoji/hashtag social posts (3 / 16 flagged) and policy memos (2 / 13).
|
| 341 |
+
- Formatting can change: 28 of 420 outputs dropped a format element; paragraph breaks, lists and headings sometimes merge or disappear.
|
| 342 |
+
- Occasionally answers in the wrong language: on short, informal English drafts with technical jargon, it occasionally writes the whole rewrite in Chinese. The app (0.3.1 and later) and `hz` check the language and sample again automatically. If you call the model yourself through llama.cpp or another runtime: when an English draft comes back with more than a few Chinese characters, sample once more with the same settings.
|
| 343 |
+
- In casual genres it sometimes adds slang or profanity that wasn't in the draft.
|
| 344 |
+
- Detector results change over time. Nothing here guarantees any detector outcome.
|
| 345 |
+
- It is a writing tool for your own drafts. Where a school, employer or publication has rules about AI assistance, follow them.
|
| 346 |
+
|
| 347 |
+
## License
|
| 348 |
+
|
| 349 |
+
Apache License 2.0, for both the weights and the code. Fine-tuned from [google/gemma-4-12B](https://huggingface.co/google/gemma-4-12B), which Google releases under Apache 2.0. This project is not affiliated with or endorsed by Google.
|
USAGE.md
ADDED
|
@@ -0,0 +1,732 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Using humanizer without the app
|
| 2 |
+
|
| 3 |
+
[中文](USAGE.zh.md) · [README](https://github.com/sgaofen/humanizer-local-model#readme) · [AGENTS.md (for AI agents)](https://github.com/sgaofen/humanizer-local-model/blob/main/AGENTS.md) · [Model files on Hugging Face](https://huggingface.co/jialinyyzz/humanizer/tree/main)
|
| 4 |
+
|
| 5 |
+
This guide is for people who want to run humanizer from their own code or the command line instead of the [desktop app](https://github.com/sgaofen/humanizer-local-model/releases/latest). Every block can be copied as is.
|
| 6 |
+
|
| 7 |
+
**Just want to rewrite files?** The `hz` command does the prompt, the sampling, the splitting of long documents and the checks for you, on top of the app or a llama-server: `pipx install git+https://github.com/sgaofen/humanizer-local-model`, then `hz draft.md -o out.md`. See [section 14](#14-hz-command-line-tool).
|
| 8 |
+
|
| 9 |
+
**Contents:** [1. What makes this model different](#1-what-makes-this-model-different) · [2. Pick a file](#2-pick-a-file) · [3. llama.cpp](#3-llamacpp-recommended) · [4. MLX](#4-mlx-apple-silicon) · [5. transformers](#5-transformers-cuda) · [6. vLLM](#6-vllm) · [7. Ollama](#7-ollama) · [8. LM Studio](#8-lm-studio) · [9. Rewrite a whole folder](#9-rewrite-a-whole-folder) · [10. Long documents](#10-long-documents) · [11. Chinese](#11-chinese) · [12. Quality checklist](#12-quality-checklist) · [13. Troubleshooting](#13-troubleshooting) · [14. hz command-line tool](#14-hz-command-line-tool)
|
| 10 |
+
|
| 11 |
+
## 1. What makes this model different
|
| 12 |
+
|
| 13 |
+
humanizer is a 12B **text-completion** model fine-tuned from `google/gemma-4-12B`. It takes one AI-written draft and writes the rewrite. Four rules apply to every runtime below. Get them right and it behaves as in our evaluation; get one wrong and the output gets noticeably worse.
|
| 14 |
+
|
| 15 |
+
| Rule | Why |
|
| 16 |
+
|---|---|
|
| 17 |
+
| **It is a text-completion model, not a chat model.** Send one plain string to a completion endpoint. Chat mode works only through the chat template built into the GGUF files (since 2026-10-04): it turns the last user message into exactly this string and ignores system prompts and earlier turns. | It was trained on raw text in exactly the shape below. A generic chat template (Gemma turns, ChatML) wraps the draft in tokens it never saw during training. The safetensors weights (transformers, vLLM, MLX) have no chat template. |
|
| 18 |
+
| **The prompt must match byte for byte.** | A reworded instruction, a translated instruction or a missing blank line all make it worse. |
|
| 19 |
+
| **Stop at EOS only.** No stop strings, and especially not `###`. | The model ends by itself. A few legitimate outputs contain `###` and would be cut off. |
|
| 20 |
+
| **Sampling: temperature 1.0, top-p 0.95, and nothing else.** top-k off (0), min-p off (0), repetition penalty 1.0. | That is how the evaluation was run. Several runtimes switch on other samplers by default: llama.cpp uses top-k 40 and min-p 0.05, and transformers reads top-k 64 from the bundled `generation_config.json`. |
|
| 21 |
+
|
| 22 |
+
### The prompt
|
| 23 |
+
|
| 24 |
+
```
|
| 25 |
+
prompt = INSTR + "\n\n" + draft.strip() + "\n\n### Rewritten:\n\n"
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
`INSTR` is exactly this text (lines joined with `\n`, blank lines included, no trailing newline):
|
| 29 |
+
|
| 30 |
+
```
|
| 31 |
+
Rewrite the text below so it reads like a person wrote it, not a language model.
|
| 32 |
+
|
| 33 |
+
Reorganize it as you see fit. Vary sentence length on purpose. Cut hedging,
|
| 34 |
+
throat-clearing, and any sentence that only announces what comes next.
|
| 35 |
+
Prefer the concrete word over the abstract one. It is fine to sound uneven.
|
| 36 |
+
|
| 37 |
+
Every fact, number, unit, date, name and quotation must survive unchanged.
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
- `draft.strip()` removes leading and trailing whitespace.
|
| 41 |
+
- The separator `\n\n### Rewritten:\n\n` ends with a blank line; the model starts writing right after it.
|
| 42 |
+
- Use the same English instruction for Chinese drafts. Don't translate it.
|
| 43 |
+
- Both strings ship with the weights in `prompt_format.json` (fields `instr` and `sep`).
|
| 44 |
+
|
| 45 |
+
A prompt builder you can paste anywhere, with its self-check:
|
| 46 |
+
|
| 47 |
+
```python
|
| 48 |
+
import hashlib
|
| 49 |
+
|
| 50 |
+
INSTR = (
|
| 51 |
+
"Rewrite the text below so it reads like a person wrote it, not a language model.\n"
|
| 52 |
+
"\n"
|
| 53 |
+
"Reorganize it as you see fit. Vary sentence length on purpose. Cut hedging,\n"
|
| 54 |
+
"throat-clearing, and any sentence that only announces what comes next.\n"
|
| 55 |
+
"Prefer the concrete word over the abstract one. It is fine to sound uneven.\n"
|
| 56 |
+
"\n"
|
| 57 |
+
"Every fact, number, unit, date, name and quotation must survive unchanged."
|
| 58 |
+
)
|
| 59 |
+
SEP = "\n\n### Rewritten:\n\n"
|
| 60 |
+
|
| 61 |
+
def build_prompt(draft: str) -> str:
|
| 62 |
+
return INSTR + "\n\n" + draft.strip() + SEP
|
| 63 |
+
|
| 64 |
+
# Fingerprint: if this fails, the prompt is wrong.
|
| 65 |
+
assert hashlib.sha256(build_prompt("X").encode("utf-8")).hexdigest()[:16] == "cc51d66b4c593fbe"
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
**Output length.** A rewrite is usually about as long as the draft. Allow about 2.5 times the draft's token count; the app uses `min(2048, max(256, 2.5 × draft tokens))`. The context is 8192 tokens for the instruction, the draft and the rewrite together; see [Long documents](#10-long-documents).
|
| 69 |
+
|
| 70 |
+
## 2. Pick a file
|
| 71 |
+
|
| 72 |
+
All files are in [`jialinyyzz/humanizer`](https://huggingface.co/jialinyyzz/humanizer/tree/main):
|
| 73 |
+
|
| 74 |
+
| Your memory | File | Size |
|
| 75 |
+
|---|---|---|
|
| 76 |
+
| 32 GB or more | `humanizer-12b-Q8_0.gguf` | 12,669,630,368 bytes (about 12.7 GB) |
|
| 77 |
+
| 16 GB | `humanizer-12b-Q6_K.gguf` | 10,029,799,584 bytes (about 10.0 GB) |
|
| 78 |
+
| About 14 GB, or short on disk | `humanizer-12b-Q4_K_M.gguf`, quantization-aware trained; peak memory about 10 GB | 7,625,160,864 bytes (about 7.6 GB) |
|
| 79 |
+
| 12 GB | `humanizer-12b-Q3-QAT.gguf`, 3-bit class; peak memory about 8 GB. A few more fact slips in English: check numbers and names | 5,587,794,816 bytes (about 5.6 GB) |
|
| 80 |
+
| 8 GB | `humanizer-12b-IQ2_XS-QAT.gguf`, 2-bit, the smallest; peak memory about 6.2 GB. More fact slips: check numbers and names | 3,893,632,896 bytes (about 3.9 GB) |
|
| 81 |
+
|
| 82 |
+
Also in the repo:
|
| 83 |
+
|
| 84 |
+
- `humanizer-12b-bf16.gguf` (23,832,049,568 bytes, about 23.8 GB): the unquantised 12B as one GGUF, for reference or for quantising yourself.
|
| 85 |
+
- `model.safetensors` (bf16, about 24 GB) with `config.json`, `generation_config.json`, `tokenizer.json`, `tokenizer_config.json` and `prompt_format.json` at the root: what transformers, vLLM and the MLX converter use.
|
| 86 |
+
|
| 87 |
+
**How much the quantised files differ from bf16.** How often each file picks the same most likely next token as bf16, English and Chinese averaged: Q8_0 98.4%, Q6_K 97.7%, Q4_K_M 95.2%, Q3 93.1%, 2-bit 87.4%. Q4_K_M, Q3 and 2-bit are quantization-aware trained and distilled from bf16, so they stay closer to it than standard llama.cpp files of the same size. On the strict fact judge from the README, Q8_0, Q6_K and Q4_K_M are within noise of bf16; Q3 and 2-bit make a few more fact slips in English (64 and 70 of 420 rewrites flagged, against 52 for bf16), mostly a single word or number. The comparison with standard quantization, KL, the full fact-check table and how the files were made: [docs/QUANTIZATION.md](https://github.com/sgaofen/humanizer-local-model/blob/main/docs/QUANTIZATION.md).
|
| 88 |
+
|
| 89 |
+
**Download:**
|
| 90 |
+
|
| 91 |
+
```bash
|
| 92 |
+
pip install -U "huggingface_hub[cli]"
|
| 93 |
+
hf download jialinyyzz/humanizer humanizer-12b-Q8_0.gguf prompt_format.json --local-dir ./humanizer-model
|
| 94 |
+
# 16 GB machine: humanizer-12b-Q6_K.gguf instead of humanizer-12b-Q8_0.gguf (or humanizer-12b-Q4_K_M.gguf if disk is tight)
|
| 95 |
+
# less memory: humanizer-12b-Q3-QAT.gguf, or the smallest, humanizer-12b-IQ2_XS-QAT.gguf
|
| 96 |
+
# Slow from mainland China: put HF_ENDPOINT=https://hf-mirror.com in front of the command
|
| 97 |
+
wc -c ./humanizer-model/*.gguf # Q8_0: 12669630368 bytes, Q6_K: 10029799584 bytes, Q4_K_M: 7625160864 bytes,
|
| 98 |
+
# Q3-QAT: 5587794816 bytes, IQ2_XS-QAT: 3893632896 bytes
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
sha256 (`shasum -a 256 FILE` on macOS, `sha256sum FILE` on Linux, `certutil -hashfile FILE SHA256` on Windows):
|
| 102 |
+
|
| 103 |
+
| File | sha256 |
|
| 104 |
+
|---|---|
|
| 105 |
+
| `humanizer-12b-Q8_0.gguf` | `8d7a457b56de6530eaaf0151ccfa7550a4b20dab979737e259da9c63e960e0b0` |
|
| 106 |
+
| `humanizer-12b-Q6_K.gguf` | `c98f03bb9e71456181f99b0e1d3391e07ce1afc357f9db4c33d6d379b8dd9f0d` |
|
| 107 |
+
| `humanizer-12b-Q4_K_M.gguf` | `2229574dec5178629575ee4a153dfee7d9e924ab997d0ad9d2ac622b67e44834` |
|
| 108 |
+
| `humanizer-12b-Q3-QAT.gguf` | `307bbfdf66fb22bf98aaa93fe7d54a713bf167e646073dc0cf10870b1525eb94` |
|
| 109 |
+
| `humanizer-12b-IQ2_XS-QAT.gguf` | `383e5ca8f1f48ab5f65013adbc1965fa70d1d1afa6c45d932c34b854e38edbb5` |
|
| 110 |
+
| `humanizer-12b-bf16.gguf` | `47d79b44c3e15ea2540f4edb63556f2b7e456067d7dd252a42ff103a26a51be9` |
|
| 111 |
+
|
| 112 |
+
All GGUF files were replaced on 2026-10-04: their metadata now holds a chat template (for LM Studio and other chat front ends; see [section 3](#start-a-server)) and the recommended sampling defaults (temperature 1.0, top-p 0.95, top-k off, min-p off, repetition penalty 1.0), so llama.cpp uses them when a request doesn't set its own. The weights inside are byte for byte the same; only the header changed. Copies downloaded before that have the earlier sizes and checksums, and still work through the completion endpoint with the sampling settings passed explicitly. `humanizer-12b-bf16.gguf` (23,832,049,568 bytes, about 23.8 GB) was added the same day: the unquantised weights as one GGUF, for reference or for quantising yourself. `humanizer-12b-Q3-QAT.gguf` (added 2026-10-05) carries the same template and defaults.
|
| 113 |
+
|
| 114 |
+
**Is my file the newest?** Files are sometimes re-uploaded under the same name. Compare your file's SHA-256 with the one Hugging Face reports, and download again if they differ (the app does this itself: **…** → **Check for updates**):
|
| 115 |
+
|
| 116 |
+
```bash
|
| 117 |
+
curl -sI https://huggingface.co/jialinyyzz/humanizer/resolve/main/humanizer-12b-Q8_0.gguf | grep -i '^x-linked-etag'
|
| 118 |
+
shasum -a 256 ./humanizer-model/humanizer-12b-Q8_0.gguf # Linux: sha256sum
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
## 3. llama.cpp (recommended)
|
| 122 |
+
|
| 123 |
+
Works on macOS (Metal), Windows and Linux (CUDA, Vulkan or CPU). The app itself runs llama.cpp build `b11335`; that build or a newer one is fine.
|
| 124 |
+
|
| 125 |
+
### Install
|
| 126 |
+
|
| 127 |
+
| System | Command |
|
| 128 |
+
|---|---|
|
| 129 |
+
| macOS | `brew install llama.cpp` |
|
| 130 |
+
| Windows | `winget install llama.cpp`, or a zip from [llama.cpp releases](https://github.com/ggml-org/llama.cpp/releases) (CUDA build for NVIDIA, Vulkan build for other GPUs) |
|
| 131 |
+
| Linux | a zip from [llama.cpp releases](https://github.com/ggml-org/llama.cpp/releases), or build it: `cmake -B build -DGGML_CUDA=ON && cmake --build build --config Release -j` (leave out `-DGGML_CUDA=ON` without an NVIDIA GPU) |
|
| 132 |
+
|
| 133 |
+
### Start a server
|
| 134 |
+
|
| 135 |
+
```bash
|
| 136 |
+
llama-server -m ./humanizer-model/humanizer-12b-Q8_0.gguf -c 8192 -np 1 -ngl 99 --host 127.0.0.1 --port 8080
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
- `-c 8192`: context for instruction + draft + rewrite.
|
| 140 |
+
- `-np 1`: one request at a time, so the whole context goes to it (newer builds otherwise split the context across parallel slots).
|
| 141 |
+
- `-ngl 99`: put every layer on the GPU. Lower it if you run out of GPU memory. The log line `offloaded N/N layers` tells you where it runs.
|
| 142 |
+
- Without a local file: `--hf-repo jialinyyzz/humanizer --hf-file humanizer-12b-Q8_0.gguf` instead of `-m …` downloads it for you.
|
| 143 |
+
|
| 144 |
+
Ready when `curl -s http://127.0.0.1:8080/health` returns `{"status":"ok"}`.
|
| 145 |
+
|
| 146 |
+
Use the `/completion` endpoint, as below; it works with every copy of the files.
|
| 147 |
+
|
| 148 |
+
**Chat endpoint.** `/v1/chat/completions` also works if your GGUF was downloaded on or after 2026-10-04 (check the size or sha256 in [section 2](#2-pick-a-file)) and the server runs with `--jinja` (the default in recent builds). The chat template built into the file takes the last user message as the draft and builds exactly the prompt above; system prompts and earlier turns are ignored, so send one draft per request. With llama.cpp we checked that the template builds the prompt byte for byte, and that with the same seed both endpoints return the same rewrite and stop on their own. A file downloaded earlier has no chat template, and the chat endpoint then gives bad output.
|
| 149 |
+
|
| 150 |
+
```bash
|
| 151 |
+
jq -n --rawfile d draft.txt '{messages: [{role: "user", content: $d}],
|
| 152 |
+
temperature: 1.0, top_p: 0.95, top_k: 0, min_p: 0, repeat_penalty: 1.0, max_tokens: 2048}' \
|
| 153 |
+
| curl -s http://127.0.0.1:8080/v1/chat/completions -H "Content-Type: application/json" -d @- \
|
| 154 |
+
| jq -r '.choices[0].message.content'
|
| 155 |
+
```
|
| 156 |
+
|
| 157 |
+
### Call it from Python (standard library only)
|
| 158 |
+
|
| 159 |
+
```python
|
| 160 |
+
import json, urllib.request
|
| 161 |
+
|
| 162 |
+
PF = json.load(open("humanizer-model/prompt_format.json", encoding="utf-8"))
|
| 163 |
+
|
| 164 |
+
def humanize(draft: str, url: str = "http://127.0.0.1:8080") -> str:
|
| 165 |
+
body = {
|
| 166 |
+
"prompt": PF["instr"] + "\n\n" + draft.strip() + PF["sep"],
|
| 167 |
+
"temperature": 1.0, "top_p": 0.95, "top_k": 0, "min_p": 0, "repeat_penalty": 1.0,
|
| 168 |
+
"n_predict": 2048, # no "stop": the model ends at EOS
|
| 169 |
+
}
|
| 170 |
+
req = urllib.request.Request(url + "/completion", json.dumps(body).encode("utf-8"),
|
| 171 |
+
{"Content-Type": "application/json"})
|
| 172 |
+
with urllib.request.urlopen(req, timeout=900) as r:
|
| 173 |
+
return json.load(r)["content"].strip()
|
| 174 |
+
|
| 175 |
+
print(humanize(open("draft.txt", encoding="utf-8").read()))
|
| 176 |
+
```
|
| 177 |
+
|
| 178 |
+
For streaming, add `"stream": true` and read the server-sent events; each event's `content` is the next piece of text.
|
| 179 |
+
|
| 180 |
+
### Call it with curl (macOS and Linux, needs jq)
|
| 181 |
+
|
| 182 |
+
```bash
|
| 183 |
+
jq -n --rawfile d draft.txt --slurpfile f humanizer-model/prompt_format.json \
|
| 184 |
+
'{prompt: ($f[0].instr + "\n\n" + ($d | sub("^\\s+"; "") | sub("\\s+$"; "")) + $f[0].sep),
|
| 185 |
+
temperature: 1.0, top_p: 0.95, top_k: 0, min_p: 0, repeat_penalty: 1.0, n_predict: 2048}' \
|
| 186 |
+
| curl -s http://127.0.0.1:8080/completion -d @- | jq -r .content
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
### One-shot, without a server
|
| 190 |
+
|
| 191 |
+
Newer llama.cpp builds call the plain text-completion tool `llama-completion`; in older builds the same flags work with `llama-cli`. `-no-cnv` keeps it out of chat mode.
|
| 192 |
+
|
| 193 |
+
```bash
|
| 194 |
+
# 1) write the exact prompt to a file. The extra "\n" at the end is deliberate:
|
| 195 |
+
# llama.cpp's -f drops exactly one trailing newline when it reads the file.
|
| 196 |
+
python3 - <<'EOF'
|
| 197 |
+
import json
|
| 198 |
+
pf = json.load(open("humanizer-model/prompt_format.json", encoding="utf-8"))
|
| 199 |
+
draft = open("draft.txt", encoding="utf-8").read()
|
| 200 |
+
with open("prompt.txt", "w", encoding="utf-8", newline="") as f:
|
| 201 |
+
f.write(pf["instr"] + "\n\n" + draft.strip() + pf["sep"] + "\n")
|
| 202 |
+
EOF
|
| 203 |
+
|
| 204 |
+
# 2) run once and print only the rewrite
|
| 205 |
+
llama-completion -m ./humanizer-model/humanizer-12b-Q8_0.gguf -f prompt.txt -c 8192 -n 2048 -ngl 99 \
|
| 206 |
+
-no-cnv --no-display-prompt --temp 1.0 --top-p 0.95 --top-k 0 --min-p 0 --repeat-penalty 1.0
|
| 207 |
+
```
|
| 208 |
+
|
| 209 |
+
We use llama-server ourselves and have not tested this one-shot path. Add `--verbose-prompt` once to see the tokenised prompt: it must end with `Rewritten`, `:` and a blank line, with nothing after that.
|
| 210 |
+
|
| 211 |
+
## 4. MLX (Apple silicon)
|
| 212 |
+
|
| 213 |
+
Needs mlx-lm 0.32 or newer. The 12B has no ready-made MLX files; convert the bf16 weights to 8-bit once. This reads the 24 GB bf16 download; a Mac with 32 GB or more is the comfortable size. We have only measured the 8-bit conversion. On an M5 Max it runs at about 30 tokens/s in English and 38 in Chinese.
|
| 214 |
+
|
| 215 |
+
```bash
|
| 216 |
+
pip install -U "mlx-lm>=0.32" huggingface_hub
|
| 217 |
+
mlx_lm.convert --hf-path jialinyyzz/humanizer --mlx-path humanizer-mlx-8bit -q --q-bits 8 --q-group-size 64
|
| 218 |
+
```
|
| 219 |
+
|
| 220 |
+
```python
|
| 221 |
+
import json
|
| 222 |
+
from huggingface_hub import hf_hub_download
|
| 223 |
+
from mlx_lm import load, generate
|
| 224 |
+
from mlx_lm.sample_utils import make_sampler
|
| 225 |
+
|
| 226 |
+
PF = json.load(open(hf_hub_download("jialinyyzz/humanizer", "prompt_format.json"), encoding="utf-8"))
|
| 227 |
+
model, tok = load("humanizer-mlx-8bit")
|
| 228 |
+
sampler = make_sampler(temp=1.0, top_p=0.95) # top-k and min-p stay off (their defaults)
|
| 229 |
+
|
| 230 |
+
def humanize(draft: str) -> str:
|
| 231 |
+
prompt = PF["instr"] + "\n\n" + draft.strip() + PF["sep"]
|
| 232 |
+
return generate(model, tok, prompt=prompt, max_tokens=2048, sampler=sampler).strip()
|
| 233 |
+
|
| 234 |
+
print(humanize(open("draft.txt", encoding="utf-8").read()))
|
| 235 |
+
```
|
| 236 |
+
|
| 237 |
+
Pass a plain string to `generate()` and never call `tok.apply_chat_template`. The `mlx_lm.generate` command-line tool applies the chat template unless you add `--ignore-chat-template`; the Python API above does not.
|
| 238 |
+
|
| 239 |
+
## 5. transformers (CUDA)
|
| 240 |
+
|
| 241 |
+
The bf16 weights are about 24 GB, so you need more GPU memory than that, or `device_map="auto"` to spread the model over several GPUs. You need a transformers version with Gemma 4 support; the weights were saved with 5.14.1.
|
| 242 |
+
|
| 243 |
+
```bash
|
| 244 |
+
pip install -U torch transformers accelerate huggingface_hub
|
| 245 |
+
```
|
| 246 |
+
|
| 247 |
+
```python
|
| 248 |
+
import json, torch
|
| 249 |
+
from huggingface_hub import hf_hub_download
|
| 250 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 251 |
+
|
| 252 |
+
repo = "jialinyyzz/humanizer"
|
| 253 |
+
PF = json.load(open(hf_hub_download(repo, "prompt_format.json"), encoding="utf-8"))
|
| 254 |
+
tok = AutoTokenizer.from_pretrained(repo)
|
| 255 |
+
model = AutoModelForCausalLM.from_pretrained(repo, dtype=torch.bfloat16, device_map="auto")
|
| 256 |
+
|
| 257 |
+
def humanize(draft: str) -> str:
|
| 258 |
+
ids = tok(PF["instr"] + "\n\n" + draft.strip() + PF["sep"], return_tensors="pt").to(model.device)
|
| 259 |
+
out = model.generate(**ids, do_sample=True, temperature=1.0, top_p=0.95,
|
| 260 |
+
top_k=0, # switches off the top-k 64 in generation_config.json
|
| 261 |
+
max_new_tokens=2048)
|
| 262 |
+
return tok.decode(out[0, ids["input_ids"].shape[1]:], skip_special_tokens=True).strip()
|
| 263 |
+
|
| 264 |
+
print(humanize(open("draft.txt", encoding="utf-8").read()))
|
| 265 |
+
```
|
| 266 |
+
|
| 267 |
+
On transformers 4.x, write `torch_dtype=` instead of `dtype=`. `top_k=0` matters: without it you sample with top-k 64.
|
| 268 |
+
|
| 269 |
+
## 6. vLLM
|
| 270 |
+
|
| 271 |
+
The offline API below mirrors how our evaluation outputs were generated (vLLM, bf16, temperature 1.0, top-p 0.95); the evaluation additionally used its anti-copy resample.
|
| 272 |
+
|
| 273 |
+
```python
|
| 274 |
+
import json
|
| 275 |
+
from huggingface_hub import hf_hub_download
|
| 276 |
+
from vllm import LLM, SamplingParams
|
| 277 |
+
|
| 278 |
+
repo = "jialinyyzz/humanizer"
|
| 279 |
+
PF = json.load(open(hf_hub_download(repo, "prompt_format.json"), encoding="utf-8"))
|
| 280 |
+
llm = LLM(model=repo, dtype="bfloat16", max_model_len=8192,
|
| 281 |
+
limit_mm_per_prompt={"image": 0, "audio": 0, "video": 0}) # text only
|
| 282 |
+
params = SamplingParams(temperature=1.0, top_p=0.95, top_k=-1, min_p=0.0,
|
| 283 |
+
repetition_penalty=1.0, max_tokens=2048) # top_k=-1: off
|
| 284 |
+
|
| 285 |
+
drafts = [open(p, encoding="utf-8").read() for p in ["draft1.txt", "draft2.txt"]]
|
| 286 |
+
prompts = [PF["instr"] + "\n\n" + d.strip() + PF["sep"] for d in drafts]
|
| 287 |
+
for result in llm.generate(prompts, params):
|
| 288 |
+
print(result.outputs[0].text.strip(), "\n---")
|
| 289 |
+
```
|
| 290 |
+
|
| 291 |
+
As a server (OpenAI-compatible). `--generation-config vllm` stops vLLM from taking top-k 64 from `generation_config.json` as the default:
|
| 292 |
+
|
| 293 |
+
```bash
|
| 294 |
+
vllm serve jialinyyzz/humanizer --dtype bfloat16 --max-model-len 8192 --generation-config vllm
|
| 295 |
+
```
|
| 296 |
+
|
| 297 |
+
```bash
|
| 298 |
+
jq -n --rawfile d draft.txt --slurpfile f humanizer-model/prompt_format.json \
|
| 299 |
+
'{model: "jialinyyzz/humanizer",
|
| 300 |
+
prompt: ($f[0].instr + "\n\n" + ($d | sub("^\\s+"; "") | sub("\\s+$"; "")) + $f[0].sep),
|
| 301 |
+
temperature: 1.0, top_p: 0.95, top_k: -1, min_p: 0, repetition_penalty: 1.0, max_tokens: 2048}' \
|
| 302 |
+
| curl -s http://127.0.0.1:8000/v1/completions -H "Content-Type: application/json" -d @- \
|
| 303 |
+
| jq -r '.choices[0].text'
|
| 304 |
+
```
|
| 305 |
+
|
| 306 |
+
Use `/v1/completions`, never `/v1/chat/completions`. We have not tested the server path ourselves.
|
| 307 |
+
|
| 308 |
+
## 7. Ollama
|
| 309 |
+
|
| 310 |
+
Ollama doesn't use the chat template stored in the GGUF; it needs its own template in the `Modelfile`. You need an Ollama version whose engine supports Gemma 4 models. We have not run Ollama ourselves: we rendered the template below with Go's `text/template` (the engine Ollama templates are written for) and got exactly the prompt from [section 1](#1-what-makes-this-model-different), but we have not tried it in Ollama.
|
| 311 |
+
|
| 312 |
+
`Modelfile`, next to the GGUF. The template takes the last user message as the draft and wraps it exactly as in section 1; system prompts and earlier turns are ignored, so every message is rewritten on its own.
|
| 313 |
+
|
| 314 |
+
```
|
| 315 |
+
FROM ./humanizer-12b-Q8_0.gguf
|
| 316 |
+
TEMPLATE """{{- $draft := "" }}{{- range .Messages }}{{- if eq .Role "user" }}{{- $draft = .Content }}{{- end }}{{- end }}Rewrite the text below so it reads like a person wrote it, not a language model.
|
| 317 |
+
|
| 318 |
+
Reorganize it as you see fit. Vary sentence length on purpose. Cut hedging,
|
| 319 |
+
throat-clearing, and any sentence that only announces what comes next.
|
| 320 |
+
Prefer the concrete word over the abstract one. It is fine to sound uneven.
|
| 321 |
+
|
| 322 |
+
Every fact, number, unit, date, name and quotation must survive unchanged.
|
| 323 |
+
|
| 324 |
+
{{ $draft }}
|
| 325 |
+
|
| 326 |
+
### Rewritten:{{ "\n\n" }}"""
|
| 327 |
+
PARAMETER temperature 1.0
|
| 328 |
+
PARAMETER top_p 0.95
|
| 329 |
+
PARAMETER top_k 0
|
| 330 |
+
PARAMETER min_p 0
|
| 331 |
+
PARAMETER repeat_penalty 1.0
|
| 332 |
+
PARAMETER num_ctx 8192
|
| 333 |
+
PARAMETER num_predict 2048
|
| 334 |
+
```
|
| 335 |
+
|
| 336 |
+
```bash
|
| 337 |
+
ollama create humanizer -f Modelfile
|
| 338 |
+
ollama show humanizer --modelfile # check that no "PARAMETER stop" lines were added
|
| 339 |
+
ollama run humanizer # then paste one draft per message
|
| 340 |
+
```
|
| 341 |
+
|
| 342 |
+
The `{{ "\n\n" }}` at the end is the blank line after `### Rewritten:`, written so that it survives even if the template's trailing whitespace is trimmed.
|
| 343 |
+
|
| 344 |
+
**Chat mode** (`ollama run`, `/api/chat`): send the draft as the user message. Ollama templates can't trim whitespace, so leave out leading and trailing blank lines (in code, send `draft.strip()`); otherwise the prompt is no longer byte for byte the one the model was trained on.
|
| 345 |
+
|
| 346 |
+
**Raw mode** skips the template and works with any Modelfile. Call `/api/generate` with `"raw": true` and the full prompt (instruction + draft + separator):
|
| 347 |
+
|
| 348 |
+
```bash
|
| 349 |
+
jq -n --rawfile d draft.txt --slurpfile f humanizer-model/prompt_format.json \
|
| 350 |
+
'{model: "humanizer", raw: true, stream: false,
|
| 351 |
+
prompt: ($f[0].instr + "\n\n" + ($d | sub("^\\s+"; "") | sub("\\s+$"; "")) + $f[0].sep),
|
| 352 |
+
options: {temperature: 1.0, top_p: 0.95, top_k: 0, min_p: 0, repeat_penalty: 1.0,
|
| 353 |
+
num_ctx: 8192, num_predict: 2048}}' \
|
| 354 |
+
| curl -s http://127.0.0.1:11434/api/generate -d @- | jq -r .response
|
| 355 |
+
```
|
| 356 |
+
|
| 357 |
+
Python:
|
| 358 |
+
|
| 359 |
+
```python
|
| 360 |
+
import json, urllib.request
|
| 361 |
+
|
| 362 |
+
PF = json.load(open("humanizer-model/prompt_format.json", encoding="utf-8"))
|
| 363 |
+
draft = open("draft.txt", encoding="utf-8").read()
|
| 364 |
+
body = {"model": "humanizer", "raw": True, "stream": False,
|
| 365 |
+
"prompt": PF["instr"] + "\n\n" + draft.strip() + PF["sep"],
|
| 366 |
+
"options": {"temperature": 1.0, "top_p": 0.95, "top_k": 0, "min_p": 0, "repeat_penalty": 1.0,
|
| 367 |
+
"num_ctx": 8192, "num_predict": 2048}}
|
| 368 |
+
req = urllib.request.Request("http://127.0.0.1:11434/api/generate", json.dumps(body).encode("utf-8"),
|
| 369 |
+
{"Content-Type": "application/json"})
|
| 370 |
+
print(json.load(urllib.request.urlopen(req, timeout=900))["response"].strip())
|
| 371 |
+
```
|
| 372 |
+
|
| 373 |
+
Ollama's built-in Gemma templates and a Modelfile without the `TEMPLATE` above both break this model in chat mode.
|
| 374 |
+
|
| 375 |
+
## 8. LM Studio
|
| 376 |
+
|
| 377 |
+
We have not tested LM Studio ourselves. LM Studio uses the chat template stored in the GGUF. The files uploaded on or after 2026-10-04 carry one that builds exactly the prompt from [section 1](#1-what-makes-this-model-different) (we checked it with llama.cpp, see [section 3](#start-a-server)); files downloaded earlier don't, so download them again or use the completion endpoint in step 4.
|
| 378 |
+
|
| 379 |
+
1. Load `humanizer-12b-Q8_0.gguf` (or Q6_K / Q4_K_M / Q3-QAT / IQ2_XS-QAT). Set the context length to 8192 when loading.
|
| 380 |
+
2. In the model's sampling settings, set **Temperature 1.0, Top P 0.95, Top K 0, Min P 0, Repeat Penalty 1.0** and remove any stop strings. Leave the prompt template as it came with the file.
|
| 381 |
+
3. **Chat tab:** leave the system prompt empty (the template ignores it) and paste one draft per message. Each message is rewritten on its own; earlier turns are not sent to the model. The server's `/v1/chat/completions` works the same way.
|
| 382 |
+
4. **Text completion** (works with any download): start the local server (Developer tab) and send the full prompt (instruction + draft + separator) to **`/v1/completions`**:
|
| 383 |
+
|
| 384 |
+
```python
|
| 385 |
+
import json, urllib.request
|
| 386 |
+
|
| 387 |
+
PF = json.load(open("humanizer-model/prompt_format.json", encoding="utf-8"))
|
| 388 |
+
draft = open("draft.txt", encoding="utf-8").read()
|
| 389 |
+
body = {"model": "humanizer-12b-q8_0", # replace with the model id shown in LM Studio
|
| 390 |
+
"prompt": PF["instr"] + "\n\n" + draft.strip() + PF["sep"],
|
| 391 |
+
"temperature": 1.0, "top_p": 0.95, "top_k": 0, "min_p": 0, "repeat_penalty": 1.0,
|
| 392 |
+
"max_tokens": 2048}
|
| 393 |
+
req = urllib.request.Request("http://127.0.0.1:1234/v1/completions", json.dumps(body).encode("utf-8"),
|
| 394 |
+
{"Content-Type": "application/json"})
|
| 395 |
+
print(json.load(urllib.request.urlopen(req, timeout=900))["choices"][0]["text"].strip())
|
| 396 |
+
```
|
| 397 |
+
|
| 398 |
+
If a chat reply starts with "Sure", repeats the instruction or doesn't stop, the file has no built-in template (downloaded before 2026-10-04) or the prompt template was changed in the model settings: download the file again, reset the template, or use `/v1/completions`. If your LM Studio version ignores `top_k`, `min_p` or `repeat_penalty` in the request, the settings from step 2 apply.
|
| 399 |
+
|
| 400 |
+
## 9. Rewrite a whole folder
|
| 401 |
+
|
| 402 |
+
`humanize_folder.py` rewrites every `.txt` in one folder into another folder through a running llama-server ([section 3](#start-a-server)). Standard library only.
|
| 403 |
+
|
| 404 |
+
- Long files are split at blank lines into pieces of at most `--max-tokens` draft tokens; the pieces are rewritten one by one and joined with a blank line.
|
| 405 |
+
- If a rewrite copies more than `--max-copy` (default 0.35) of a piece, the piece is sampled once more and the less-copied version is kept.
|
| 406 |
+
- Numbers that appear in a draft but not in its rewrite go into `report.tsv` for you to check.
|
| 407 |
+
- Files already in the output folder are skipped, so you can stop and resume.
|
| 408 |
+
|
| 409 |
+
```bash
|
| 410 |
+
python3 humanize_folder.py drafts/ rewrites/
|
| 411 |
+
python3 humanize_folder.py drafts/ rewrites/ --url http://127.0.0.1:8080 --max-tokens 1500
|
| 412 |
+
```
|
| 413 |
+
|
| 414 |
+
```python
|
| 415 |
+
#!/usr/bin/env python3
|
| 416 |
+
"""Rewrite every .txt file in a folder with humanizer, through a running llama-server.
|
| 417 |
+
|
| 418 |
+
python3 humanize_folder.py drafts/ rewrites/
|
| 419 |
+
python3 humanize_folder.py drafts/ rewrites/ --url http://127.0.0.1:8080 --max-tokens 1500
|
| 420 |
+
|
| 421 |
+
Standard library only. Long files are split at blank lines into pieces of at most --max-tokens
|
| 422 |
+
draft tokens; each piece is rewritten on its own and the pieces are joined with a blank line.
|
| 423 |
+
A piece whose rewrite copies more than --max-copy of the draft is sampled once more and the
|
| 424 |
+
less-copied version is kept. Numbers that appear in a draft but not in its rewrite are listed in
|
| 425 |
+
report.tsv so you can check them by hand. Files already in the output folder are skipped.
|
| 426 |
+
"""
|
| 427 |
+
import argparse, hashlib, json, math, os, re, sys, urllib.request
|
| 428 |
+
|
| 429 |
+
INSTR = (
|
| 430 |
+
"Rewrite the text below so it reads like a person wrote it, not a language model.\n"
|
| 431 |
+
"\n"
|
| 432 |
+
"Reorganize it as you see fit. Vary sentence length on purpose. Cut hedging,\n"
|
| 433 |
+
"throat-clearing, and any sentence that only announces what comes next.\n"
|
| 434 |
+
"Prefer the concrete word over the abstract one. It is fine to sound uneven.\n"
|
| 435 |
+
"\n"
|
| 436 |
+
"Every fact, number, unit, date, name and quotation must survive unchanged."
|
| 437 |
+
)
|
| 438 |
+
SEP = "\n\n### Rewritten:\n\n"
|
| 439 |
+
|
| 440 |
+
|
| 441 |
+
def build_prompt(draft):
|
| 442 |
+
return INSTR + "\n\n" + draft.strip() + SEP
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
assert hashlib.sha256(build_prompt("X").encode("utf-8")).hexdigest()[:16] == "cc51d66b4c593fbe"
|
| 446 |
+
|
| 447 |
+
|
| 448 |
+
def post(url, path, body, timeout=1800):
|
| 449 |
+
req = urllib.request.Request(url.rstrip("/") + path, json.dumps(body).encode("utf-8"),
|
| 450 |
+
{"Content-Type": "application/json"})
|
| 451 |
+
with urllib.request.urlopen(req, timeout=timeout) as r:
|
| 452 |
+
return json.load(r)
|
| 453 |
+
|
| 454 |
+
|
| 455 |
+
def n_tokens(url, text):
|
| 456 |
+
return len(post(url, "/tokenize", {"content": text})["tokens"])
|
| 457 |
+
|
| 458 |
+
|
| 459 |
+
def split_long(url, text, max_tokens):
|
| 460 |
+
"""Group paragraphs (split at blank lines) into pieces of at most max_tokens tokens."""
|
| 461 |
+
paras = [p.strip() for p in re.split(r"\n\s*\n", text.strip()) if p.strip()]
|
| 462 |
+
pieces, cur = [], []
|
| 463 |
+
for p in paras:
|
| 464 |
+
if cur and n_tokens(url, "\n\n".join(cur + [p])) > max_tokens:
|
| 465 |
+
pieces.append("\n\n".join(cur))
|
| 466 |
+
cur = []
|
| 467 |
+
cur.append(p)
|
| 468 |
+
if cur:
|
| 469 |
+
pieces.append("\n\n".join(cur))
|
| 470 |
+
return pieces
|
| 471 |
+
|
| 472 |
+
|
| 473 |
+
def rewrite(url, draft, ctx):
|
| 474 |
+
prompt = build_prompt(draft)
|
| 475 |
+
room = ctx - n_tokens(url, prompt) - 16
|
| 476 |
+
n_predict = max(256, min(math.ceil(n_tokens(url, draft) * 2.5), room))
|
| 477 |
+
body = {"prompt": prompt, "n_predict": n_predict, "temperature": 1.0, "top_p": 0.95,
|
| 478 |
+
"top_k": 0, "min_p": 0, "repeat_penalty": 1.0} # no "stop": ends at EOS
|
| 479 |
+
return post(url, "/completion", body)["content"].strip()
|
| 480 |
+
|
| 481 |
+
|
| 482 |
+
# Rough copy ratio: share of the rewrite's 5-grams (words; single CJK characters) found in the draft.
|
| 483 |
+
UNIT = re.compile(r"[㐀-鿿]|[^\W_㐀-鿿]+|[^\w\s]")
|
| 484 |
+
|
| 485 |
+
|
| 486 |
+
def copy_ratio(draft, out, n=5):
|
| 487 |
+
a = [t.lower() for t in UNIT.findall(draft)]
|
| 488 |
+
b = [t.lower() for t in UNIT.findall(out)]
|
| 489 |
+
seen = {tuple(a[i:i + n]) for i in range(len(a) - n + 1)}
|
| 490 |
+
grams = [tuple(b[i:i + n]) for i in range(len(b) - n + 1)]
|
| 491 |
+
return sum(g in seen for g in grams) / len(grams) if grams else 0.0
|
| 492 |
+
|
| 493 |
+
|
| 494 |
+
NUM = re.compile(r"\d+(?:[.,:/]\d+)*")
|
| 495 |
+
|
| 496 |
+
|
| 497 |
+
def missing_numbers(draft, out):
|
| 498 |
+
have = {m.replace(",", "") for m in NUM.findall(out)} | set(re.findall(r"\d+", out))
|
| 499 |
+
return sorted({m.replace(",", "") for m in NUM.findall(draft)} - have)
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
def main():
|
| 503 |
+
ap = argparse.ArgumentParser()
|
| 504 |
+
ap.add_argument("src"); ap.add_argument("dst")
|
| 505 |
+
ap.add_argument("--url", default="http://127.0.0.1:8080")
|
| 506 |
+
ap.add_argument("--ctx", type=int, default=8192, help="the -c you gave llama-server")
|
| 507 |
+
ap.add_argument("--max-tokens", type=int, default=1500, help="max draft tokens per piece")
|
| 508 |
+
ap.add_argument("--max-copy", type=float, default=0.35, help="resample a piece once above this")
|
| 509 |
+
a = ap.parse_args()
|
| 510 |
+
os.makedirs(a.dst, exist_ok=True)
|
| 511 |
+
files = sorted(f for f in os.listdir(a.src) if f.lower().endswith(".txt"))
|
| 512 |
+
report = open(os.path.join(a.dst, "report.tsv"), "a", encoding="utf-8")
|
| 513 |
+
for i, name in enumerate(files, 1):
|
| 514 |
+
out_path = os.path.join(a.dst, name)
|
| 515 |
+
if os.path.exists(out_path):
|
| 516 |
+
print(f"[{i}/{len(files)}] {name}: already done, skipped"); continue
|
| 517 |
+
draft = open(os.path.join(a.src, name), encoding="utf-8").read()
|
| 518 |
+
if not draft.strip():
|
| 519 |
+
continue
|
| 520 |
+
parts = []
|
| 521 |
+
for piece in split_long(a.url, draft, a.max_tokens):
|
| 522 |
+
out = rewrite(a.url, piece, a.ctx)
|
| 523 |
+
c = copy_ratio(piece, out)
|
| 524 |
+
if c > a.max_copy:
|
| 525 |
+
out2 = rewrite(a.url, piece, a.ctx)
|
| 526 |
+
c2 = copy_ratio(piece, out2)
|
| 527 |
+
if c2 < c:
|
| 528 |
+
out, c = out2, c2
|
| 529 |
+
parts.append(out)
|
| 530 |
+
text = "\n\n".join(parts)
|
| 531 |
+
with open(out_path, "w", encoding="utf-8", newline="") as f:
|
| 532 |
+
f.write(text + "\n")
|
| 533 |
+
miss = missing_numbers(draft, text)
|
| 534 |
+
report.write(f"{name}\t{len(parts)} piece(s)\tcopy {copy_ratio(draft, text):.2f}\tcheck numbers: {' '.join(miss) or '-'}\n")
|
| 535 |
+
report.flush()
|
| 536 |
+
print(f"[{i}/{len(files)}] {name}: {len(parts)} piece(s), copy {copy_ratio(draft, text):.2f}"
|
| 537 |
+
+ (f", check numbers {miss}" if miss else ""))
|
| 538 |
+
|
| 539 |
+
|
| 540 |
+
if __name__ == "__main__":
|
| 541 |
+
main()
|
| 542 |
+
```
|
| 543 |
+
|
| 544 |
+
The copy ratio here is a rough measure (share of the rewrite's 5-word or 5-character runs found in the draft), not the exact metric from our evaluation, and the second sample has no anti-copy penalty, unlike the evaluation's resample.
|
| 545 |
+
|
| 546 |
+
## 10. Long documents
|
| 547 |
+
|
| 548 |
+
- The context is **8192 tokens** for the instruction, the draft and the rewrite together. Since a rewrite is about as long as its draft, keep each draft piece well under half of that.
|
| 549 |
+
- Split at **paragraph boundaries** (blank lines), never mid-sentence, and rewrite the pieces separately. The batch script above does this; its default piece size is 1,500 tokens.
|
| 550 |
+
- The model was trained and evaluated on single emails, posts, essays and report sections of a few hundred words. Pieces of that size work best.
|
| 551 |
+
- Each piece is rewritten without seeing the others, so tone can shift a little between pieces and a fact can't move from one piece to another. Read the joined result once from top to bottom.
|
| 552 |
+
- Headings, code blocks and bullet lists are often dropped or turned into prose. Rewrite only the prose and keep headings and code yourself; [`humanizer/markdown_guard.py`](https://github.com/sgaofen/humanizer-local-model/blob/main/humanizer/markdown_guard.py) does this block by block.
|
| 553 |
+
- **[`hz`](#14-hz-command-line-tool) does all of this in one command**, for `.md`, `.txt` and `.docx`: it keeps headings, code, tables and links, splits the prose into pieces of about 350 words (600 Chinese characters) without crossing a heading, and checks every piece.
|
| 554 |
+
- The app (0.3.2 and later) can import a `.docx` or PDF and shows a note on long drafts, but it rewrites the whole draft in one request; its **Create fact** keeps a selected passage word for word by sending a placeholder in its place and rewriting again if the placeholder doesn't come back intact. For long or structured documents, `hz` is still the better tool.
|
| 555 |
+
|
| 556 |
+
## 11. Chinese
|
| 557 |
+
|
| 558 |
+
- Use **the same English instruction**; don't translate it.
|
| 559 |
+
- Chinese is **still catching up with English**: our fact judge found no factual problem in 149 of 204 Chinese rewrites; where it found one, about 9 in 10 fixes are a single word or phrase.
|
| 560 |
+
- Chinese rewrites copy the draft more often. With the Q8_0 file, 14 of 204 Chinese rewrites copied more than 35% of the draft, against 1 of 420 English ones. If a rewrite looks too close to the draft, sample again; the batch script does this automatically.
|
| 561 |
+
- **Numbers change form** more often in Chinese: Chinese numerals become digits (三 → 3) and dates get reformatted (6月14日 → 6.14). Digit-only checks, like the one in the app and in the batch script, can't see this. Check the numbers by reading.
|
| 562 |
+
- Full-width and half-width punctuation may switch, and greeting, body and sign-off lines are sometimes merged into one paragraph. Fix the layout before sending.
|
| 563 |
+
- Speed is similar to English. With llama.cpp Q8_0 on an M5 Max, a Chinese email of about 300 characters takes about 8.5 seconds.
|
| 564 |
+
|
| 565 |
+
## 12. Quality checklist
|
| 566 |
+
|
| 567 |
+
- **Read the rewrite once.** Check every number, date, unit and name, and the direction of every claim (who did what, more or less, before or after). On our evaluation set a strict judge found no factual problem in 376 of 420 English rewrites; where it found one, more than 9 in 10 fixes are a single word or phrase, like "37 complaints" becoming "37% of complaints".
|
| 568 |
+
- **Restore formatting** you need: subject lines, lists, headings and sign-offs are sometimes dropped (28 of 420 outputs).
|
| 569 |
+
- **Too close to the draft? Sample again.** Each run is a fresh sample.
|
| 570 |
+
- **Casual genres can drift in register.** In Reddit-style posts it sometimes adds slang or profanity that wasn't in the draft; edit it out.
|
| 571 |
+
- **No detector guarantee.** Our detector numbers are one measurement on one date with one detector; templated genres (emoji or hashtag social posts, policy memos) are still the most often flagged. Nothing here promises any detector outcome.
|
| 572 |
+
- It is a writing tool for your own drafts. Where a school, employer or publication has rules about AI assistance, follow them.
|
| 573 |
+
|
| 574 |
+
## 13. Troubleshooting
|
| 575 |
+
|
| 576 |
+
| Symptom | Cause | Fix |
|
| 577 |
+
|---|---|---|
|
| 578 |
+
| Output starts with "Sure", "Here is…", repeats the instruction, or doesn't stop | A generic chat template is in use: a GGUF downloaded before 2026-10-04 (no built-in template), a template changed in the app's settings, Ollama without the Modelfile from [section 7](#7-ollama), or a chat endpoint on the safetensors weights | Download the GGUF again, or use a completion endpoint (`/completion`, `/v1/completions`, Ollama `raw: true`) with the exact prompt from [section 1](#1-what-makes-this-model-different) |
|
| 579 |
+
| Output contains `<start_of_turn>`, `<end_of_turn>` or similar markers | Same: chat formatting | Same fix |
|
| 580 |
+
| Output stops at `###` or very early | A stop string is set | Remove all stop strings; rely on EOS |
|
| 581 |
+
| An English draft comes back in Chinese (occasionally, on short informal English drafts with technical jargon) | Sampling luck | Sample again with the same settings when the rewrite of an English draft has more than a few Chinese characters. The app (0.3.1 and later) and `hz` do this automatically, up to 3 times |
|
| 582 |
+
| Output is almost the same as the draft | Sampling luck, or temperature too low | Check temperature 1.0 and sample again |
|
| 583 |
+
| Rambling, odd word choices, or repeated phrases | Wrong samplers (llama.cpp's default top-k 40 / min-p 0.05, the top-k 64 from `generation_config.json`, or a repetition penalty) | Set top-k 0, min-p 0, repetition penalty 1.0 explicitly |
|
| 584 |
+
| Rewrite cut off mid-sentence | Output limit or context too small | Raise `n_predict` / `max_tokens`; give llama-server `-c 8192 -np 1`; split long drafts |
|
| 585 |
+
| Out of memory while loading | File too large for your RAM or VRAM | Use Q6_K (16 GB), Q4_K_M (about 14 GB), Q3-QAT (12 GB) or the 2-bit IQ2_XS-QAT (8 GB); lower `-ngl` |
|
| 586 |
+
| Very slow | Running on the CPU | Look for `offloaded N/N layers` in the llama.cpp log; install the Metal, CUDA or Vulkan build |
|
| 587 |
+
| 404 when downloading | Wrong file name | Use the names in [section 2](#2-pick-a-file) |
|
| 588 |
+
| Self-test fingerprint fails | The prompt builder differs from training | Copy `build_prompt` from [section 1](#the-prompt) |
|
| 589 |
+
|
| 590 |
+
**Self-test.** [AGENTS.md, section 8](https://github.com/sgaofen/humanizer-local-model/blob/main/AGENTS.md#8-self-test-verify-the-install) has a standard-library script that sends a real draft from the evaluation set to llama-server and checks the prompt format, the endpoint, chat-template leaks and copying. It prints `PASS` when the setup is right.
|
| 591 |
+
|
| 592 |
+
## 14. hz command-line tool
|
| 593 |
+
|
| 594 |
+
`hz` rewrites a draft or a whole document in one command and works the same for people and AI agents. It talks to the [app](https://github.com/sgaofen/humanizer-local-model/releases/latest) or to a llama-server ([section 3](#start-a-server)), builds the exact prompt, uses the evaluated sampling, splits long documents, keeps their structure, and checks every piece. Python 3.8 or newer, standard library only; `.docx` support is optional.
|
| 595 |
+
|
| 596 |
+
### Install
|
| 597 |
+
|
| 598 |
+
```bash
|
| 599 |
+
pipx install git+https://github.com/sgaofen/humanizer-local-model
|
| 600 |
+
pipx inject humanize-model python-docx # only if you need .docx
|
| 601 |
+
|
| 602 |
+
# or with pip, in any environment:
|
| 603 |
+
pip install git+https://github.com/sgaofen/humanizer-local-model
|
| 604 |
+
pip install "humanize-model[docx] @ git+https://github.com/sgaofen/humanizer-local-model" # with .docx support
|
| 605 |
+
|
| 606 |
+
# from a clone, without installing:
|
| 607 |
+
python3 -m humanizer.hz draft.txt
|
| 608 |
+
```
|
| 609 |
+
|
| 610 |
+
It installs one command, `hz`. If you already have another program called `hz`, the one that comes first in your `PATH` wins.
|
| 611 |
+
|
| 612 |
+
### Use
|
| 613 |
+
|
| 614 |
+
```bash
|
| 615 |
+
hz draft.txt # print the rewrite to stdout
|
| 616 |
+
hz < draft.txt > rewrite.txt
|
| 617 |
+
hz paper.md -o paper.out.md # long Markdown: structure kept, prose rewritten in pieces
|
| 618 |
+
hz report.docx -o report.out.docx # without -o it writes report.hz.docx
|
| 619 |
+
hz paper.md --json > report.json # per-piece stats; with -o the text goes to the file
|
| 620 |
+
hz paper.md --dry-run # show how it will be split; no model needed
|
| 621 |
+
hz paper.md --server http://127.0.0.1:8080 # a specific llama-server
|
| 622 |
+
```
|
| 623 |
+
|
| 624 |
+
Progress goes to stderr, one line per piece. `--quiet` hides it; the list of pieces to check is always printed.
|
| 625 |
+
|
| 626 |
+
### Where it finds the model
|
| 627 |
+
|
| 628 |
+
1. `--server URL`: that llama-server and nothing else. (If the address is the app's, it talks to the app.)
|
| 629 |
+
2. Otherwise the **app**, if it is running and ready. It reads the app's port from the app's `instance.json`, and falls back to `http://127.0.0.1:47615`. Requests go to `POST /api/completion` with the `X-Humanizer: 1` header the app requires.
|
| 630 |
+
3. Otherwise a **llama-server** at `http://127.0.0.1:8080` (`POST /completion`).
|
| 631 |
+
4. Otherwise, on macOS, if the app is installed but closed: `hz` starts it in the background (`open -a Humanizer`) and waits up to 3 minutes for the model to load, printing progress.
|
| 632 |
+
5. Otherwise it stops with exit code 3 and prints how to install the app or start llama-server.
|
| 633 |
+
|
| 634 |
+
If the app is open but has no model yet (first run), `hz` tells you to pick one in the app first.
|
| 635 |
+
|
| 636 |
+
### How a document is split
|
| 637 |
+
|
| 638 |
+
- Blank lines separate blocks. These are **kept exactly as written** and never sent to the model: Markdown headings (`#`, underlined, or a line that is only `**bold**`), fenced and indented code, tables, lines that are only images or links, HTML blocks and comments, `$$…$$` and `\[…\]` math, block quotes, horizontal rules, YAML front matter, footnote and link definitions, a short line ending in a colon right before a list, table or code block ("Key points:"), and everything under a heading called References, Bibliography, Works Cited, Sources or 参考文献.
|
| 639 |
+
- Prose paragraphs are grouped in order into pieces of at most about **350 English words or 600 Chinese characters** (`--max-words`, `--max-chars`). A piece never crosses a heading or any kept block, and pieces in one stretch of prose are made about the same size.
|
| 640 |
+
- A paragraph longer than that is cut at sentence ends; its rewritten pieces are joined back into one paragraph.
|
| 641 |
+
- **Lists:** each item keeps its bullet or number and a leading `**Label:**`. An item of at least about 15 words (26 Chinese characters) is rewritten on its own; shorter items are kept. Short items are where the model is weakest, so read them.
|
| 642 |
+
- The rewritten pieces go back in their original places, with the original blank lines between blocks. Inline formatting inside prose (bold, inline code, links) is up to the model and is sometimes dropped.
|
| 643 |
+
|
| 644 |
+
### Checks and the retry
|
| 645 |
+
|
| 646 |
+
Each rewritten piece is checked. If it has one of these problems, the piece is rewritten once more (`--retries`) and the version with fewer problems is kept:
|
| 647 |
+
|
| 648 |
+
| Problem | Meaning |
|
| 649 |
+
|---|---|
|
| 650 |
+
| `missing_numbers` | A number in the draft is not in the rewrite. Numbers are normalized first: `1,250` = `1250`, `3.50` = `3.5`, `07` = `7`, `480k` = `480,000`, `31.7万` = `317,000`, and a number written as a word in the rewrite (`three`, `两`) counts. |
|
| 651 |
+
| `copy` | More than half of the rewrite copies the draft (`--max-copy`, default 0.5): share of the rewrite's word 5-grams, or 6-character grams for Chinese, that also appear in the draft. |
|
| 652 |
+
| `missing_urls` | A link in the draft is not in the rewrite. |
|
| 653 |
+
| `empty`, `truncated` | Nothing came back, or the output hit the length limit. |
|
| 654 |
+
| `too_short`, `too_long` | The rewrite is under 35% or over 175% of the draft's length. On a real run, outputs of normal pieces were 0.7 to 1.5 times the draft; the ones above 1.75 had added a made-up paragraph, a made-up figure, or the same sentence twice. |
|
| 655 |
+
| `repeated` | Two sentences of the rewrite say nearly the same thing, and the draft had no such pair. |
|
| 656 |
+
| `markup` | The rewrite contains an HTML tag the draft doesn't have (on real runs, a stray `<p>` after a short list item). |
|
| 657 |
+
|
| 658 |
+
`added_numbers`, numbers in the rewrite that are not in the draft, are **reported but never retried**: the model sometimes does correct arithmetic ("cut costs from $480k to $305k" became "saved $175k"), and sometimes invents a figure. Either way, look at them.
|
| 659 |
+
|
| 660 |
+
**Wrong language.** Before these checks, a rewrite in the wrong language (an English draft written in Chinese, or a Chinese draft written in English; it only counts characters) is sampled again with the same settings, up to 3 times. `pieces[].language_resampled` counts those extra samples; if every sample is in the wrong language, the piece is flagged with `language`.
|
| 661 |
+
|
| 662 |
+
Pieces that still have a problem after the retry are listed on stderr with their line number (`.docx`: paragraph number) and, with `--json`, marked `"flagged": true`. The exit code is still 0.
|
| 663 |
+
|
| 664 |
+
### `--json` output
|
| 665 |
+
|
| 666 |
+
From a real run on a 1,200-word English Markdown article through the app (one of its 21 pieces shown):
|
| 667 |
+
|
| 668 |
+
```json
|
| 669 |
+
{
|
| 670 |
+
"hz": "0.1.0", "input": "article.md", "output": "article.out.md", "format": "text",
|
| 671 |
+
"backend": {"kind": "app", "url": "http://127.0.0.1:47615", "model": "q8"},
|
| 672 |
+
"summary": {"pieces": 21, "retried": 0, "flagged": 1, "seconds": 41.4,
|
| 673 |
+
"words_in": 1035, "words_out": 1153, "copy_rate": 0.05},
|
| 674 |
+
"kept": {"heading": 8, "intro": 2, "table": 1, "code": 1},
|
| 675 |
+
"pieces": [
|
| 676 |
+
{"id": 15, "kind": "prose", "line": 65, "words_in": 102, "words_out": 111,
|
| 677 |
+
"copy_rate": 0.036, "missing_numbers": [], "missing_urls": [], "added_numbers": ["175k"],
|
| 678 |
+
"truncated": false, "retried": false, "chosen": 1, "seconds": 4.4,
|
| 679 |
+
"flagged": true, "issues": ["added_numbers"],
|
| 680 |
+
"attempts": [{"copy_rate": 0.036, "missing_numbers": [], "issues": ["added_numbers"], "seconds": 4.4}],
|
| 681 |
+
"draft_start": "The results of the migration have exceeded our initial expec"}
|
| 682 |
+
]
|
| 683 |
+
}
|
| 684 |
+
```
|
| 685 |
+
|
| 686 |
+
| Field | Meaning |
|
| 687 |
+
|---|---|
|
| 688 |
+
| `summary.pieces` / `retried` / `flagged` | Pieces sent to the model; how many were rewritten a second time; how many still have a problem |
|
| 689 |
+
| `summary.copy_rate` | Copy rate of all rewritten prose against all of its drafts |
|
| 690 |
+
| `kept` | Blocks kept as they were, by kind (`short` = prose too short to rewrite; `list item` = short list items) |
|
| 691 |
+
| `pieces[].line` (`.docx`: `paragraph`) | Where the piece starts in the input, 1-based |
|
| 692 |
+
| `pieces[].kind` | `prose` (one or more paragraphs), `item` (a list item), `sentences` (part of an over-long paragraph), `paragraph` (`.docx`) |
|
| 693 |
+
| `pieces[].words_in` / `words_out` | Size of draft and rewrite; a Chinese character counts as one word |
|
| 694 |
+
| `pieces[].copy_rate` | Copy rate of the chosen version, 0 to 1 |
|
| 695 |
+
| `pieces[].missing_numbers` / `missing_urls` / `added_numbers` | As in the table above, spelled as in the draft (missing) or the rewrite (added) |
|
| 696 |
+
| `pieces[].retried` / `chosen` / `attempts` | Whether it was rewritten twice, which try was kept, and each try's checks |
|
| 697 |
+
| `pieces[].seconds` | Model time for this piece, all tries together |
|
| 698 |
+
| `pieces[].language_resampled` | Extra samples taken because a rewrite came back in the wrong language (usually 0) |
|
| 699 |
+
| `pieces[].flagged` / `issues` | Whether the kept version still has a problem, and which |
|
| 700 |
+
| `text` | The whole result, only when there is no `-o` and the input is not `.docx` |
|
| 701 |
+
|
| 702 |
+
### `.docx` files
|
| 703 |
+
|
| 704 |
+
- Needs `python-docx` (see Install). Body paragraphs are rewritten one by one. Headings (Heading, Title and Subtitle styles, or an outline level), tables, empty paragraphs, captions, quotes, code styles, the table of contents and everything under a References heading are left alone. Headers, footers, footnotes and text boxes are not touched.
|
| 705 |
+
- A paragraph that contains a hyperlink, an image, a field, a footnote reference, tracked changes or an equation is left alone too, so nothing in it is lost.
|
| 706 |
+
- The rewrite is written into the paragraph's **first run** and the other runs are emptied. The paragraph keeps its style and numbering, but **bold, italic or other formatting on part of a paragraph is lost**; the whole paragraph takes the first run's formatting.
|
| 707 |
+
|
| 708 |
+
### Options
|
| 709 |
+
|
| 710 |
+
| Option | Default | Meaning |
|
| 711 |
+
|---|---|---|
|
| 712 |
+
| `-o FILE` | stdout (`.docx`: `NAME.hz.docx`) | Where to write the result. It refuses to overwrite the input. |
|
| 713 |
+
| `--server URL` | | Use this llama-server only |
|
| 714 |
+
| `--app URL` | auto | The app's address, if it is not the usual one |
|
| 715 |
+
| `--json` | off | Print the JSON report on stdout |
|
| 716 |
+
| `-q`, `--quiet` | off | No progress lines |
|
| 717 |
+
| `--dry-run` | off | Show the pieces and the kept blocks; no model is called |
|
| 718 |
+
| `--max-words` / `--max-chars` | 350 / 600 | Piece size for English / Chinese |
|
| 719 |
+
| `--max-copy` | 0.5 | Copy rate that triggers a retry |
|
| 720 |
+
| `--retries` | 1 | Extra tries for a piece with a problem (0 = none) |
|
| 721 |
+
| `--no-launch` | off | Don't start the app if it is closed |
|
| 722 |
+
|
| 723 |
+
Exit codes: 0 done (also when some pieces are flagged), 1 error during the run (nothing is written), 2 bad input or arguments, 3 no model found.
|
| 724 |
+
|
| 725 |
+
### Limits
|
| 726 |
+
|
| 727 |
+
- The checks cover numbers, links, copying, length, repetition and stray HTML tags. **A changed word, name or meaning is not caught.** On a real Chinese run, "all 48 stores" became "48 stores in the province" and "from time to time" became "often", with every number intact. Read the result.
|
| 728 |
+
- Each piece is rewritten without seeing the others, so tone can shift a little between pieces.
|
| 729 |
+
- Short list items are the weakest part: the model sometimes drops the subject of a short item or repeats it.
|
| 730 |
+
- Chinese numerals written as characters are only partly understood, so a number that changes between 三 and 3 can slip through either way.
|
| 731 |
+
- Speed with the app on an M5 Max (Q8_0): a 1,200-word English Markdown article, 21 pieces, 41 to 54 seconds; a Chinese report of about 700 characters, 6 pieces, 13 to 17 seconds.
|
| 732 |
+
- No AI detector result is promised.
|
USAGE.zh.md
ADDED
|
@@ -0,0 +1,732 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 不用 App 怎么用 humanizer
|
| 2 |
+
|
| 3 |
+
[English](USAGE.md) · [README](https://github.com/sgaofen/humanizer-local-model/blob/main/README.zh.md) · [AGENTS.md(给 AI Agent)](https://github.com/sgaofen/humanizer-local-model/blob/main/AGENTS.md) · [Hugging Face 上的模型文件](https://huggingface.co/jialinyyzz/humanizer/tree/main)
|
| 4 |
+
|
| 5 |
+
这份文档写给不想用[桌面 App](https://github.com/sgaofen/humanizer-local-model/releases/latest)、要在自己的代码或命令行里跑 humanizer 的人。每个代码块都可以直接复制。
|
| 6 |
+
|
| 7 |
+
**只是想改写文件**?命令行工具 `hz` 替你处理提示词、采样参数、长文切块和检查,后端用 App 或 llama-server 都行:`pipx install git+https://github.com/sgaofen/humanizer-local-model`,然后 `hz draft.md -o out.md`。见[第 14 节](#14-命令行工具-hz)。
|
| 8 |
+
|
| 9 |
+
**目录**:[1. 这个模型哪里特殊](#1-这个模型哪里特殊) · [2. 选哪个文件](#2-选哪个文件) · [3. llama.cpp](#3-llamacpp推荐) · [4. MLX](#4-mlxapple-芯片) · [5. transformers](#5-transformerscuda) · [6. vLLM](#6-vllm) · [7. Ollama](#7-ollama) · [8. LM Studio](#8-lm-studio) · [9. 批量改写一个文件夹](#9-批量改写一个文件夹) · [10. 长文](#10-长文) · [11. 中文](#11-中文) · [12. 质量检查清单](#12-质量检查清单) · [13. 排错](#13-排错) · [14. 命令行工具 hz](#14-命令行工具-hz)
|
| 10 |
+
|
| 11 |
+
## 1. 这个模型哪里特殊
|
| 12 |
+
|
| 13 |
+
humanizer 是一个 12B 的**文本续写**模型,由 `google/gemma-4-12B` 微调而来:给它一篇 AI 写的草稿,它接着写出改写。下面四条规则对所有运行方式都成立。做对了,效果和我们的评测一致;错一条,效果就会明显变差。
|
| 14 |
+
|
| 15 |
+
| 规则 | 原因 |
|
| 16 |
+
|---|---|
|
| 17 |
+
| **它是文本续写模型,不是聊天模型**。把一个纯文本字符串发到续写接口。聊天模式只能靠 GGUF 文件里自带的对话模板(2026-10-04 起才有):它把最后一条用户消息拼成和下面一模一样的字符串,系统提示词和之前的对话都不用。 | 它训练时见到的就是下面这种原始文本。通用的聊天模板(Gemma 轮次、ChatML)会给草稿套上它训练时从没见过的标记。safetensors 权重(transformers、vLLM、MLX)没有对话模板。 |
|
| 18 |
+
| **提示词必须逐字一致。** | 指令改写过、翻译过,或者少一个空行,效果都会变差。 |
|
| 19 |
+
| **只靠 EOS 停**。不要设停止符,尤其不要用 `###`。 | 模型会自己结束。少数正常输出里本来就有 `###`,会被截断。 |
|
| 20 |
+
| **采样:temperature 1.0、top-p 0.95,别的都关掉**。top-k 关(0),min-p 关(0),重复惩罚 1.0。 | 评测就是这样跑的。好几个运行环境默认会开别的采样:llama.cpp 默认 top-k 40、min-p 0.05;transformers 会从随权重附带的 `generation_config.json` 里读到 top-k 64。 |
|
| 21 |
+
|
| 22 |
+
### 提示词
|
| 23 |
+
|
| 24 |
+
```
|
| 25 |
+
prompt = INSTR + "\n\n" + draft.strip() + "\n\n### Rewritten:\n\n"
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
`INSTR` 就是下面这段原文(各行用 `\n` 连接,空行也算,结尾不带换行):
|
| 29 |
+
|
| 30 |
+
```
|
| 31 |
+
Rewrite the text below so it reads like a person wrote it, not a language model.
|
| 32 |
+
|
| 33 |
+
Reorganize it as you see fit. Vary sentence length on purpose. Cut hedging,
|
| 34 |
+
throat-clearing, and any sentence that only announces what comes next.
|
| 35 |
+
Prefer the concrete word over the abstract one. It is fine to sound uneven.
|
| 36 |
+
|
| 37 |
+
Every fact, number, unit, date, name and quotation must survive unchanged.
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
- `draft.strip()` 是去掉草稿首尾的空白。
|
| 41 |
+
- 分隔符 `\n\n### Rewritten:\n\n` 结尾是一个空行,模型从这之后开始写。
|
| 42 |
+
- 中文草稿也用这段英文指令,**不要翻译**。
|
| 43 |
+
- 这两段字符串随权重放在 `prompt_format.json` 里(字段 `instr` 和 `sep`)。
|
| 44 |
+
|
| 45 |
+
一个可以贴到任何地方的提示词拼接函数,带自检:
|
| 46 |
+
|
| 47 |
+
```python
|
| 48 |
+
import hashlib
|
| 49 |
+
|
| 50 |
+
INSTR = (
|
| 51 |
+
"Rewrite the text below so it reads like a person wrote it, not a language model.\n"
|
| 52 |
+
"\n"
|
| 53 |
+
"Reorganize it as you see fit. Vary sentence length on purpose. Cut hedging,\n"
|
| 54 |
+
"throat-clearing, and any sentence that only announces what comes next.\n"
|
| 55 |
+
"Prefer the concrete word over the abstract one. It is fine to sound uneven.\n"
|
| 56 |
+
"\n"
|
| 57 |
+
"Every fact, number, unit, date, name and quotation must survive unchanged."
|
| 58 |
+
)
|
| 59 |
+
SEP = "\n\n### Rewritten:\n\n"
|
| 60 |
+
|
| 61 |
+
def build_prompt(draft: str) -> str:
|
| 62 |
+
return INSTR + "\n\n" + draft.strip() + SEP
|
| 63 |
+
|
| 64 |
+
# 指纹:这一行报错就说明提示词拼错了
|
| 65 |
+
assert hashlib.sha256(build_prompt("X").encode("utf-8")).hexdigest()[:16] == "cc51d66b4c593fbe"
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
**输出长度**。改写通常和草稿差不多长。输出上限留草稿 token 数的 2.5 倍左右;App 用的是 `min(2048, max(256, 2.5 × 草稿 token 数))`。指令、草稿和改写加起来共用 8192 token 的上下文,见[长文](#10-长文)。
|
| 69 |
+
|
| 70 |
+
## 2. 选哪个文件
|
| 71 |
+
|
| 72 |
+
文件都在 [`jialinyyzz/humanizer`](https://huggingface.co/jialinyyzz/humanizer/tree/main):
|
| 73 |
+
|
| 74 |
+
| 内存 | 文件 | 大小 |
|
| 75 |
+
|---|---|---|
|
| 76 |
+
| 32 GB 及以��� | `humanizer-12b-Q8_0.gguf` | 12,669,630,368 字节(约 12.7 GB) |
|
| 77 |
+
| 16 GB | `humanizer-12b-Q6_K.gguf` | 10,029,799,584 字节(约 10.0 GB) |
|
| 78 |
+
| 14 GB 左右,或硬盘紧张 | `humanizer-12b-Q4_K_M.gguf`,量化感知训练;峰值内存约 10 GB | 7,625,160,864 字节(约 7.6 GB) |
|
| 79 |
+
| 12 GB | `humanizer-12b-Q3-QAT.gguf`,3 bit 档;峰值内存约 8 GB。英文事实小错稍多,核对数字和名字 | 5,587,794,816 字节(约 5.6 GB) |
|
| 80 |
+
| 8 GB | `humanizer-12b-IQ2_XS-QAT.gguf`,2 bit,最小;峰值内存约 6.2 GB。事实小错更多,核对数字和名字 | 3,893,632,896 字节(约 3.9 GB) |
|
| 81 |
+
|
| 82 |
+
仓库里还有:
|
| 83 |
+
|
| 84 |
+
- `humanizer-12b-bf16.gguf`(23,832,049,568 字节,约 23.8 GB):不量化的 12B,单个 GGUF,给需要参照或想自己量化的人。
|
| 85 |
+
- 根目录的 `model.safetensors`(bf16,约 24 GB),以及 `config.json`、`generation_config.json`、`tokenizer.json`、`tokenizer_config.json`、`prompt_format.json`:transformers、vLLM 和 MLX 转换都用它们。
|
| 86 |
+
|
| 87 |
+
**量化版和 bf16 差多少**。量化文件认为最可能的下一个词和 bf16 相同的比例(中英文平均):Q8_0 98.4%、Q6_K 97.7%、Q4_K_M 95.2%、Q3 93.1%、2-bit 87.4%。Q4_K_M、Q3 和 2-bit 做过量化感知训练,并从 bf16 蒸馏过,所以比同样大小的普通 llama.cpp 量化更接近完整模型。用 README 里那个从严的事实判官看,Q8_0、Q6_K、Q4_K_M 和 bf16 的差别都在噪声范围内;Q3 和 2-bit 英文事实小错稍多(420 篇改写里标出 64 篇、70 篇,bf16 是 52 篇),多是一个词或一个数字。和普通量化的对比、KL、完整的事实检查表以及这些文件是怎么做的,见 [docs/QUANTIZATION.zh.md](https://github.com/sgaofen/humanizer-local-model/blob/main/docs/QUANTIZATION.zh.md)。
|
| 88 |
+
|
| 89 |
+
**下载:**
|
| 90 |
+
|
| 91 |
+
```bash
|
| 92 |
+
pip install -U "huggingface_hub[cli]"
|
| 93 |
+
hf download jialinyyzz/humanizer humanizer-12b-Q8_0.gguf prompt_format.json --local-dir ./humanizer-model
|
| 94 |
+
# 16 GB 的机器:把 humanizer-12b-Q8_0.gguf 换成 humanizer-12b-Q6_K.gguf(硬盘紧张就用 humanizer-12b-Q4_K_M.gguf)
|
| 95 |
+
# 内存更少:humanizer-12b-Q3-QAT.gguf,或最小的 humanizer-12b-IQ2_XS-QAT.gguf
|
| 96 |
+
# 国内下载慢:在命令前面加 HF_ENDPOINT=https://hf-mirror.com
|
| 97 |
+
wc -c ./humanizer-model/*.gguf # Q8_0 应为 12669630368 字节,Q6_K 应为 10029799584 字节,Q4_K_M 应为 7625160864 字节,
|
| 98 |
+
# Q3-QAT 应为 5587794816 字节,IQ2_XS-QAT 应为 3893632896 字节
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
sha256 校验值(macOS 用 `shasum -a 256 文件名`,Linux 用 `sha256sum 文件名`,Windows 用 `certutil -hashfile 文件名 SHA256`):
|
| 102 |
+
|
| 103 |
+
| 文件 | sha256 |
|
| 104 |
+
|---|---|
|
| 105 |
+
| `humanizer-12b-Q8_0.gguf` | `8d7a457b56de6530eaaf0151ccfa7550a4b20dab979737e259da9c63e960e0b0` |
|
| 106 |
+
| `humanizer-12b-Q6_K.gguf` | `c98f03bb9e71456181f99b0e1d3391e07ce1afc357f9db4c33d6d379b8dd9f0d` |
|
| 107 |
+
| `humanizer-12b-Q4_K_M.gguf` | `2229574dec5178629575ee4a153dfee7d9e924ab997d0ad9d2ac622b67e44834` |
|
| 108 |
+
| `humanizer-12b-Q3-QAT.gguf` | `307bbfdf66fb22bf98aaa93fe7d54a713bf167e646073dc0cf10870b1525eb94` |
|
| 109 |
+
| `humanizer-12b-IQ2_XS-QAT.gguf` | `383e5ca8f1f48ab5f65013adbc1965fa70d1d1afa6c45d932c34b854e38edbb5` |
|
| 110 |
+
| `humanizer-12b-bf16.gguf` | `47d79b44c3e15ea2540f4edb63556f2b7e456067d7dd252a42ff103a26a51be9` |
|
| 111 |
+
|
| 112 |
+
所有 GGUF 文件在 2026-10-04 换过一次:元数据里加了对话模板(给 LM Studio 等聊天软件用,见[第 3 节](#起服务)),并写入推荐的采样默认值(temperature 1.0、top-p 0.95、top-k 关、min-p 关、重复惩罚 1.0),请求里没设采样参数时 llama.cpp 就用它们。里面的权重逐字节没变,只改了文件头。在那之前下载的文件大小和校验值都是旧的,用续写接口、显式传采样参数照样能用。同一天还加了 `humanizer-12b-bf16.gguf`(23,832,049,568 字节,约 23.8 GB):不量化的完整权重,单个 GGUF,给需要参照或想自己量化的人。`humanizer-12b-Q3-QAT.gguf`(2026-10-05 加入)带同样的模板和默认值。
|
| 113 |
+
|
| 114 |
+
**手上的文件是不是最新的**?文件偶尔会用同一个名字重新上传。拿本地文件的 SHA-256 和 Hugging Face 报的比一下,不一样就重新下载(App 会自己做这件事:「…」→「检查更新」):
|
| 115 |
+
|
| 116 |
+
```bash
|
| 117 |
+
curl -sI https://huggingface.co/jialinyyzz/humanizer/resolve/main/humanizer-12b-Q8_0.gguf | grep -i '^x-linked-etag'
|
| 118 |
+
shasum -a 256 ./humanizer-model/humanizer-12b-Q8_0.gguf # Linux 用 sha256sum
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
## 3. llama.cpp(推荐)
|
| 122 |
+
|
| 123 |
+
macOS(Metal)、Windows、Linux(CUDA、Vulkan 或 CPU)都能用。App 自己用的是 llama.cpp `b11335`,这个版本或更新的都可以。
|
| 124 |
+
|
| 125 |
+
### 安装
|
| 126 |
+
|
| 127 |
+
| 系统 | 命令 |
|
| 128 |
+
|---|---|
|
| 129 |
+
| macOS | `brew install llama.cpp` |
|
| 130 |
+
| Windows | `winget install llama.cpp`,或到 [llama.cpp Releases](https://github.com/ggml-org/llama.cpp/releases) 下 zip(NVIDIA 选 CUDA 版,其他显卡选 Vulkan 版) |
|
| 131 |
+
| Linux | 到 [llama.cpp Releases](https://github.com/ggml-org/llama.cpp/releases) 下 zip,或自己编译:`cmake -B build -DGGML_CUDA=ON && cmake --build build --config Release -j`(没有 NVIDIA 显卡就去掉 `-DGGML_CUDA=ON`) |
|
| 132 |
+
|
| 133 |
+
### 起服务
|
| 134 |
+
|
| 135 |
+
```bash
|
| 136 |
+
llama-server -m ./humanizer-model/humanizer-12b-Q8_0.gguf -c 8192 -np 1 -ngl 99 --host 127.0.0.1 --port 8080
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
- `-c 8192`:指令 + 草稿 + 改写共用的上下文。
|
| 140 |
+
- `-np 1`:一次只处理一个请求,整个上下文都给它(新版本默认会把上下文分给几个并行槽位)。
|
| 141 |
+
- `-ngl 99`:所有层放到显卡上。显存不够就调低。日志里 `offloaded N/N layers` 那一行说明它跑在哪。
|
| 142 |
+
- 本地还没有文件时,把 `-m …` 换成 `--hf-repo jialinyyzz/humanizer --hf-file humanizer-12b-Q8_0.gguf`,它会自己下载。
|
| 143 |
+
|
| 144 |
+
`curl -s http://127.0.0.1:8080/health` 返回 `{"status":"ok"}` 就是准备好了。
|
| 145 |
+
|
| 146 |
+
用 `/completion` 接口,见下面的例子;不管哪天下载的文件都能用。
|
| 147 |
+
|
| 148 |
+
**聊天接口**。如果你的 GGUF 是 2026-10-04 或之后下载的(大小和 sha256 见[第 2 节](#2-选哪个文件)),并且服务带 `--jinja`(新版默认就开),`/v1/chat/completions` 也能用。文件里自带的对话模板把最后一条用户消息当作草稿,拼出和上面一模一样的提示词;系统提示词和之前的对话都不用,所以一次请求发一篇草稿。我们用 llama.cpp 核对过:模板拼出的提示词逐字相同;同一个 seed 下两个接口给出同一篇改写,都会自己停。更早下载的文件没有对话模板,用聊天接口会出怪输出。
|
| 149 |
+
|
| 150 |
+
```bash
|
| 151 |
+
jq -n --rawfile d draft.txt '{messages: [{role: "user", content: $d}],
|
| 152 |
+
temperature: 1.0, top_p: 0.95, top_k: 0, min_p: 0, repeat_penalty: 1.0, max_tokens: 2048}' \
|
| 153 |
+
| curl -s http://127.0.0.1:8080/v1/chat/completions -H "Content-Type: application/json" -d @- \
|
| 154 |
+
| jq -r '.choices[0].message.content'
|
| 155 |
+
```
|
| 156 |
+
|
| 157 |
+
### 用 Python 调用(只用标准库)
|
| 158 |
+
|
| 159 |
+
```python
|
| 160 |
+
import json, urllib.request
|
| 161 |
+
|
| 162 |
+
PF = json.load(open("humanizer-model/prompt_format.json", encoding="utf-8"))
|
| 163 |
+
|
| 164 |
+
def humanize(draft: str, url: str = "http://127.0.0.1:8080") -> str:
|
| 165 |
+
body = {
|
| 166 |
+
"prompt": PF["instr"] + "\n\n" + draft.strip() + PF["sep"],
|
| 167 |
+
"temperature": 1.0, "top_p": 0.95, "top_k": 0, "min_p": 0, "repeat_penalty": 1.0,
|
| 168 |
+
"n_predict": 2048, # 不传 "stop":模型在 EOS 处自己结束
|
| 169 |
+
}
|
| 170 |
+
req = urllib.request.Request(url + "/completion", json.dumps(body).encode("utf-8"),
|
| 171 |
+
{"Content-Type": "application/json"})
|
| 172 |
+
with urllib.request.urlopen(req, timeout=900) as r:
|
| 173 |
+
return json.load(r)["content"].strip()
|
| 174 |
+
|
| 175 |
+
print(humanize(open("draft.txt", encoding="utf-8").read()))
|
| 176 |
+
```
|
| 177 |
+
|
| 178 |
+
要流式输出就加 `"stream": true`,读服务端推送的事件,每个事件的 `content` 是接下来的一段文字。
|
| 179 |
+
|
| 180 |
+
### 用 curl 调用(macOS 和 Linux,需要 jq)
|
| 181 |
+
|
| 182 |
+
```bash
|
| 183 |
+
jq -n --rawfile d draft.txt --slurpfile f humanizer-model/prompt_format.json \
|
| 184 |
+
'{prompt: ($f[0].instr + "\n\n" + ($d | sub("^\\s+"; "") | sub("\\s+$"; "")) + $f[0].sep),
|
| 185 |
+
temperature: 1.0, top_p: 0.95, top_k: 0, min_p: 0, repeat_penalty: 1.0, n_predict: 2048}' \
|
| 186 |
+
| curl -s http://127.0.0.1:8080/completion -d @- | jq -r .content
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
### 不起服务,一次性跑
|
| 190 |
+
|
| 191 |
+
新版 llama.cpp 里,纯文本续写工具叫 `llama-completion`;老版本用 `llama-cli`,参数一样。`-no-cnv` 让它不进入聊天模式。
|
| 192 |
+
|
| 193 |
+
```bash
|
| 194 |
+
# 1) 把逐字的提示词写进文件。结尾多一个 "\n" 是故意的:
|
| 195 |
+
# llama.cpp 用 -f 读文件时会去掉恰好一个结尾换行。
|
| 196 |
+
python3 - <<'EOF'
|
| 197 |
+
import json
|
| 198 |
+
pf = json.load(open("humanizer-model/prompt_format.json", encoding="utf-8"))
|
| 199 |
+
draft = open("draft.txt", encoding="utf-8").read()
|
| 200 |
+
with open("prompt.txt", "w", encoding="utf-8", newline="") as f:
|
| 201 |
+
f.write(pf["instr"] + "\n\n" + draft.strip() + pf["sep"] + "\n")
|
| 202 |
+
EOF
|
| 203 |
+
|
| 204 |
+
# 2) 跑一次,只打印改写
|
| 205 |
+
llama-completion -m ./humanizer-model/humanizer-12b-Q8_0.gguf -f prompt.txt -c 8192 -n 2048 -ngl 99 \
|
| 206 |
+
-no-cnv --no-display-prompt --temp 1.0 --top-p 0.95 --top-k 0 --min-p 0 --repeat-penalty 1.0
|
| 207 |
+
```
|
| 208 |
+
|
| 209 |
+
我们自己用的是 llama-server,这条一次性跑的路没有测过。第一次跑时加上 `--verbose-prompt` 看看切好的提示词:结尾必须是 `Rewritten`、`:` 和一个空行,后面不能再有东西。
|
| 210 |
+
|
| 211 |
+
## 4. MLX(Apple 芯片)
|
| 212 |
+
|
| 213 |
+
需要 mlx-lm 0.32 或更新。12B 没有现成的 MLX 文件,要先把 bf16 权重转成 8 bit(只需转一次)。转换要读 24 GB 的 bf16 下载,32 GB 及以上内存的 Mac 比较从容。我们只实测过 8 bit 的转换:M5 Max 上英文约 30 token/s,中文约 38 token/s。
|
| 214 |
+
|
| 215 |
+
```bash
|
| 216 |
+
pip install -U "mlx-lm>=0.32" huggingface_hub
|
| 217 |
+
mlx_lm.convert --hf-path jialinyyzz/humanizer --mlx-path humanizer-mlx-8bit -q --q-bits 8 --q-group-size 64
|
| 218 |
+
```
|
| 219 |
+
|
| 220 |
+
```python
|
| 221 |
+
import json
|
| 222 |
+
from huggingface_hub import hf_hub_download
|
| 223 |
+
from mlx_lm import load, generate
|
| 224 |
+
from mlx_lm.sample_utils import make_sampler
|
| 225 |
+
|
| 226 |
+
PF = json.load(open(hf_hub_download("jialinyyzz/humanizer", "prompt_format.json"), encoding="utf-8"))
|
| 227 |
+
model, tok = load("humanizer-mlx-8bit")
|
| 228 |
+
sampler = make_sampler(temp=1.0, top_p=0.95) # top-k、min-p 保持默认的关闭状态
|
| 229 |
+
|
| 230 |
+
def humanize(draft: str) -> str:
|
| 231 |
+
prompt = PF["instr"] + "\n\n" + draft.strip() + PF["sep"]
|
| 232 |
+
return generate(model, tok, prompt=prompt, max_tokens=2048, sampler=sampler).strip()
|
| 233 |
+
|
| 234 |
+
print(humanize(open("draft.txt", encoding="utf-8").read()))
|
| 235 |
+
```
|
| 236 |
+
|
| 237 |
+
给 `generate()` 传纯字符串,不要调用 `tok.apply_chat_template`。命令行工具 `mlx_lm.generate` 默认会套聊天模板,要加 `--ignore-chat-template`;上面的 Python 接口不会套。
|
| 238 |
+
|
| 239 |
+
## 5. transformers(CUDA)
|
| 240 |
+
|
| 241 |
+
bf16 权重约 24 GB,显存要比这更大,或者用 `device_map="auto"` 把模型分到几张卡上。transformers 要用支持 Gemma 4 的版本,权重是用 5.14.1 存的。
|
| 242 |
+
|
| 243 |
+
```bash
|
| 244 |
+
pip install -U torch transformers accelerate huggingface_hub
|
| 245 |
+
```
|
| 246 |
+
|
| 247 |
+
```python
|
| 248 |
+
import json, torch
|
| 249 |
+
from huggingface_hub import hf_hub_download
|
| 250 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 251 |
+
|
| 252 |
+
repo = "jialinyyzz/humanizer"
|
| 253 |
+
PF = json.load(open(hf_hub_download(repo, "prompt_format.json"), encoding="utf-8"))
|
| 254 |
+
tok = AutoTokenizer.from_pretrained(repo)
|
| 255 |
+
model = AutoModelForCausalLM.from_pretrained(repo, dtype=torch.bfloat16, device_map="auto")
|
| 256 |
+
|
| 257 |
+
def humanize(draft: str) -> str:
|
| 258 |
+
ids = tok(PF["instr"] + "\n\n" + draft.strip() + PF["sep"], return_tensors="pt").to(model.device)
|
| 259 |
+
out = model.generate(**ids, do_sample=True, temperature=1.0, top_p=0.95,
|
| 260 |
+
top_k=0, # 关掉 generation_config.json 里的 top-k 64
|
| 261 |
+
max_new_tokens=2048)
|
| 262 |
+
return tok.decode(out[0, ids["input_ids"].shape[1]:], skip_special_tokens=True).strip()
|
| 263 |
+
|
| 264 |
+
print(humanize(open("draft.txt", encoding="utf-8").read()))
|
| 265 |
+
```
|
| 266 |
+
|
| 267 |
+
transformers 4.x 要把 `dtype=` 写成 `torch_dtype=`。`top_k=0` 不能省,省了就是在 top-k 64 下采样。
|
| 268 |
+
|
| 269 |
+
## 6. vLLM
|
| 270 |
+
|
| 271 |
+
下面的离线接口和我们生成评测输出的方式一致(vLLM、bf16、temperature 1.0、top-p 0.95);评测另外还用了防照抄重采。
|
| 272 |
+
|
| 273 |
+
```python
|
| 274 |
+
import json
|
| 275 |
+
from huggingface_hub import hf_hub_download
|
| 276 |
+
from vllm import LLM, SamplingParams
|
| 277 |
+
|
| 278 |
+
repo = "jialinyyzz/humanizer"
|
| 279 |
+
PF = json.load(open(hf_hub_download(repo, "prompt_format.json"), encoding="utf-8"))
|
| 280 |
+
llm = LLM(model=repo, dtype="bfloat16", max_model_len=8192,
|
| 281 |
+
limit_mm_per_prompt={"image": 0, "audio": 0, "video": 0}) # 只用文本
|
| 282 |
+
params = SamplingParams(temperature=1.0, top_p=0.95, top_k=-1, min_p=0.0,
|
| 283 |
+
repetition_penalty=1.0, max_tokens=2048) # top_k=-1 表示关闭
|
| 284 |
+
|
| 285 |
+
drafts = [open(p, encoding="utf-8").read() for p in ["draft1.txt", "draft2.txt"]]
|
| 286 |
+
prompts = [PF["instr"] + "\n\n" + d.strip() + PF["sep"] for d in drafts]
|
| 287 |
+
for result in llm.generate(prompts, params):
|
| 288 |
+
print(result.outputs[0].text.strip(), "\n---")
|
| 289 |
+
```
|
| 290 |
+
|
| 291 |
+
起成服务(兼容 OpenAI 接口)。`--generation-config vllm` 让 vLLM 不把 `generation_config.json` 里的 top-k 64 当默认值:
|
| 292 |
+
|
| 293 |
+
```bash
|
| 294 |
+
vllm serve jialinyyzz/humanizer --dtype bfloat16 --max-model-len 8192 --generation-config vllm
|
| 295 |
+
```
|
| 296 |
+
|
| 297 |
+
```bash
|
| 298 |
+
jq -n --rawfile d draft.txt --slurpfile f humanizer-model/prompt_format.json \
|
| 299 |
+
'{model: "jialinyyzz/humanizer",
|
| 300 |
+
prompt: ($f[0].instr + "\n\n" + ($d | sub("^\\s+"; "") | sub("\\s+$"; "")) + $f[0].sep),
|
| 301 |
+
temperature: 1.0, top_p: 0.95, top_k: -1, min_p: 0, repetition_penalty: 1.0, max_tokens: 2048}' \
|
| 302 |
+
| curl -s http://127.0.0.1:8000/v1/completions -H "Content-Type: application/json" -d @- \
|
| 303 |
+
| jq -r '.choices[0].text'
|
| 304 |
+
```
|
| 305 |
+
|
| 306 |
+
用 `/v1/completions`,不要用 `/v1/chat/completions`。服务这条路我们自己没测过。
|
| 307 |
+
|
| 308 |
+
## 7. Ollama
|
| 309 |
+
|
| 310 |
+
Ollama 不用 GGUF 里存的对话模板,要在 `Modelfile` 里写它自己的模板。Ollama 的引擎要支持 Gemma 4 才能加载。Ollama 我们自己没跑过:下面的模板我们用 Go 的 `text/template`(Ollama 模板用的就是这套语法)渲染过,结果和[第 1 节](#1-这个模型哪里特殊)的提示词逐字相同,但没有在 Ollama 里实际试过。
|
| 311 |
+
|
| 312 |
+
`Modelfile`(和 GGUF 放在一起)。模板把最后一条用户消息当作草稿,按第 1 节原样拼好;系统提示词和之前的对话都不用,每条消息各改各的。
|
| 313 |
+
|
| 314 |
+
```
|
| 315 |
+
FROM ./humanizer-12b-Q8_0.gguf
|
| 316 |
+
TEMPLATE """{{- $draft := "" }}{{- range .Messages }}{{- if eq .Role "user" }}{{- $draft = .Content }}{{- end }}{{- end }}Rewrite the text below so it reads like a person wrote it, not a language model.
|
| 317 |
+
|
| 318 |
+
Reorganize it as you see fit. Vary sentence length on purpose. Cut hedging,
|
| 319 |
+
throat-clearing, and any sentence that only announces what comes next.
|
| 320 |
+
Prefer the concrete word over the abstract one. It is fine to sound uneven.
|
| 321 |
+
|
| 322 |
+
Every fact, number, unit, date, name and quotation must survive unchanged.
|
| 323 |
+
|
| 324 |
+
{{ $draft }}
|
| 325 |
+
|
| 326 |
+
### Rewritten:{{ "\n\n" }}"""
|
| 327 |
+
PARAMETER temperature 1.0
|
| 328 |
+
PARAMETER top_p 0.95
|
| 329 |
+
PARAMETER top_k 0
|
| 330 |
+
PARAMETER min_p 0
|
| 331 |
+
PARAMETER repeat_penalty 1.0
|
| 332 |
+
PARAMETER num_ctx 8192
|
| 333 |
+
PARAMETER num_predict 2048
|
| 334 |
+
```
|
| 335 |
+
|
| 336 |
+
```bash
|
| 337 |
+
ollama create humanizer -f Modelfile
|
| 338 |
+
ollama show humanizer --modelfile # 确认没有被自动加上 "PARAMETER stop" 之类的行
|
| 339 |
+
ollama run humanizer # 然后一条消息贴一篇草稿
|
| 340 |
+
```
|
| 341 |
+
|
| 342 |
+
结尾的 `{{ "\n\n" }}` 就是 `### Rewritten:` 后面那个空行,这样写是为了模板末尾的空白万一被裁掉也还在。
|
| 343 |
+
|
| 344 |
+
**聊天模式**(`ollama run`、`/api/chat`):把草稿当作用户消息发。Ollama 模板没法去掉首尾空白,所以别带开头和结尾的空行(写代码就发 `draft.strip()`),否则提示词就和训练时不完全一样了。
|
| 345 |
+
|
| 346 |
+
**原始(raw)模式**不经过模板,用哪个 Modelfile 都行。调 `/api/generate`,带 `"raw": true` 和完整提示词(指令 + 草稿 + 分隔符):
|
| 347 |
+
|
| 348 |
+
```bash
|
| 349 |
+
jq -n --rawfile d draft.txt --slurpfile f humanizer-model/prompt_format.json \
|
| 350 |
+
'{model: "humanizer", raw: true, stream: false,
|
| 351 |
+
prompt: ($f[0].instr + "\n\n" + ($d | sub("^\\s+"; "") | sub("\\s+$"; "")) + $f[0].sep),
|
| 352 |
+
options: {temperature: 1.0, top_p: 0.95, top_k: 0, min_p: 0, repeat_penalty: 1.0,
|
| 353 |
+
num_ctx: 8192, num_predict: 2048}}' \
|
| 354 |
+
| curl -s http://127.0.0.1:11434/api/generate -d @- | jq -r .response
|
| 355 |
+
```
|
| 356 |
+
|
| 357 |
+
Python:
|
| 358 |
+
|
| 359 |
+
```python
|
| 360 |
+
import json, urllib.request
|
| 361 |
+
|
| 362 |
+
PF = json.load(open("humanizer-model/prompt_format.json", encoding="utf-8"))
|
| 363 |
+
draft = open("draft.txt", encoding="utf-8").read()
|
| 364 |
+
body = {"model": "humanizer", "raw": True, "stream": False,
|
| 365 |
+
"prompt": PF["instr"] + "\n\n" + draft.strip() + PF["sep"],
|
| 366 |
+
"options": {"temperature": 1.0, "top_p": 0.95, "top_k": 0, "min_p": 0, "repeat_penalty": 1.0,
|
| 367 |
+
"num_ctx": 8192, "num_predict": 2048}}
|
| 368 |
+
req = urllib.request.Request("http://127.0.0.1:11434/api/generate", json.dumps(body).encode("utf-8"),
|
| 369 |
+
{"Content-Type": "application/json"})
|
| 370 |
+
print(json.load(urllib.request.urlopen(req, timeout=900))["response"].strip())
|
| 371 |
+
```
|
| 372 |
+
|
| 373 |
+
Ollama 自带的 Gemma 模板,或者没写上面那段 `TEMPLATE` 的 Modelfile,在聊天模式下都会把这个模型搞坏。
|
| 374 |
+
|
| 375 |
+
## 8. LM Studio
|
| 376 |
+
|
| 377 |
+
LM Studio 我们自己没测过。LM Studio 用的是 GGUF 里存的对话模板。2026-10-04 及之后上传的文件自带一个模板,拼出的就是[第 1 节](#1-这个模型哪里特殊)的提示词(我们用 llama.cpp 核对过,见[第 3 节](#起服务));更早下载的文件没有,请重新下载,或者用第 4 步的续写接口。
|
| 378 |
+
|
| 379 |
+
1. 加载 `humanizer-12b-Q8_0.gguf`(或 Q6_K / Q4_K_M / Q3-QAT / IQ2_XS-QAT),加载时把上下文长度设成 8192。
|
| 380 |
+
2. 在模型的采样设置里设 **Temperature 1.0、Top P 0.95、Top K 0、Min P 0、Repeat Penalty 1.0**,删掉所有停止符。提示词模板保持文件自带的,别改。
|
| 381 |
+
3. **聊天页面**:系统提示词留空(模板反正不用它),一条消息贴一篇草稿。每条消息各改各的,之前的对话不会发给模型。本地服务的 `/v1/chat/completions` 也是这样。
|
| 382 |
+
4. **文本续写**(哪天下载的文件都能用):在 Developer 页开本地服务,把完整提示词(指令 + 草稿 + 分隔符)发到 **`/v1/completions`**:
|
| 383 |
+
|
| 384 |
+
```python
|
| 385 |
+
import json, urllib.request
|
| 386 |
+
|
| 387 |
+
PF = json.load(open("humanizer-model/prompt_format.json", encoding="utf-8"))
|
| 388 |
+
draft = open("draft.txt", encoding="utf-8").read()
|
| 389 |
+
body = {"model": "humanizer-12b-q8_0", # 换成 LM Studio 里显示的模型 id
|
| 390 |
+
"prompt": PF["instr"] + "\n\n" + draft.strip() + PF["sep"],
|
| 391 |
+
"temperature": 1.0, "top_p": 0.95, "top_k": 0, "min_p": 0, "repeat_penalty": 1.0,
|
| 392 |
+
"max_tokens": 2048}
|
| 393 |
+
req = urllib.request.Request("http://127.0.0.1:1234/v1/completions", json.dumps(body).encode("utf-8"),
|
| 394 |
+
{"Content-Type": "application/json"})
|
| 395 |
+
print(json.load(urllib.request.urlopen(req, timeout=900))["choices"][0]["text"].strip())
|
| 396 |
+
```
|
| 397 |
+
|
| 398 |
+
如果聊天回复以“Sure”开头、复述指令或者停不下来,说明文件里没有自带模板(2026-10-04 以前下载的),或者模型设置里的提示词模板被改过:重新下载文件、把模板恢复原样,或者改用 `/v1/completions`。如果你的 LM Studio 版本不认请求里的 `top_k`、`min_p`、`repeat_penalty`,第 2 步的设置会生效。
|
| 399 |
+
|
| 400 |
+
## 9. 批量改写一个文件夹
|
| 401 |
+
|
| 402 |
+
`humanize_folder.py` 通过正在运行的 llama-server(见[第 3 节](#起服务)),把一个文件夹里的每个 `.txt` 改写到另一个文件夹。只用标准库。
|
| 403 |
+
|
| 404 |
+
- 长文件按空行切成若干段,每段的草稿不超过 `--max-tokens` 个 token;逐段改写后用空行拼起来。
|
| 405 |
+
- 某段改写照抄草稿超过 `--max-copy`(默认 0.35)时,这段会再采一次,保留照抄更少的那一版。
|
| 406 |
+
- 草稿里有、改写里找不到的数字写进 `report.tsv`,供你人工核对。
|
| 407 |
+
- 输出文件夹里已经有的文件会跳过,可以中途停下再接着跑。
|
| 408 |
+
|
| 409 |
+
```bash
|
| 410 |
+
python3 humanize_folder.py drafts/ rewrites/
|
| 411 |
+
python3 humanize_folder.py drafts/ rewrites/ --url http://127.0.0.1:8080 --max-tokens 1500
|
| 412 |
+
```
|
| 413 |
+
|
| 414 |
+
```python
|
| 415 |
+
#!/usr/bin/env python3
|
| 416 |
+
"""Rewrite every .txt file in a folder with humanizer, through a running llama-server.
|
| 417 |
+
|
| 418 |
+
python3 humanize_folder.py drafts/ rewrites/
|
| 419 |
+
python3 humanize_folder.py drafts/ rewrites/ --url http://127.0.0.1:8080 --max-tokens 1500
|
| 420 |
+
|
| 421 |
+
Standard library only. Long files are split at blank lines into pieces of at most --max-tokens
|
| 422 |
+
draft tokens; each piece is rewritten on its own and the pieces are joined with a blank line.
|
| 423 |
+
A piece whose rewrite copies more than --max-copy of the draft is sampled once more and the
|
| 424 |
+
less-copied version is kept. Numbers that appear in a draft but not in its rewrite are listed in
|
| 425 |
+
report.tsv so you can check them by hand. Files already in the output folder are skipped.
|
| 426 |
+
"""
|
| 427 |
+
import argparse, hashlib, json, math, os, re, sys, urllib.request
|
| 428 |
+
|
| 429 |
+
INSTR = (
|
| 430 |
+
"Rewrite the text below so it reads like a person wrote it, not a language model.\n"
|
| 431 |
+
"\n"
|
| 432 |
+
"Reorganize it as you see fit. Vary sentence length on purpose. Cut hedging,\n"
|
| 433 |
+
"throat-clearing, and any sentence that only announces what comes next.\n"
|
| 434 |
+
"Prefer the concrete word over the abstract one. It is fine to sound uneven.\n"
|
| 435 |
+
"\n"
|
| 436 |
+
"Every fact, number, unit, date, name and quotation must survive unchanged."
|
| 437 |
+
)
|
| 438 |
+
SEP = "\n\n### Rewritten:\n\n"
|
| 439 |
+
|
| 440 |
+
|
| 441 |
+
def build_prompt(draft):
|
| 442 |
+
return INSTR + "\n\n" + draft.strip() + SEP
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
assert hashlib.sha256(build_prompt("X").encode("utf-8")).hexdigest()[:16] == "cc51d66b4c593fbe"
|
| 446 |
+
|
| 447 |
+
|
| 448 |
+
def post(url, path, body, timeout=1800):
|
| 449 |
+
req = urllib.request.Request(url.rstrip("/") + path, json.dumps(body).encode("utf-8"),
|
| 450 |
+
{"Content-Type": "application/json"})
|
| 451 |
+
with urllib.request.urlopen(req, timeout=timeout) as r:
|
| 452 |
+
return json.load(r)
|
| 453 |
+
|
| 454 |
+
|
| 455 |
+
def n_tokens(url, text):
|
| 456 |
+
return len(post(url, "/tokenize", {"content": text})["tokens"])
|
| 457 |
+
|
| 458 |
+
|
| 459 |
+
def split_long(url, text, max_tokens):
|
| 460 |
+
"""Group paragraphs (split at blank lines) into pieces of at most max_tokens tokens."""
|
| 461 |
+
paras = [p.strip() for p in re.split(r"\n\s*\n", text.strip()) if p.strip()]
|
| 462 |
+
pieces, cur = [], []
|
| 463 |
+
for p in paras:
|
| 464 |
+
if cur and n_tokens(url, "\n\n".join(cur + [p])) > max_tokens:
|
| 465 |
+
pieces.append("\n\n".join(cur))
|
| 466 |
+
cur = []
|
| 467 |
+
cur.append(p)
|
| 468 |
+
if cur:
|
| 469 |
+
pieces.append("\n\n".join(cur))
|
| 470 |
+
return pieces
|
| 471 |
+
|
| 472 |
+
|
| 473 |
+
def rewrite(url, draft, ctx):
|
| 474 |
+
prompt = build_prompt(draft)
|
| 475 |
+
room = ctx - n_tokens(url, prompt) - 16
|
| 476 |
+
n_predict = max(256, min(math.ceil(n_tokens(url, draft) * 2.5), room))
|
| 477 |
+
body = {"prompt": prompt, "n_predict": n_predict, "temperature": 1.0, "top_p": 0.95,
|
| 478 |
+
"top_k": 0, "min_p": 0, "repeat_penalty": 1.0} # no "stop": ends at EOS
|
| 479 |
+
return post(url, "/completion", body)["content"].strip()
|
| 480 |
+
|
| 481 |
+
|
| 482 |
+
# Rough copy ratio: share of the rewrite's 5-grams (words; single CJK characters) found in the draft.
|
| 483 |
+
UNIT = re.compile(r"[㐀-鿿]|[^\W_㐀-鿿]+|[^\w\s]")
|
| 484 |
+
|
| 485 |
+
|
| 486 |
+
def copy_ratio(draft, out, n=5):
|
| 487 |
+
a = [t.lower() for t in UNIT.findall(draft)]
|
| 488 |
+
b = [t.lower() for t in UNIT.findall(out)]
|
| 489 |
+
seen = {tuple(a[i:i + n]) for i in range(len(a) - n + 1)}
|
| 490 |
+
grams = [tuple(b[i:i + n]) for i in range(len(b) - n + 1)]
|
| 491 |
+
return sum(g in seen for g in grams) / len(grams) if grams else 0.0
|
| 492 |
+
|
| 493 |
+
|
| 494 |
+
NUM = re.compile(r"\d+(?:[.,:/]\d+)*")
|
| 495 |
+
|
| 496 |
+
|
| 497 |
+
def missing_numbers(draft, out):
|
| 498 |
+
have = {m.replace(",", "") for m in NUM.findall(out)} | set(re.findall(r"\d+", out))
|
| 499 |
+
return sorted({m.replace(",", "") for m in NUM.findall(draft)} - have)
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
def main():
|
| 503 |
+
ap = argparse.ArgumentParser()
|
| 504 |
+
ap.add_argument("src"); ap.add_argument("dst")
|
| 505 |
+
ap.add_argument("--url", default="http://127.0.0.1:8080")
|
| 506 |
+
ap.add_argument("--ctx", type=int, default=8192, help="the -c you gave llama-server")
|
| 507 |
+
ap.add_argument("--max-tokens", type=int, default=1500, help="max draft tokens per piece")
|
| 508 |
+
ap.add_argument("--max-copy", type=float, default=0.35, help="resample a piece once above this")
|
| 509 |
+
a = ap.parse_args()
|
| 510 |
+
os.makedirs(a.dst, exist_ok=True)
|
| 511 |
+
files = sorted(f for f in os.listdir(a.src) if f.lower().endswith(".txt"))
|
| 512 |
+
report = open(os.path.join(a.dst, "report.tsv"), "a", encoding="utf-8")
|
| 513 |
+
for i, name in enumerate(files, 1):
|
| 514 |
+
out_path = os.path.join(a.dst, name)
|
| 515 |
+
if os.path.exists(out_path):
|
| 516 |
+
print(f"[{i}/{len(files)}] {name}: already done, skipped"); continue
|
| 517 |
+
draft = open(os.path.join(a.src, name), encoding="utf-8").read()
|
| 518 |
+
if not draft.strip():
|
| 519 |
+
continue
|
| 520 |
+
parts = []
|
| 521 |
+
for piece in split_long(a.url, draft, a.max_tokens):
|
| 522 |
+
out = rewrite(a.url, piece, a.ctx)
|
| 523 |
+
c = copy_ratio(piece, out)
|
| 524 |
+
if c > a.max_copy:
|
| 525 |
+
out2 = rewrite(a.url, piece, a.ctx)
|
| 526 |
+
c2 = copy_ratio(piece, out2)
|
| 527 |
+
if c2 < c:
|
| 528 |
+
out, c = out2, c2
|
| 529 |
+
parts.append(out)
|
| 530 |
+
text = "\n\n".join(parts)
|
| 531 |
+
with open(out_path, "w", encoding="utf-8", newline="") as f:
|
| 532 |
+
f.write(text + "\n")
|
| 533 |
+
miss = missing_numbers(draft, text)
|
| 534 |
+
report.write(f"{name}\t{len(parts)} piece(s)\tcopy {copy_ratio(draft, text):.2f}\tcheck numbers: {' '.join(miss) or '-'}\n")
|
| 535 |
+
report.flush()
|
| 536 |
+
print(f"[{i}/{len(files)}] {name}: {len(parts)} piece(s), copy {copy_ratio(draft, text):.2f}"
|
| 537 |
+
+ (f", check numbers {miss}" if miss else ""))
|
| 538 |
+
|
| 539 |
+
|
| 540 |
+
if __name__ == "__main__":
|
| 541 |
+
main()
|
| 542 |
+
```
|
| 543 |
+
|
| 544 |
+
这里的照抄率是粗略估计(改写里连续 5 个词或 5 个字的片段有多少出现在草稿里),不是我们评测用的精确指标;第二次采样也不带防照抄惩罚,和评测里的重采不同。
|
| 545 |
+
|
| 546 |
+
## 10. 长文
|
| 547 |
+
|
| 548 |
+
- 指令、草稿和改写加起来共用 **8192 token** 的上下文。改写和草稿差不多长,所以每段草稿要明显少于一半。
|
| 549 |
+
- 按**段落**(空行)切开,不要在句子中间切,然后分段改写。上面的批处理脚本就是这么做的,默认每段最多 1,500 token。
|
| 550 |
+
- 模型训练和评测用的都是单篇邮件、帖子、作文、报告段落,几百词的长度,这个长度的段效果最好。
|
| 551 |
+
- 每段改写时看不到其他段,所以段与段之间语气可能略有变化,事实也不会在段之间挪动。拼好之后从头到尾读一遍。
|
| 552 |
+
- 标题、代码块和列表常被删掉或改成正文。只改写正文,标题和代码自己留着;[`humanizer/markdown_guard.py`](https://github.com/sgaofen/humanizer-local-model/blob/main/humanizer/markdown_guard.py) 就是这样逐块处理的。
|
| 553 |
+
- **[`hz`](#14-命令行工具-hz) 一行命令就能做完上面这些**,支持 `.md`、`.txt` 和 `.docx`:标题、代码、表格、链接原样保留,正文按约 350 个英文词(中文约 600 字)一块切开,不跨标题,每块都做检查。
|
| 554 |
+
- App(0.3.2 及以后)可以导入 `.docx` 和 PDF,草稿太长时会提示,但它是整篇一次改写;它的「原样保留」(Create fact)把选中的文字换成占位符送给模型,占位符没原样回来就重写,从而保证这段文字一字不改。长文档或有结构的文档仍然推荐用 `hz`。
|
| 555 |
+
|
| 556 |
+
## 11. 中文
|
| 557 |
+
|
| 558 |
+
- 用**同一段英文指令**,不要翻译。
|
| 559 |
+
- 中文**还在追赶英文**:204 篇中文改写里,事实判官在 149 篇里没挑出问题;有问题的,约 9 成改一个词或短语就好。
|
| 560 |
+
- 中文改写更容易照抄草稿。用 Q8_0 文件时,中文 204 发里有 14 发照抄草稿超过 35%,英文 420 发里只有 1 发。改写看着和草稿太像就再采一次;批处理脚本会自动这样做。
|
| 561 |
+
- 中文里**数字更常换写法**:汉字数字变成阿拉伯数字(三 → 3),日期改格式(6月14日 → 6.14)。App 和批处理脚本里那种只看阿拉伯数字的核对发现不了这类问题,请读一遍核对。
|
| 562 |
+
- 全角、半角标点可能互换;问候、正文、落款有时会被并成一段。发出去之前把格式理一理。
|
| 563 |
+
- 速度和英文差不多:llama.cpp Q8_0 在 M5 Max 上,一封 300 字左右的中文邮件约 8.5 秒。
|
| 564 |
+
|
| 565 |
+
## 12. 质量检查清单
|
| 566 |
+
|
| 567 |
+
- **通读一遍改写**。核对每个数字、日期、单位和人名,以及每个论断的方向(谁做了什么、多还是少、先还是后)。在我们的评测集上,英文 420 篇改写里有 376 篇判官没挑出事实问题;有问题的,9 成以上改一个词或短语就好,比如“37 complaints”(37 条投诉)变成了“37% of complaints”(37% 的投诉)。
|
| 568 |
+
- **补回需要的格式**:主题行、列表、标题、落款有时会丢(420 发里有 28 发)。
|
| 569 |
+
- **和草稿太像?再采一次**。每次都是重新采样。
|
| 570 |
+
- **随意体裁里语气会跑**。Reddit 一类的帖子里,它有时会加上原文没有的俚语或粗口,请删掉。
|
| 571 |
+
- **不保证过检测器**。我们的检测数字是一个检测器在某一天的一次测量;模板化的体裁(带 emoji、井号的社交帖,政策备忘)仍然最常被判 AI。这里不对任何检测结果做承诺。
|
| 572 |
+
- 这是给你改自己草稿的写作工具。学校、单位或出版方对 AI 辅助有规定的,请按规定来。
|
| 573 |
+
|
| 574 |
+
## 13. 排错
|
| 575 |
+
|
| 576 |
+
| 现象 | 原因 | 解决 |
|
| 577 |
+
|---|---|---|
|
| 578 |
+
| 输出以“好的”“Sure”“Here is…”开头、复述指令,或者停不下来 | 套上了通用的聊天模板:GGUF 是 2026-10-04 以前下载的(没有自带模板)、软件设置里改过模板、Ollama 没用[第 7 节](#7-ollama)的 Modelfile,或者对 safetensors 权重用了聊天接口 | 重新下载 GGUF,或者用续写接口(`/completion`、`/v1/completions`、Ollama 的 `raw: true`),发[第 1 节](#1-这个模型哪里特殊)里逐字的提示词 |
|
| 579 |
+
| 输出里有 `<start_of_turn>`、`<end_of_turn>` 之类的标记 | 同上:聊天格式 | 同上 |
|
| 580 |
+
| 输出在 `###` 处或很早就停了 | 设了停止符 | 删掉所有停止符,只靠 EOS |
|
| 581 |
+
| 英文草稿被写成了中文(偶尔发生,多见于夹技术术语的英文口语短稿) | 采样运气 | 英文草稿的输出里出现大段中文,就用同样的参数再采一次。App(0.3.1 及以后���和 `hz` 会自动做,最多 3 次 |
|
| 582 |
+
| 改写和草稿几乎一样 | 采样运气不好,或温度太低 | 确认 temperature 1.0,再采一次 |
|
| 583 |
+
| 胡言乱语、用词古怪或反复重复 | 采样参数不对(llama.cpp 默认的 top-k 40 / min-p 0.05、`generation_config.json` 里的 top-k 64,或者开了重复惩罚) | 显式设 top-k 0、min-p 0、重复惩罚 1.0 |
|
| 584 |
+
| 改写在句子中间断了 | 输出上限或上下文太小 | 调大 `n_predict` / `max_tokens`;llama-server 加 `-c 8192 -np 1`;长稿分段 |
|
| 585 |
+
| 加载时内存不够 | 文件对你的内存或显存太大 | 16 GB 用 Q6_K,14 GB 左右用 Q4_K_M,12 GB 用 Q3-QAT,8 GB 用 2 bit 的 IQ2_XS-QAT;调低 `-ngl` |
|
| 586 |
+
| 很慢 | 在用 CPU 跑 | 看 llama.cpp 日志里有没有 `offloaded N/N layers`;装 Metal、CUDA 或 Vulkan 版 |
|
| 587 |
+
| 下载时 404 | 文件名写错 | 用[第 2 节](#2-选哪个文件)里的文件名 |
|
| 588 |
+
| 指纹自检不过 | 提示词拼法和训练时不一样 | 直接复制[第 1 节](#提示词)里的 `build_prompt` |
|
| 589 |
+
|
| 590 |
+
**自检**。[AGENTS.md 第 8 节](https://github.com/sgaofen/humanizer-local-model/blob/main/AGENTS.md#8-self-test-verify-the-install)有一个只用标准库的脚本:它把评测集里的一篇真实草稿发给 llama-server,检查提示词格式、接口、聊天模板有没有漏进来、有没有照抄。设置正确会打印 `PASS`。
|
| 591 |
+
|
| 592 |
+
## 14. 命令行工具 hz
|
| 593 |
+
|
| 594 |
+
`hz` 一行命令改写一篇草稿或一整份文档,人和 AI Agent 用法一样。它连接 [App](https://github.com/sgaofen/humanizer-local-model/releases/latest) 或 llama-server([第 3 节](#起服务)),逐字拼好提示词,用评测时的采样参数,把长文切块、保留结构,并检查每一块。需要 Python 3.8 及以上,只用标准库;`.docx` 支持是可选的。
|
| 595 |
+
|
| 596 |
+
### 安装
|
| 597 |
+
|
| 598 |
+
```bash
|
| 599 |
+
pipx install git+https://github.com/sgaofen/humanizer-local-model
|
| 600 |
+
pipx inject humanize-model python-docx # 只有要处理 .docx 时才需要
|
| 601 |
+
|
| 602 |
+
# 或者用 pip,装进任意环境:
|
| 603 |
+
pip install git+https://github.com/sgaofen/humanizer-local-model
|
| 604 |
+
pip install "humanize-model[docx] @ git+https://github.com/sgaofen/humanizer-local-model" # 连同 .docx 支持
|
| 605 |
+
|
| 606 |
+
# 克隆了仓库、不想安装:
|
| 607 |
+
python3 -m humanizer.hz draft.txt
|
| 608 |
+
```
|
| 609 |
+
|
| 610 |
+
装好后只有一个命令 `hz`。如果你机器上已经有别的程序也叫 `hz`,以 `PATH` 里排在前面的为准。
|
| 611 |
+
|
| 612 |
+
### 用法
|
| 613 |
+
|
| 614 |
+
```bash
|
| 615 |
+
hz draft.txt # 改写结果打印到 stdout
|
| 616 |
+
hz < draft.txt > rewrite.txt
|
| 617 |
+
hz paper.md -o paper.out.md # 长篇 Markdown:结构保留,正文分块改写
|
| 618 |
+
hz report.docx -o report.out.docx # 不写 -o 时输出到 report.hz.docx
|
| 619 |
+
hz paper.md --json > report.json # 每块的统计;有 -o 时正文写进文件
|
| 620 |
+
hz paper.md --dry-run # 只看怎么切块,不调用模型
|
| 621 |
+
hz paper.md --server http://127.0.0.1:8080 # 指定某个 llama-server
|
| 622 |
+
```
|
| 623 |
+
|
| 624 |
+
进度打印在 stderr,每块一行。`--quiet` 不打印进度;需要人工核对的块无论如何都会列出来。
|
| 625 |
+
|
| 626 |
+
### 它怎么找模型
|
| 627 |
+
|
| 628 |
+
1. 给了 `--server URL`:只用这个 llama-server。(如果这个地址其实是 App,就按 App 的方式调用。)
|
| 629 |
+
2. 否则先找 **App**:正在运行且状态为 ready 就用它。端口从 App 的 `instance.json` 里读,读不到就用 `http://127.0.0.1:47615`。请求发到 `POST /api/completion`,带上 App 要求的 `X-Humanizer: 1` 头。
|
| 630 |
+
3. 再找 `http://127.0.0.1:8080` 上的 **llama-server**(`POST /completion`)。
|
| 631 |
+
4. 都没有、而 macOS 上装了 App 但没开:`hz` 在后台拉起它(`open -a Humanizer`),最多等 3 分钟让模型加载完,期间打印进度。
|
| 632 |
+
5. 还是没有就以退出码 3 结束,并告诉你怎么装 App、怎么起 llama-server。
|
| 633 |
+
|
| 634 |
+
如果 App 开着但还没下模型(第一次运行),`hz` 会提示你先在 App 里选一个档位下载。
|
| 635 |
+
|
| 636 |
+
### 文档怎么切
|
| 637 |
+
|
| 638 |
+
- 用空行分块。下面这些**原样保留**,不送给模型:Markdown 标题(`#`、下划线式标题,或整行只有 `**粗体**`)、围栏和缩进代码块、表格、只有图片或链接的行、HTML 块和注释、`$$…$$` 与 `\[…\]` 公式、引用块、分隔线、YAML front matter、脚注和链接定义、紧挨在列表/表格/代码块前面以冒号结尾的短句(如"主要成效如下:"),以及 References、Bibliography、Works Cited、Sources、参考文献等标题下面的全部内容。
|
| 639 |
+
- 正文段落按顺序拼成块,每块最多约 **350 个英文词或 600 个汉字**(`--max-words`、`--max-chars`)。块不会跨过标题或任何保留块;同一段连续正文切出的几块大小大致相同。
|
| 640 |
+
- 单段超长时按句子切开,各块改完再拼回同一段。
|
| 641 |
+
- **列表**:每一项保留原来的项目符号或编号,以及开头的 `**小标题:**`。一项有约 15 个英文词(26 个汉字)以上就单独改写,更短的保持原样。短列表项是模型最弱的地方,请重点看。
|
| 642 |
+
- 改写后的块放回原来的位置,块与块���间的空行照旧。正文里的行内格式(粗体、行内代码、链接)由模型决定,有时会丢。
|
| 643 |
+
|
| 644 |
+
### 检查和重写
|
| 645 |
+
|
| 646 |
+
每块改完都会检查。出现下表任一问题就再改一次(`--retries`),两版里留问题少的那版:
|
| 647 |
+
|
| 648 |
+
| 问题 | 含义 |
|
| 649 |
+
|---|---|
|
| 650 |
+
| `missing_numbers` | 草稿里的某个数字在改写里找不到。先做规范化:`1,250` = `1250`,`3.50` = `3.5`,`07` = `7`,`480k` = `480,000`,`31.7万` = `317,000`;改写里写成文字的数字(`three`、`两`)也算找到。 |
|
| 651 |
+
| `copy` | 改写有一半以上照抄草稿(`--max-copy`,默认 0.5):改写里的英文 5 词片段(中文 6 字片段)有多少在草稿里出现过。 |
|
| 652 |
+
| `missing_urls` | 草稿里的链接在改写里找不到。 |
|
| 653 |
+
| `empty`、`truncated` | 什么都没返回,或者输出撞到了长度上限。 |
|
| 654 |
+
| `too_short`、`too_long` | 改写不到草稿长度的 35%,或超过 175%。真机测试里正常块的长度是草稿的 0.7 到 1.5 倍;超过 1.75 倍的几块分别是凭空多出一段、编了一个数、同一句话写了两遍。 |
|
| 655 |
+
| `repeated` | 改写里有两句话几乎在说同一件事,而草稿里没有这样的重复。 |
|
| 656 |
+
| `markup` | 改写里出现了草稿没有的 HTML 标签(真机测试里见过短列表项末尾多出一个 `<p>`)。 |
|
| 657 |
+
|
| 658 |
+
`added_numbers`(改写里有、草稿里没有的数字)**只报告、不重写**:模型有时是做了正确的算术("成本从 $480k 降到 $305k"被写成"省了 $175k"),有时是编出来的数字。不管哪种,都要看一眼。
|
| 659 |
+
|
| 660 |
+
**语言不对**。在这些检查之前,写成了另一种语言的改写(英文草稿写成中文,或中文草稿写成英文;只数字符)会用同样的参数重新生成,最多 3 次。`pieces[].language_resampled` 记重采了几次;3 次都不对的话,这块会标上 `language`。
|
| 661 |
+
|
| 662 |
+
重写之后仍有问题的块会在 stderr 列出行号(`.docx` 为段落序号);用 `--json` 时标为 `"flagged": true`。这种情况退出码仍是 0。
|
| 663 |
+
|
| 664 |
+
### `--json` 输出
|
| 665 |
+
|
| 666 |
+
下面是一次真机运行的结果(1,200 词的英文 Markdown 文章,走 App,21 块里只列出一块):
|
| 667 |
+
|
| 668 |
+
```json
|
| 669 |
+
{
|
| 670 |
+
"hz": "0.1.0", "input": "article.md", "output": "article.out.md", "format": "text",
|
| 671 |
+
"backend": {"kind": "app", "url": "http://127.0.0.1:47615", "model": "q8"},
|
| 672 |
+
"summary": {"pieces": 21, "retried": 0, "flagged": 1, "seconds": 41.4,
|
| 673 |
+
"words_in": 1035, "words_out": 1153, "copy_rate": 0.05},
|
| 674 |
+
"kept": {"heading": 8, "intro": 2, "table": 1, "code": 1},
|
| 675 |
+
"pieces": [
|
| 676 |
+
{"id": 15, "kind": "prose", "line": 65, "words_in": 102, "words_out": 111,
|
| 677 |
+
"copy_rate": 0.036, "missing_numbers": [], "missing_urls": [], "added_numbers": ["175k"],
|
| 678 |
+
"truncated": false, "retried": false, "chosen": 1, "seconds": 4.4,
|
| 679 |
+
"flagged": true, "issues": ["added_numbers"],
|
| 680 |
+
"attempts": [{"copy_rate": 0.036, "missing_numbers": [], "issues": ["added_numbers"], "seconds": 4.4}],
|
| 681 |
+
"draft_start": "The results of the migration have exceeded our initial expec"}
|
| 682 |
+
]
|
| 683 |
+
}
|
| 684 |
+
```
|
| 685 |
+
|
| 686 |
+
| 字段 | 含义 |
|
| 687 |
+
|---|---|
|
| 688 |
+
| `summary.pieces` / `retried` / `flagged` | 送去改写的块数;其中重写过的块数;重写后仍有问题的块数 |
|
| 689 |
+
| `summary.copy_rate` | 全部改写正文相对全部草稿的照抄率 |
|
| 690 |
+
| `kept` | 原样保留的块,按类型计数(`short` 是短到不值得改写的正文,`list item` 是短列表项) |
|
| 691 |
+
| `pieces[].line`(`.docx` 为 `paragraph`) | 这块在输入里从第几行开始,从 1 数起 |
|
| 692 |
+
| `pieces[].kind` | `prose`(一段或几段正文)、`item`(列表项)、`sentences`(超长段落切出的一部分)、`paragraph`(`.docx` 段落) |
|
| 693 |
+
| `pieces[].words_in` / `words_out` | 草稿和改写的长度;一个汉字算一个词 |
|
| 694 |
+
| `pieces[].copy_rate` | 选中那版的照抄率,0 到 1 |
|
| 695 |
+
| `pieces[].missing_numbers` / `missing_urls` / `added_numbers` | 见上表;缺失的按草稿里的写法,新增的按改写里的写法 |
|
| 696 |
+
| `pieces[].retried` / `chosen` / `attempts` | 是否重写过、留的是第几版、每一版的检查结果 |
|
| 697 |
+
| `pieces[].seconds` | 这块所有版本加起来的模型耗时 |
|
| 698 |
+
| `pieces[].language_resampled` | 因为语言不对多采了几次(通常是 0) |
|
| 699 |
+
| `pieces[].flagged` / `issues` | 留下的那版是否仍有问题,以及是哪些问题 |
|
| 700 |
+
| `text` | 完整结果,只在没给 `-o`、输入又不是 `.docx` 时才有 |
|
| 701 |
+
|
| 702 |
+
### `.docx` 文件
|
| 703 |
+
|
| 704 |
+
- 需要 `python-docx`(见上面的安装)。正文段落逐段改写。标题(Heading、Title、Subtitle 样式或带大纲级别的段落)、表格、空段、题注、引用、代码样式、目录,以及参考文献标题下面的内容都不动。页眉、页脚、脚注和文本框也不碰。
|
| 705 |
+
- 段落里有超链接、图片、域、脚注引用、修订痕迹或公式的,整段不动,免得丢东西。
|
| 706 |
+
- 改写结果写进段落的**第一个 run**,其余 run 清空。段落样式和编号都保留,但**段内局部的粗体、斜体等格��会丢**,整段统一用第一个 run 的格式。
|
| 707 |
+
|
| 708 |
+
### 选项
|
| 709 |
+
|
| 710 |
+
| 选项 | 默认 | 含义 |
|
| 711 |
+
|---|---|---|
|
| 712 |
+
| `-o FILE` | stdout(`.docx`:`NAME.hz.docx`) | 结果写到哪里。不允许覆盖输入文件。 |
|
| 713 |
+
| `--server URL` | | 只用这个 llama-server |
|
| 714 |
+
| `--app URL` | 自动 | App 不在常用地址时用 |
|
| 715 |
+
| `--json` | 关 | 在 stdout 输出 JSON 报告 |
|
| 716 |
+
| `-q`、`--quiet` | 关 | 不打印进度 |
|
| 717 |
+
| `--dry-run` | 关 | 只显示切块和保留块,不调用模型 |
|
| 718 |
+
| `--max-words` / `--max-chars` | 350 / 600 | 英文 / 中文每块大小 |
|
| 719 |
+
| `--max-copy` | 0.5 | 照抄率超过多少就重写 |
|
| 720 |
+
| `--retries` | 1 | 有问题的块最多再改几次(0 = 不重写) |
|
| 721 |
+
| `--no-launch` | 关 | App 没开时不自动拉起 |
|
| 722 |
+
|
| 723 |
+
退出码:0 完成(有块被标记也是 0),1 运行中出错(不写任何输出),2 输入或参数有误,3 找不到模型。
|
| 724 |
+
|
| 725 |
+
### 局限
|
| 726 |
+
|
| 727 |
+
- 检查只管数字、链接、照抄、长度、重复和多余的 HTML 标签。**改了一个词、一个人名或句子意思,它查不出来**。真机中文测试里,"覆盖全部 48 家门店"被改成"覆盖全省 48 家门店","时有发生"被改成"经常出现",数字却一个没错。请通读结果。
|
| 728 |
+
- 每块改写时看不到其他块,所以块与块之间语气可能略有变化。
|
| 729 |
+
- 短列表项最弱:模型有时会丢掉短项的主语,或者把同一句话写两遍。
|
| 730 |
+
- 用汉字写的数字只能部分识别,三和 3 之间来回换写法时,两个方向都可能漏报或误报。
|
| 731 |
+
- 速度(M5 Max 上走 App,Q8_0):1,200 词的英文 Markdown 文章切成 21 块,41 到 54 秒;约 700 字的中文报告切成 6 块,13 到 17 秒。
|
| 732 |
+
- 不承诺任何 AI 检测器结果。
|
assets/app-en.png
ADDED
|
Git LFS Details
|
assets/app-showcase-en.png
ADDED
|
Git LFS Details
|
assets/app-showcase-zh.png
ADDED
|
Git LFS Details
|
assets/app-zh.png
ADDED
|
Git LFS Details
|
assets/banner-en.png
ADDED
|
Git LFS Details
|
assets/banner-zh.png
ADDED
|
Git LFS Details
|
assets/compare-en-email.png
ADDED
|
Git LFS Details
|
assets/compare-en-forum.png
ADDED
|
Git LFS Details
|
assets/compare-zh-email.png
ADDED
|
Git LFS Details
|
assets/compare-zh-zhihu.png
ADDED
|
Git LFS Details
|
assets/eval-en.png
ADDED
|
Git LFS Details
|
assets/eval-zh.png
ADDED
|
Git LFS Details
|
assets/quant-top1-en.png
ADDED
|
Git LFS Details
|
assets/results-detector-en.png
ADDED
|
Git LFS Details
|
assets/results-detector-zh.png
ADDED
|
Git LFS Details
|
assets/results-fidelity-en.png
ADDED
|
Git LFS Details
|
assets/results-fidelity-zh.png
ADDED
|
Git LFS Details
|
assets/training-en.png
ADDED
|
Git LFS Details
|
assets/training-zh.png
ADDED
|
Git LFS Details
|
config.json
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"Gemma4UnifiedForConditionalGeneration"
|
| 4 |
+
],
|
| 5 |
+
"audio_config": {
|
| 6 |
+
"_name_or_path": "",
|
| 7 |
+
"architectures": null,
|
| 8 |
+
"audio_embed_dim": 640,
|
| 9 |
+
"chunk_size_feed_forward": 0,
|
| 10 |
+
"dtype": "bfloat16",
|
| 11 |
+
"id2label": {
|
| 12 |
+
"0": "LABEL_0",
|
| 13 |
+
"1": "LABEL_1"
|
| 14 |
+
},
|
| 15 |
+
"initializer_range": 0.02,
|
| 16 |
+
"is_encoder_decoder": false,
|
| 17 |
+
"label2id": {
|
| 18 |
+
"LABEL_0": 0,
|
| 19 |
+
"LABEL_1": 1
|
| 20 |
+
},
|
| 21 |
+
"model_type": "gemma4_unified_audio",
|
| 22 |
+
"output_attentions": false,
|
| 23 |
+
"output_hidden_states": false,
|
| 24 |
+
"problem_type": null,
|
| 25 |
+
"return_dict": true,
|
| 26 |
+
"rms_norm_eps": 1e-06
|
| 27 |
+
},
|
| 28 |
+
"audio_token_id": 258881,
|
| 29 |
+
"boa_token_id": 256000,
|
| 30 |
+
"boi_token_id": 255999,
|
| 31 |
+
"dtype": "bfloat16",
|
| 32 |
+
"eoa_token_index": 258883,
|
| 33 |
+
"eoi_token_id": 258882,
|
| 34 |
+
"image_token_id": 258880,
|
| 35 |
+
"initializer_range": 0.02,
|
| 36 |
+
"model_type": "gemma4_unified",
|
| 37 |
+
"text_config": {
|
| 38 |
+
"attention_bias": false,
|
| 39 |
+
"attention_dropout": 0.0,
|
| 40 |
+
"attention_k_eq_v": true,
|
| 41 |
+
"bos_token_id": 2,
|
| 42 |
+
"dtype": "bfloat16",
|
| 43 |
+
"enable_moe_block": false,
|
| 44 |
+
"eos_token_id": 1,
|
| 45 |
+
"final_logit_softcapping": 30.0,
|
| 46 |
+
"global_head_dim": 512,
|
| 47 |
+
"head_dim": 256,
|
| 48 |
+
"hidden_activation": "gelu_pytorch_tanh",
|
| 49 |
+
"hidden_size": 3840,
|
| 50 |
+
"hidden_size_per_layer_input": 0,
|
| 51 |
+
"initializer_range": 0.02,
|
| 52 |
+
"intermediate_size": 15360,
|
| 53 |
+
"layer_types": [
|
| 54 |
+
"sliding_attention",
|
| 55 |
+
"sliding_attention",
|
| 56 |
+
"sliding_attention",
|
| 57 |
+
"sliding_attention",
|
| 58 |
+
"sliding_attention",
|
| 59 |
+
"full_attention",
|
| 60 |
+
"sliding_attention",
|
| 61 |
+
"sliding_attention",
|
| 62 |
+
"sliding_attention",
|
| 63 |
+
"sliding_attention",
|
| 64 |
+
"sliding_attention",
|
| 65 |
+
"full_attention",
|
| 66 |
+
"sliding_attention",
|
| 67 |
+
"sliding_attention",
|
| 68 |
+
"sliding_attention",
|
| 69 |
+
"sliding_attention",
|
| 70 |
+
"sliding_attention",
|
| 71 |
+
"full_attention",
|
| 72 |
+
"sliding_attention",
|
| 73 |
+
"sliding_attention",
|
| 74 |
+
"sliding_attention",
|
| 75 |
+
"sliding_attention",
|
| 76 |
+
"sliding_attention",
|
| 77 |
+
"full_attention",
|
| 78 |
+
"sliding_attention",
|
| 79 |
+
"sliding_attention",
|
| 80 |
+
"sliding_attention",
|
| 81 |
+
"sliding_attention",
|
| 82 |
+
"sliding_attention",
|
| 83 |
+
"full_attention",
|
| 84 |
+
"sliding_attention",
|
| 85 |
+
"sliding_attention",
|
| 86 |
+
"sliding_attention",
|
| 87 |
+
"sliding_attention",
|
| 88 |
+
"sliding_attention",
|
| 89 |
+
"full_attention",
|
| 90 |
+
"sliding_attention",
|
| 91 |
+
"sliding_attention",
|
| 92 |
+
"sliding_attention",
|
| 93 |
+
"sliding_attention",
|
| 94 |
+
"sliding_attention",
|
| 95 |
+
"full_attention",
|
| 96 |
+
"sliding_attention",
|
| 97 |
+
"sliding_attention",
|
| 98 |
+
"sliding_attention",
|
| 99 |
+
"sliding_attention",
|
| 100 |
+
"sliding_attention",
|
| 101 |
+
"full_attention"
|
| 102 |
+
],
|
| 103 |
+
"max_position_embeddings": 262144,
|
| 104 |
+
"model_type": "gemma4_unified_text",
|
| 105 |
+
"moe_intermediate_size": null,
|
| 106 |
+
"num_attention_heads": 16,
|
| 107 |
+
"num_experts": null,
|
| 108 |
+
"num_global_key_value_heads": 1,
|
| 109 |
+
"num_hidden_layers": 48,
|
| 110 |
+
"num_key_value_heads": 8,
|
| 111 |
+
"num_kv_shared_layers": 0,
|
| 112 |
+
"pad_token_id": 0,
|
| 113 |
+
"rms_norm_eps": 1e-06,
|
| 114 |
+
"rope_parameters": {
|
| 115 |
+
"full_attention": {
|
| 116 |
+
"partial_rotary_factor": 0.25,
|
| 117 |
+
"rope_theta": 1000000.0,
|
| 118 |
+
"rope_type": "proportional"
|
| 119 |
+
},
|
| 120 |
+
"sliding_attention": {
|
| 121 |
+
"rope_theta": 10000.0,
|
| 122 |
+
"rope_type": "default"
|
| 123 |
+
}
|
| 124 |
+
},
|
| 125 |
+
"sliding_window": 1024,
|
| 126 |
+
"tie_word_embeddings": true,
|
| 127 |
+
"top_k_experts": null,
|
| 128 |
+
"use_bidirectional_attention": "vision",
|
| 129 |
+
"use_cache": true,
|
| 130 |
+
"use_double_wide_mlp": false,
|
| 131 |
+
"vocab_size": 262144,
|
| 132 |
+
"vocab_size_per_layer_input": 262144
|
| 133 |
+
},
|
| 134 |
+
"tie_word_embeddings": true,
|
| 135 |
+
"transformers_version": "5.14.1",
|
| 136 |
+
"video_token_id": 258884,
|
| 137 |
+
"vision_config": {
|
| 138 |
+
"_name_or_path": "",
|
| 139 |
+
"architectures": null,
|
| 140 |
+
"chunk_size_feed_forward": 0,
|
| 141 |
+
"dtype": "bfloat16",
|
| 142 |
+
"id2label": {
|
| 143 |
+
"0": "LABEL_0",
|
| 144 |
+
"1": "LABEL_1"
|
| 145 |
+
},
|
| 146 |
+
"initializer_range": 0.02,
|
| 147 |
+
"is_encoder_decoder": false,
|
| 148 |
+
"label2id": {
|
| 149 |
+
"LABEL_0": 0,
|
| 150 |
+
"LABEL_1": 1
|
| 151 |
+
},
|
| 152 |
+
"mm_embed_dim": 3840,
|
| 153 |
+
"mm_posemb_size": 1120,
|
| 154 |
+
"model_type": "gemma4_unified_vision",
|
| 155 |
+
"num_soft_tokens": 280,
|
| 156 |
+
"output_attentions": false,
|
| 157 |
+
"output_hidden_states": false,
|
| 158 |
+
"output_proj_dims": 3840,
|
| 159 |
+
"patch_size": 16,
|
| 160 |
+
"pooling_kernel_size": 3,
|
| 161 |
+
"problem_type": null,
|
| 162 |
+
"return_dict": true,
|
| 163 |
+
"rms_norm_eps": 1e-06
|
| 164 |
+
}
|
| 165 |
+
}
|
generation_config.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bos_token_id": 2,
|
| 3 |
+
"do_sample": true,
|
| 4 |
+
"eos_token_id": 1,
|
| 5 |
+
"pad_token_id": 0,
|
| 6 |
+
"suppress_tokens": [
|
| 7 |
+
258883,
|
| 8 |
+
258882
|
| 9 |
+
],
|
| 10 |
+
"temperature": 1.0,
|
| 11 |
+
"top_k": 64,
|
| 12 |
+
"top_p": 0.95,
|
| 13 |
+
"transformers_version": "5.14.1"
|
| 14 |
+
}
|
humanizer-12b-IQ2_XS-QAT.gguf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:383e5ca8f1f48ab5f65013adbc1965fa70d1d1afa6c45d932c34b854e38edbb5
|
| 3 |
+
size 3893632896
|
humanizer-12b-Q3-QAT.gguf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:307bbfdf66fb22bf98aaa93fe7d54a713bf167e646073dc0cf10870b1525eb94
|
| 3 |
+
size 5587794816
|
humanizer-12b-Q4_K_M.gguf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2229574dec5178629575ee4a153dfee7d9e924ab997d0ad9d2ac622b67e44834
|
| 3 |
+
size 7625160864
|
humanizer-12b-Q6_K.gguf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c98f03bb9e71456181f99b0e1d3391e07ce1afc357f9db4c33d6d379b8dd9f0d
|
| 3 |
+
size 10029799584
|
humanizer-12b-Q8_0.gguf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8d7a457b56de6530eaaf0151ccfa7550a4b20dab979737e259da9c63e960e0b0
|
| 3 |
+
size 12669630368
|
humanizer-12b-bf16.gguf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:47d79b44c3e15ea2540f4edb63556f2b7e456067d7dd252a42ff103a26a51be9
|
| 3 |
+
size 23832049568
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f6e8c0d176f9340cc2af82efac085e4ba292781bb0783a08eef07540d6b95548
|
| 3 |
+
size 23919549408
|
prompt_format.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"style": "legacy",
|
| 3 |
+
"sep": "\n\n### Rewritten:\n\n",
|
| 4 |
+
"fingerprint": "cc51d66b4c593fbe",
|
| 5 |
+
"instr": "Rewrite the text below so it reads like a person wrote it, not a language model.\n\nReorganize it as you see fit. Vary sentence length on purpose. Cut hedging,\nthroat-clearing, and any sentence that only announces what comes next.\nPrefer the concrete word over the abstract one. It is fine to sound uneven.\n\nEvery fact, number, unit, date, name and quotation must survive unchanged.",
|
| 6 |
+
"chat": false,
|
| 7 |
+
"note": "推理时需 instr + draft + sep"
|
| 8 |
+
}
|
tokenizer.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:12bac982b793c44b03d52a250a9f0d0b666813da566b910c24a6da0695fd11e6
|
| 3 |
+
size 32170070
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"audio_token": "<|audio|>",
|
| 3 |
+
"backend": "tokenizers",
|
| 4 |
+
"boa_token": "<|audio>",
|
| 5 |
+
"boi_token": "<|image>",
|
| 6 |
+
"bos_token": "<bos>",
|
| 7 |
+
"eoa_token": "<audio|>",
|
| 8 |
+
"eoc_token": "<channel|>",
|
| 9 |
+
"eoi_token": "<image|>",
|
| 10 |
+
"eos_token": "<eos>",
|
| 11 |
+
"eot_token": "<turn|>",
|
| 12 |
+
"escape_token": "<|\"|>",
|
| 13 |
+
"etc_token": "<tool_call|>",
|
| 14 |
+
"etd_token": "<tool|>",
|
| 15 |
+
"etr_token": "<tool_response|>",
|
| 16 |
+
"extra_special_tokens": [
|
| 17 |
+
"<|video|>"
|
| 18 |
+
],
|
| 19 |
+
"image_token": "<|image|>",
|
| 20 |
+
"is_local": false,
|
| 21 |
+
"local_files_only": false,
|
| 22 |
+
"mask_token": "<mask>",
|
| 23 |
+
"model_max_length": 1000000000000000019884624838656,
|
| 24 |
+
"model_specific_special_tokens": {
|
| 25 |
+
"audio_token": "<|audio|>",
|
| 26 |
+
"boa_token": "<|audio>",
|
| 27 |
+
"boi_token": "<|image>",
|
| 28 |
+
"eoa_token": "<audio|>",
|
| 29 |
+
"eoc_token": "<channel|>",
|
| 30 |
+
"eoi_token": "<image|>",
|
| 31 |
+
"eot_token": "<turn|>",
|
| 32 |
+
"escape_token": "<|\"|>",
|
| 33 |
+
"etc_token": "<tool_call|>",
|
| 34 |
+
"etd_token": "<tool|>",
|
| 35 |
+
"etr_token": "<tool_response|>",
|
| 36 |
+
"image_token": "<|image|>",
|
| 37 |
+
"soc_token": "<|channel>",
|
| 38 |
+
"sot_token": "<|turn>",
|
| 39 |
+
"stc_token": "<|tool_call>",
|
| 40 |
+
"std_token": "<|tool>",
|
| 41 |
+
"str_token": "<|tool_response>",
|
| 42 |
+
"think_token": "<|think|>"
|
| 43 |
+
},
|
| 44 |
+
"pad_token": "<pad>",
|
| 45 |
+
"padding_side": "left",
|
| 46 |
+
"processor_class": "Gemma4UnifiedProcessor",
|
| 47 |
+
"soc_token": "<|channel>",
|
| 48 |
+
"sot_token": "<|turn>",
|
| 49 |
+
"stc_token": "<|tool_call>",
|
| 50 |
+
"std_token": "<|tool>",
|
| 51 |
+
"str_token": "<|tool_response>",
|
| 52 |
+
"think_token": "<|think|>",
|
| 53 |
+
"tokenizer_class": "GemmaTokenizer",
|
| 54 |
+
"unk_token": "<unk>"
|
| 55 |
+
}
|