mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-08-09 23:50:40 +02:00
Delete every knob the fixed profiles cannot honor
The CLI still advertised --model, --steps, --guidance-scale, --device and a deprecated --auto. Each pinned a value the two surviving profiles fix -- the model stack, the per-stage distilled schedule, CFG 1.0, CUDA -- so the only outcome any of them had was an error raised several frames below the caller, under a message naming an internal profile. A flag whose sole result is a refusal is worse than no flag: it advertises a capability that does not exist, and it lets a wrapper thread a value that will silently do nothing. They are gone from the parser, from InvisibleEngine, and from WatermarkRemover, so the failure is now a TypeError or a Click "No such option" at the point the caller can act on. The install hint was wrong in the same way. is_available() checked torch and diffusers, then told the user to install [diffusion] -- which contains neither DiffSynth nor the Z-Image face stage both profiles run. Following the advice produced a second, different failure. The module list and the extra name now live once in watermark_profiles (REMOVAL_MODULES, INVISIBLE_EXTRA) and are read by both the CLI gate and the remover's precondition, which cannot drift apart because they are the same tuple. The adaptive-polish default moved out of the argument parser. It was resolved by reading Click's parameter source, which put per-profile data in the CLI layer, left the engine declaring the opposite default (False vs True) so a library caller and a CLI caller on one profile got different output, and lost the polish entirely for anything that supplies the flag non-interactively. The flag is now tri-state (default=None) and resolve_adaptive_polish owns the per-profile answer. The seed follows the same rule: the CLI stopped pre-resolving it. Dead code removed with it: six scan_*_video wrappers and the _scan_video helper none of them had a caller for, PNG_METADATA_KEYS, feather_region_composite and the remover region path that was only reachable from a no-caller convenience wrapper, remove_watermark_batch on both layers, try_empty_device_cache, the _generate/_run_qwen_zimage pass-through pair, self.model_id, and the _internal PEP 562 shim that no caller ever went through. get_device now answers cuda or cpu only: mps and xpu travelled one frame to the same CUDA-only refusal while costing a device probe each, and that refusal now names the resolved device, so device=None on a CUDA-less host says 'cpu' rather than 'None'. The XPU wheel index went with them. Docs: README, cli, installation, python-api, supported-signals, known-limitations and module-internals all still described the removed profiles, the CPU/MPS/XPU ladder, a `default`->`sdxl` alias, and the wrong extra. known-limitations still listed the retired SDXL strength ladder as current. scripts/smoke_matrix.py and real_examples_e2e.py drove --device mps. Next release is 0.25.0, not a patch: this removes public parameters and narrows a published extra on top of the released 0.24.0. pre-commit: 1) maintain.sh - exit 0 (1091 tests, Pyright 0 errors, no vulnerabilities); 2) /simplify - 4 agents, 11 findings applied, 2 skipped (dropping the `device` parameter entirely, which raiw-app pins; folding diffsynth into the `diffusion` extra, which video-only callers do not need); 3) docs sync - grepped every removed identifier across README, docs/, scripts/, .claude/; updated 9 docs; 4) CLAUDE.md - added the no-error-only-knobs rule to .claude/rules/development.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
bf4bfc1ab7
commit
52b2c115e8
+23
-23
@@ -17,9 +17,10 @@ WHAT IT COVERS AND WHY THAT SHAPE
|
||||
work" report); a no-op must be byte-identical; `metadata --remove` must actually
|
||||
strip; a JPEG strip must not touch the pixels. Note the pixel-lossless contract is
|
||||
the DEFAULT path's -- `--remove-all` deliberately re-encodes (see metadata.py).
|
||||
* The diffusion bodies under `--diffusion`, at `--max-resolution 512` so they fit MPS
|
||||
(~1 min/image on 32 GB unified memory). Not just exit codes: `invisible` must
|
||||
restore the input resolution and must NOT re-stamp SDXL's own open watermark.
|
||||
* The diffusion bodies under `--diffusion`, at a small `--max-resolution`. Both
|
||||
profiles are CUDA-only, so those rows need an NVIDIA GPU and are reported as
|
||||
skips elsewhere. Not just exit codes: `invisible` must restore the input
|
||||
resolution and must NOT re-stamp SDXL's own open watermark.
|
||||
|
||||
WHAT IT DOES NOT COVER, DELIBERATELY AND LOUDLY
|
||||
* Without `--diffusion`, the model-running bodies are reported as SKIPPED with a
|
||||
@@ -34,7 +35,7 @@ WHAT IT DOES NOT COVER, DELIBERATELY AND LOUDLY
|
||||
|
||||
uv run python scripts/smoke_matrix.py # corpus + fixtures
|
||||
uv run python scripts/smoke_matrix.py --quick # fixtures only, no corpus
|
||||
uv run python scripts/smoke_matrix.py --diffusion # + the SDXL model paths
|
||||
uv run python scripts/smoke_matrix.py --diffusion # + the model paths (needs CUDA)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -113,7 +114,7 @@ def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--quick", action="store_true", help="fixtures only; skip the corpus rows")
|
||||
ap.add_argument(
|
||||
"--diffusion", action="store_true", help="also run the model-running paths (SDXL weights, ~1 min/image)"
|
||||
"--diffusion", action="store_true", help="also run the model-running paths (needs CUDA, ~1 min/image)"
|
||||
)
|
||||
a = ap.parse_args()
|
||||
|
||||
@@ -359,10 +360,12 @@ def _knob_rows(r: Runner, tmp: Path, img: Path) -> None:
|
||||
per-knob oracle and most of them have none (`--humanize` has no oracle at all), so
|
||||
claiming more here would be dishonest.
|
||||
"""
|
||||
# Both surviving profiles are CUDA-only and pin a distilled four-step schedule at
|
||||
# CFG 1.0, so most rows here now assert a knob is REJECTED rather than accepted.
|
||||
# That is the coverage worth having: a knob the CLI takes and the library refuses
|
||||
# several layers down is exactly what this matrix exists to catch.
|
||||
# Both surviving profiles are CUDA-only, pin a fixed model stack and let each stage
|
||||
# own its schedule and CFG. The knobs that used to contradict that (--model, --steps,
|
||||
# --guidance-scale, --device, --auto) are gone from the parser, so the rows below
|
||||
# assert Click itself refuses them. That is the coverage worth having: an option the
|
||||
# CLI accepts and the library refuses several layers down is what this matrix exists
|
||||
# to catch, and the cheapest way to keep it caught is for the option not to exist.
|
||||
fast = ["--max-resolution", "384", "--force", "--seed", "0"]
|
||||
|
||||
def run(name: str, extra: list[str], *, tag: str, expect: int | None = 0) -> None:
|
||||
@@ -377,10 +380,15 @@ def _knob_rows(r: Runner, tmp: Path, img: Path) -> None:
|
||||
for retired in ("sdxl", "controlnet", "qwen", "default"):
|
||||
run(f"--pipeline {retired} is rejected", ["--pipeline", retired], tag=f"retired_{retired}", expect=2)
|
||||
|
||||
# Fixed-graph knobs: accepted by Click, refused by the library (exit 1).
|
||||
run("--steps 20 is rejected", ["--steps", "20"], tag="steps20", expect=1)
|
||||
run("--guidance-scale 5.0 is rejected", ["--guidance-scale", "5.0"], tag="gs5", expect=1)
|
||||
run("--model override is rejected", ["--model", "org/custom"], tag="model", expect=1)
|
||||
# Retired options: no longer parsed at all (exit 2, "No such option").
|
||||
for args, tag in (
|
||||
(["--steps", "20"], "steps20"),
|
||||
(["--guidance-scale", "5.0"], "gs5"),
|
||||
(["--model", "org/custom"], "model"),
|
||||
(["--device", "cpu"], "devcpu"),
|
||||
(["--auto"], "auto"),
|
||||
):
|
||||
run(f"{args[0]} is no longer an option", args, tag=tag, expect=2)
|
||||
|
||||
try:
|
||||
import torch
|
||||
@@ -393,11 +401,6 @@ def _knob_rows(r: Runner, tmp: Path, img: Path) -> None:
|
||||
# Without CUDA the honest outcome is a CLEAN refusal naming the reason, not a
|
||||
# traceback from inside a half-built pipeline.
|
||||
run("no CUDA fails cleanly", [], tag="nocuda", expect=1)
|
||||
for name, extra, tag in (
|
||||
("--device cpu fails cleanly", ["--device", "cpu"], "cpu"),
|
||||
("--device mps fails cleanly", ["--device", "mps"], "mps"),
|
||||
):
|
||||
run(name, extra, tag=tag, expect=1)
|
||||
for label in (
|
||||
"--strength",
|
||||
"--controlnet-scale",
|
||||
@@ -405,7 +408,6 @@ def _knob_rows(r: Runner, tmp: Path, img: Path) -> None:
|
||||
"--unsharp",
|
||||
"--no-adaptive-polish",
|
||||
"--tile",
|
||||
"--auto",
|
||||
"--pipeline sdxl-zimage",
|
||||
):
|
||||
r.skip(label, "accepted-knob rows need a CUDA device")
|
||||
@@ -418,11 +420,9 @@ def _knob_rows(r: Runner, tmp: Path, img: Path) -> None:
|
||||
run("--unsharp", ["--unsharp", "0.5"], tag="uns")
|
||||
run("--no-adaptive-polish", ["--no-adaptive-polish"], tag="nap")
|
||||
run("--tile", ["--tile", "--tile-size", "256", "--tile-overlap", "64"], tag="tile")
|
||||
run("--auto (deprecated, requests polish only)", ["--auto"], tag="auto")
|
||||
|
||||
# --model and --hf-token are deliberately not exercised: one would download a second
|
||||
# multi-GB checkpoint, the other needs a real credential. Skipped loudly, not passed.
|
||||
r.skip("--model", "would download a second multi-GB checkpoint")
|
||||
# --hf-token needs a real credential, so it cannot be exercised meaningfully here.
|
||||
# Skipped loudly rather than silently passed over.
|
||||
r.skip("--hf-token", "needs a real credential; cannot be exercised meaningfully here")
|
||||
|
||||
# CONTRACT, not just execution: the same seed must reproduce the same pixels.
|
||||
|
||||
Reference in New Issue
Block a user