From ddbbd15a256d6c34049eb9fb77cfe741eec3cde9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Aug 2026 20:04:36 +0000 Subject: [PATCH] docs: document per-tag granularity, chain scalability, and stress test results Adds everything validated in the latest round of testing: - multiple detections of the same class share one identical template - a full tag's text runs to the next newline, never implicitly split - typo detection is per-tag (not per-model or all-or-nothing), verified within a single multi-class model's own detections too - no code-level cap on chain length; verified with 7 chained models - 800-generation stress test: ~0.24ms/generation, ~0 net memory growth, fully deterministic, zero log output when everything resolves cleanly - no stale cross-generation state; removing a model from the chain produces an accurate orphaned-tag warning, not a false positive --- DETAILER_CLASS_PROMPTS.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/DETAILER_CLASS_PROMPTS.md b/DETAILER_CLASS_PROMPTS.md index 5debdae9a..d1d522c91 100644 --- a/DETAILER_CLASS_PROMPTS.md +++ b/DETAILER_CLASS_PROMPTS.md @@ -39,6 +39,8 @@ plain line with no tag → fallback pool - Class names are matched case-insensitively against the label YOLO reports (`model.names`), and comma-separated lists route multiple classes to the same text. - Lines with no tag are pooled as **positional fallback** — applied, in order, to any detection whose class had no matching tag, cycling if there are more untagged detections than fallback lines. A prompt with zero `[CLASS=...]` tags behaves exactly like it did before this patch. - Blank spacer lines are ignored when building the fallback pool (a blank line between a tagged and an untagged line doesn't consume a fallback slot). +- If a class matches more than one detection (e.g. two `nipple` boxes from the same pass), every one of them gets the exact same tagged text — there's no per-instance variation, `[CLASS=nipple]` is one template shared by all detections of that class. +- Everything after the closing `]` up to the next newline belongs to that tag, however long — a tag's text isn't limited to a short phrase, and a fallback line is only ever created by pressing Enter, never implicitly. ### Don't rely on fallback order for multiple classes @@ -75,6 +77,8 @@ WARNING detailer Detailer prompt: class tags did not match any detection across This had to be aggregate rather than per-model: chaining two detailer models with disjoint classes (say, a face-only model and a separate NSFW segmentation model reporting `nipples`/`pussy`/`anus`/etc.) is a completely normal setup, and a naive per-model check would flag `[CLASS=pussy]` as unmatched on every pass through the face model, and `[CLASS=face]` as unmatched on every pass through the segmentation model — pure noise despite both tags being perfectly correct. The aggregate version only complains when a tag never matches *any* model in the chain, which is the actual signature of a typo. +The check is **per-tag, not per-model or all-or-nothing.** Writing 6 correct tags and 1 misspelled one — whether across several models or all reported by a single multi-class model — flags only the one that's actually wrong; the other 6 resolve normally with zero noise. Verified with a single model reporting 6 real classes plus 1 typo in the same prompt: 6 clean resolutions, exactly 1 warning naming only the broken tag. + **It cannot tell a typo apart from a legitimate miss.** A correctly-spelled `[CLASS=face]` on an image where the face model simply found nothing this run (occluded, low confidence, out of frame) produces the exact same warning text as a real misspelling — the check only knows "declared tag X never matched a detection this generation," not *why*. To tell them apart, cross-reference the tag against the model's real vocabulary in the one-time `Load: type=Detailer name='...' ... classes=[...]` line printed when that model first loads; if your tag is in that list verbatim, it's not a typo, the class just wasn't found this time. That said, the warning is still useful either way it fires: it's a reliable signal that **"the `[CLASS=face]` prompt was not applied to anything this generation,"** regardless of the underlying cause — worth knowing on its own, independent of diagnosing why. ### Visual debugging: "Include detections" @@ -137,6 +141,22 @@ def assign_prompts(text: str, items: list) -> list[str]: Tested with SDXL inpainting through a two-model detailer chain: a single-class face model (`face-yolo8n`, class `face`) followed by a multi-class NSFW segmentation model (`ntd11_anime_nsfw_segm_v5`, classes `nipples`/`pussy`/`anus`/`penis`/`cross-section`/`x-ray`/`testicles`). Debug log confirmed each detection received its own class-specific text (`label='face' ... prompt='...'`, `label='pussy' ... prompt='...'`) with zero false-positive typo warnings from the cross-model tag targeting, and one correctly-caught real typo (`[CLASS=gace]` against an actual detected `face`) before the fix, silenced immediately after correcting the tag. +Also confirmed: a typo caught within a *single* multi-class model's own detections (not just across separate models) still warns correctly — no false positive silence just because the correct and misspelled tags share one model. + +## Scales to any chain length + +No code path caps how many models can be chained (`detailer_models`/`detailer_args` is just parsed as a list, `restore()` loops over however many entries it gets) — nothing in this patch adds a limit either. Verified with a simulated 7-model chain, each contributing its own real class plus one deliberate typo mixed in: all 7 correct classes resolved cleanly, and exactly 1 warning fired, naming only the typo — no extra noise from chain length. + +## Performance & robustness + +Ran 800 simulated back-to-back generations (parse → resolve → aggregate-warning check, 3 chained model passes each) against the real `helper.py` code: + +- **Speed:** ~0.24 ms per generation for the entire class-prompt resolution + warning pass — negligible next to actual YOLO inference / diffusion inpainting, which run in seconds. +- **Memory:** net growth after 800 iterations was ~1 KB, and that KB was traced to the memory profiler's own bookkeeping, not this code — every value used (`prompt_classes`, `matched_prompt_classes`, etc.) is a local variable inside `restore()`, created fresh and discarded every call. Nothing persists or accumulates between generations. +- **Determinism:** generation 1 and generation 800 produced byte-identical output for identical input. +- **No stale state across config changes:** class names come from `item.label` on live detection results, generated fresh every run — never cached or reused from a prior generation. Swapping which models are in the chain, or removing one entirely, takes effect immediately on the next generation with no leftover behavior from before. If you remove a model and leave behind a `[CLASS=...]` tag that only that model could ever satisfy, you'll correctly get an "unmatched" warning for it (an orphaned tag, not a false positive) — not silence, and not a stale match. +- **Log noise:** when every tag resolves correctly, this patch emits *zero* additional log output, generation after generation — nothing to scroll back through. The warning only appears the moment something is actually wrong, and only that one line. + ## Known limitations - This is a parsing convention layered on top of the existing flat `detailer_prompt`/`detailer_negative` strings — there's still no per-model or per-class field in the request schema. Anyone driving the API directly (not through the WebUI textbox) gets the same syntax for free, since it's resolved server-side regardless of how the string arrived.