SPB Git

Repository Public

README.md 18.7 KB 249 lines markdown

1<p align="center">2  <img src="static/favicon.svg" width="64" height="64" alt="HTR logo">3</p>45<h1 align="center">HTR — Human Text Reconstruction</h1>67<p align="center">8  <b>Map LLM-rewritten scientific prose back towards the register of pre-2018 human writing.</b><br>9  Public research platform of UQO Working Paper <b>WP-AI-0002</b> · <a href="https://htr.spboucher.ai">htr.spboucher.ai</a>10</p>1112<p align="center">13  <img src="docs/badges/status.svg" alt="status: research preview">14  <img src="docs/badges/model.svg" alt="models: 4, default Qwen2.5-1.5B-HTR">15  <img src="docs/badges/pangram.svg" alt="Pangram 4.1: HTR-Tiny and Flan-T5-Large 0 % flagged, 54/54 Human">16  <img src="docs/badges/license.svg" alt="license: Apache-2.0">17  <img src="docs/badges/node.svg" alt="node: m4mm:8540">18  <img src="docs/badges/latency.svg" alt="latency p50 by model">19  <img src="docs/badges/tracking.svg" alt="tracking: none">20  <img src="docs/badges/python.svg" alt="server: Python standard library">21  <img src="docs/badges/api.svg" alt="API: JSON, 30 requests per minute">22</p>2324> [!IMPORTANT]25> **Outputs are synthetic reconstructions.** They are a model's estimate of pre-2018-style prose given an LLM rewrite. They are **not** the original text and **not** evidence of human authorship or of AI use. Do not use HTR for AI-text detection, authorship attribution, grading or academic-integrity decisions. The deployed models are **Medium-stage research models**; their gains over the untouched input are small by construction (reconstruction from a paraphrase is non-identifiable).2627---2829## Contents3031- [What it is](#what-it-is)32- [Models](#models)33- [Architecture](#architecture)34- [API reference](#api-reference)35- [Deployment (MacLustr)](#deployment-maclustr)36- [Shipping a new model](#shipping-a-new-model)37- [Development](#development)38- [Limits and rate limits](#limits-and-rate-limits)39- [Honest-use notice](#honest-use-notice)40- [Links](#links)4142## What it is4344HTR models are trained on pairs of *(LLM rewrite → human original)*. The originals are rights-verified scientific passages published before 2018. For a rewritten paragraph, the platform:45461. **protects** citations, equations and URLs behind opaque placeholders,472. **encodes** the protected text (HTR models: 32 k SentencePiece vocabulary, ≤ 640 tokens; pretrained backbones: their own tokenizer, context as a text prefix),483. **decodes** a reconstruction (greedy or beam 3 for HTR models, greedy for the backbones; seeded and deterministic),494. **restores** the placeholders and **checks** numbers, citations, negations, hedges and length against the input. Every deviation is returned as a warning.5051The web UI (`/`) is a workbench with these features:5253- a **model selector** (compact card per model: family, parameters, speed, test Δ chrF++, fidelity, Pangram share; the default highlighted; a caveat for the backbones); the choice is kept in `localStorage`, share links and history54- input and output panes, with optional context (discipline, era or year, section) and the decoding mode (beam 3 for HTR models only)55- a "loading model…" state when the chosen model is not yet in memory (first call can take a minute)56- **Reconstruction / Changes / Checks** tabs: the plain text, a word diff, and the preservation checks; the status line names the model used57- copy, `.txt` and `.json` downloads; share links (text in the URL hash, never on the server); a history of the last 10 runs in `localStorage`58- light and dark themes; responsive down to 360 px; print styles5960## Models6162All metrics are differences in chrF++ versus the identity baseline (B0, return the input unchanged) on the Medium Haiku pairs. They are exploratory, except the confirmatory test value of HTR-Tiny (95 % interval in brackets; ± as reported). Numbers / placeholders are preservation rates on the test split; Pangram is the share of 54 reconstructions the Pangram classifier labels Human.6364| id | Name | Family | Params | Val Δ | Test Δ | Held-out domain Δ | Held-out period Δ | Numbers | Placeholders | Pangram Human · mean AI score · flagged | p50 latency* |65|---|---|---|---|---|---|---|---|---|---|---|---|66| `htr-tiny-0.3.0` | HTR-Tiny 0.3.0 (paper's M*) | `htr` | 33.5 M | — | **+0.44** [+0.30, +0.58] (confirmatory) | −0.10 | −0.53 | 0.991 | 0.992 | **54/54 · 0.000 · 0 %** | 0.3 s |67| `htr-base-0.1.0` | HTR-Base 0.1.0 | `htr` | 335 M | +0.83 | +0.69 ± 0.05 | +0.29 | −0.12 | 0.992 | 0.993 | 53/54 · 0.003 · 1.9 % | 0.7 (1.1 beam 3) s |68| `flan-t5-large-htr` | Flan-T5-Large-HTR (google/flan-t5-large) | `hf-seq2seq` | 783 M | +2.04 ± 0.15 | +1.79 ± 0.07 | +1.27 ± 0.18 | +1.09 ± 0.14 | 0.986 | 0.992 | **54/54 · 0.000 · 0 %** | 1.5 s |69| `qwen2.5-1.5b-htr` **(default)** | Qwen2.5-1.5B-HTR (Qwen/Qwen2.5-1.5B) | `hf-causal` | 1.54 B | +2.19 ± 0.12 | **+1.97 ± 0.06** | +1.78 ± 0.13 | +1.30 ± 0.14 | 0.997 | 0.998 | 48/54 (6 Mixed) · 0.032 · 11.1 % | 2.1 s |7071\* greedy decoding, ~600-character paragraph, Apple GPU (MPS) on m4mm, measured 2026-11-10 (3 000 characters: Flan-T5 ≈6 s, Qwen ≈8 s); live values at `/api/stats`.7273**Detector sensitivity (Pangram 4.1, n = 54 test passages, 2026-11-10).** Human originals: 54/54 labelled Human, mean AI-likelihood 0.000; the LLM rewrites given as input: 29 Human / 25 Mixed (46 % flagged), mean 0.188. HTR-Tiny 0.3.0 and Flan-T5-Large-HTR reach **0 % flagged — 54/54 labelled Human, mean AI score 0.000**; HTR-Base 53/54 (mean 0.003), Qwen2.5-1.5B-HTR 48/54 (mean 0.032). Each card stores this under `metrics.pangram` (`n`, `human_share`, `flagged_share`, `mean_ai_likelihood`, `detector`, `date`), and the platform shows it on the selector tiles, the comparison table and every model card. This measures one commercial detector's behaviour on a 54-passage sample. A low AI-likelihood is not evidence of human authorship, and HTR must not be used to evade AI-text detection or for integrity decisions.7475**Pretrained backbones (Flan-T5, Qwen).** Their pretraining corpus may contain pre-2018 scientific text; the memorisation controls of the paper cover only the HTR training pairs (see its threats-to-validity section). Beam search is not offered for them (`beam` is mapped to greedy and the response says so). Flan-T5 reads at most 512 tokens (about 2 000 characters) and returns a truncation warning beyond that.7677Each model lives in `models/<id>/` with a tracked `model_card.json` (schema in [`models/README.md`](models/README.md)); the weights are not in git.7879## Architecture8081```82htr-platform/83├── server.py            # stdlib ThreadingHTTPServer: UI, JSON API, model registry, model cards, gzip, CSP, rate limits84├── htr/                 # Python package (copy of WP-AI-0002 src/htr): inference pipeline, tokenizer, models, HF backbones85│   └── inference/backbones.py  # HFSeq2SeqReconstructor, HFCausalReconstructor, load_reconstructor(dir), ModelRegistry86├── models/              # one directory per model — weights NOT in git, model_card.json (+ README.md) tracked87│   ├── README.md               # model list, card schema, how to add one88│   ├── htr-tiny-0.3.0/  htr-base-0.1.0/        # model.safetensors, config.json, tokenizer.model, release_meta.json, README.md89│   ├── flan-t5-large-htr/  qwen2.5-1.5b-htr/   # Hugging Face save_pretrained directories90├── scripts/add_model.sh # laptop → node: rsync weights, restart, check /api/models, commit the card91├── static/              # index.html, app.css, app.js (single CSS + single JS, no framework),92│   │                    # card.html, 404.html, favicon.svg, og.svg, site.webmanifest93├── docs/badges/         # static shields-style SVG badges used by this README94└── requirements.txt     # torch, transformers, accelerate, sentencepiece, safetensors, … (inference only)95```9697- **Model registry.** `ModelRegistry(models/, device=HTR_DEVICE, max_resident=2, pinned=("htr-tiny-0.3.0",))` discovers the sub-directories at start-up. The model flagged `default` in its card is loaded immediately (if it fails, HTR-Tiny takes over for the hero demo); the others load on their first request (load times are logged). At most two non-pinned models stay resident (LRU eviction, `torch.mps.empty_cache()` after eviction); HTR-Tiny is pinned.98- **Inference** runs on the Apple GPU (`--device mps`, env `HTR_DEVICE`) with one lock per model, so a slow Qwen request does not block a Tiny request. Backbone generation is capped at `HTR_MAX_GEN_S` seconds (default 55) through `generation_config.max_time`, so a 3 000-character input finishes in about a minute at most.99- **No third-party web dependencies.** The fonts are system fonts, and the icons, logo and OpenGraph image are inline or self-hosted SVG. The CSP is `default-src 'self'` and the page makes no external request. Assets are cache-busted with `?v=<sha256>`, served `immutable` and gzip-compressed.100- **Theming.** The design tokens use CSS `light-dark()`, and the toggle stores your choice in `localStorage` (`htr.theme`); the model choice is `htr.model`.101102## API reference103104Base URL: `https://htr.spboucher.ai`. No key and no account are needed. All responses are JSON unless noted.105106| Method | Path | Description |107|---|---|---|108| `POST` | `/api/reconstruct` | Reconstruct one passage with the chosen model |109| `GET` | `/api/models` | Model cards with `loaded`, `load_s`, `default`, `beam_supported`, `rate_limit_per_min`, request counts |110| `GET` | `/api/info` | Default model metadata and metrics, model ids, loaded ids, limits, `conditioning` flag |111| `GET` | `/api/stats` | Uptime, per-model requests / errors / latency p50, p95, max, limits |112| `GET` | `/api/examples` | Example inputs |113| `GET` | `/health` | Liveness: `{"ok": true, "model": …, "default": …, "loaded": […], "requests": …, "version": …}` |114| `GET` | `/model-card`, `/model-card/<id>` | Structured model card of the default model / of `<id>`: header band, metric tiles, Pangram block, bar comparison vs identity and the other models, training/decoding/limits/call-it sections, then the model's `README.md` if present (`?raw` returns the Markdown) |115| `GET` | `/og.svg`, `/site.webmanifest`, `/robots.txt` | Metadata assets |116117### `POST /api/reconstruct`118119Request body:120121| Field | Type | Notes |122|---|---|---|123| `text` | string, **required** | 1–8 000 characters |124| `model` | string | model id from `/api/models` (default: the registry default, `qwen2.5-1.5b-htr`); unknown id → `400` |125| `discipline` | string | e.g. `economics`, `biomedicine`, `finance`, `physics`, … |126| `pub_year` | int | clamped to 1500–2017 |127| `section_role` | string | `abstract`, `introduction`, `methods`, `results`, `discussion`, … |128| `beam` | `1` \| `3` | greedy (default) or beam 3 — HTR models only; mapped to greedy for the backbones (`beam_requested` and `decoding_note` say so) |129130> The HTR models of architecture `m1` **ignore** context (`/api/info` returns `"conditioning": false` when one of them is the default); the backbones receive it as a text prefix. The context fields are echoed in `metadata_used`.131132```bash133curl -s https://htr.spboucher.ai/api/reconstruct \134  -H 'Content-Type: application/json' \135  -d '{"text": "Across all specifications, the estimated elasticity remains positive and statistically significant, ranging from 0.18 to 0.24 [12].", "model": "htr-tiny-0.3.0", "discipline": "economics", "pub_year": 2015, "beam": 3}'136```137138```python139import json, urllib.request140req = urllib.request.Request("https://htr.spboucher.ai/api/reconstruct",141    data=json.dumps({"text": "…LLM-rewritten passage…", "model": "qwen2.5-1.5b-htr"}).encode(),142    headers={"Content-Type": "application/json"})143r = json.load(urllib.request.urlopen(req))144print(r["model"], r["latency_s"], "s"); print(r["reconstruction"]); print(r["warnings"])145```146147Response (200):148149```jsonc150{151  "input_text": "…",152  "reconstruction": "…",                 // synthetic text153  "warnings": ["negation count changed by -1"],154  "preservation": {155    "placeholders_exact": 1.0, "placeholder_recall": 1.0,156    "number_recall": 1.0, "number_precision": 1.0,157    "negation_delta": 0.0, "hedge_delta": 0.0, "sentence_count_delta": 0.0158  },159  "metadata_used": {"discipline": "economics", "pub_year": 2015, "arch": "hf-causal"},160  "diff_html": "… <del>…</del> <ins>…</ins> …",   // word diff, HTML-escaped161  "diff_stats": {"words_in": 64, "words_out": 60, "words_added": 21, "words_removed": 22, "word_similarity": 0.68},162  "model": "Qwen2.5-1.5B-HTR", "model_id": "qwen2.5-1.5b-htr", "family": "hf-causal", "device": "mps",163  "beam": 1, "beam_requested": 3, "decoding": "greedy",164  "decoding_note": "beam search is only offered for HTR models; this backbone used greedy decoding",165  "load_s": 9.8,                          // only present when the call had to load the model first166  "latency_s": 8.4,167  "notice": "Synthetic reconstruction produced by HTR. …"168}169```170171Errors return `{"error": "…"}` with one of these codes: `400` (invalid JSON, empty text or unknown `model`), `413` (more than 8 000 characters or a body over 64 kB), `429` (rate limit, with `Retry-After: 60`), `500` (inference failure, with `model_id`). Every POST response carries `X-RateLimit-Limit` and `X-RateLimit-Remaining` for the limit that applies to the chosen model.172173`model`, `model_id`, `family`, `device`, `beam_requested`, `decoding`, `decoding_note` and `load_s` were added in platform v0.3.0 and are additive: v0.2 clients that omit `model` get the default model. v0.4.0 adds `metrics.pangram` to each card and `pangram_reference` to `/api/models`.174175## Deployment (MacLustr)176177| Item | Value |178|---|---|179| Node | **m4mm** (Mac mini M4 Pro, 12 cores, 48 GB, LAN), directory `~/apps/htr` (this git clone) |180| Process | PM2 `htr-web`: `.venv/bin/python server.py --port 8540 --host 0.0.0.0 --device mps` (env `HTR_MODELS_DIR`, `HTR_DEVICE=mps`, `OMP_NUM_THREADS=4`, `max_memory_restart 20G`) |181| Public URL | `https://htr.spboucher.ai`: MacLustr Tunnel (Caddy on BHS64 → WireGuard → `m4mm:8540`) |182| Manifest | `M1M32:~/dispatch/apps/htr.json` (placement prefers m4mm and avoids the WP-AI-0002 training nodes) |183| Git origin | spbgit `htr-platform.git` (gateway M1M32) |184185The MacLustr rule is *remote-first*: edit **on the node**, restart, verify, then commit and push from the node.186187```bash188ssh m4mm189export PATH=/opt/homebrew/bin:$PATH; cd ~/apps/htr190# … edit server.py / static/* …191python3 -m py_compile server.py192pm2 restart htr-web --update-env193curl -s https://htr.spboucher.ai/health194git add -A && git commit -m "…" && git push origin main195```196197`mld status htr` shows the current placement. `mld move htr --to <node>` migrates the app and repoints the tunnel route (the weights under `models/` move with the directory).198199## Shipping a new model200201No code change is needed: the registry discovers `models/<id>/` at start-up and the UI builds itself from `/api/models`.2022031. Export the model directory on the laptop (HTR release: `model.safetensors`, `config.json`, `tokenizer.model`, `release_meta.json`, `README.md`; backbone: the Hugging Face `save_pretrained` directory).2042. Write `model_card.json` in that directory (fields and example in [`models/README.md`](models/README.md): `id`, `name`, `family` ∈ `htr | hf-seq2seq | hf-causal`, `n_params`, `base_model`, `licence`, `default`, `speed_hint`, `max_len` / `max_new_tokens`, `metrics`, `notes` (backbones: keep the pretraining-corpus caveat), `trained_on`, `exported_at`).2053. From the laptop:206   ```bash207   scripts/add_model.sh <laptop-model-dir> <id>        # e.g. scripts/add_model.sh artifacts/runs/medium_llm_qwen25_3b/best qwen2.5-3b-htr208   ```209   The script validates the card, rsyncs the directory to `m4mm:~/apps/htr/models/<id>/`, restarts `htr-web`, waits until `/api/models` lists the id, and commits + pushes the tracked card files from the node.2104. Check `https://htr.spboucher.ai/model-card/<id>` and run one `POST /api/reconstruct` with `"model": "<id>"`. Planned ids: `flan-t5-xl-htr`, `qwen2.5-3b-htr`, `htr-large`.211212To change the default model, set `"default": true` in exactly one card (and `false` in the previous one), then `pm2 restart htr-web --update-env`.213214## Development215216```bash217cd ~/apps/htr                       # or any clone that has models/ and .venv218.venv/bin/python server.py --port 8541 --host 127.0.0.1 --device mps219open http://127.0.0.1:8541/         # from the laptop: ssh -L 8541:127.0.0.1:8541 m4mm220```221222- `--device cpu` runs everything on CPU; `--models /path/to/dir` serves another models directory; `HTR_MAX_GEN_S` caps backbone generation time.223- Dependencies: `uv pip install --python .venv/bin/python -r requirements.txt` (torch ≥ 2.4, transformers ≥ 4.45, accelerate).224- The frontend is plain HTML, CSS and JS: no build step and no bundler. Keep `app.css` plus `app.js` under 70 kB.225- The CSP forbids inline scripts. The server passes page data (examples, demo, info, models) to the frontend in `<script type="application/json" id="boot">`.226227## Limits and rate limits228229| Limit | Value |230|---|---|231| Characters per request | 8 000 (HTR models read about 640 tokens, Flan-T5 512; longer inputs are truncated, with a warning for Flan-T5) |232| Request body | 64 kB |233| Rate | 30 requests per minute per client IP (`X-Forwarded-For`); 10 per minute for the pretrained backbones |234| Concurrency | 1 inference at a time per model (other requests for the same model wait) |235| Generation time | backbones capped at 55 s per request (`HTR_MAX_GEN_S`) |236| Latency | see the model table above; live per-model p50/p95 at `/api/stats` |237| Storage | none: the server keeps no texts, cookies or analytics. The log records only client IP, model id, length, latency and warning count |238239## Honest-use notice240241Reconstruction from a paraphrase is **non-identifiable**: many originals share one rewrite. A model's output is one plausible estimate. The platform is a research artefact for studying how LLM rewriting changes scientific prose, and a suggestion tool for authors who check every change. Verify every number, citation and negation against your source. Never present the output as the original text or as proof of who wrote it. The pretrained backbones add one caveat: their pretraining corpus may contain pre-2018 scientific text, and the paper's memorisation controls cover only the HTR training pairs.242243## Links244245- Paper: *Human Text Reconstruction: Learning the Inverse of LLM-Induced Transformations in Scientific Writing*, UQO Working Paper **WP-AI-0002** (Simon-Pierre Boucher, Université du Québec en Outaouais, 2026)246- Live platform: <https://htr.spboucher.ai> · model cards: <https://htr.spboucher.ai/model-card> · <https://htr.spboucher.ai/api/models>247- Repository: spbgit `htr-platform.git` (`gitsrv:srv/git/htr-platform.git`)248- Licence: code and HTR weights under **Apache-2.0**; the fine-tuned backbones keep the Apache-2.0 licence of `google/flan-t5-large` and `Qwen/Qwen2.5-1.5B`. Use is restricted to non-commercial research where CC-BY-NC sources were included in training (see `models/<id>/LICENSE-MODEL.txt` and the cards).249
Files main
Menu

Theme