Release experimental-X1.1: completed 448px ONNX and FP32 safetensors
Browse files- .gitattributes +2 -35
- LICENSE +48 -0
- README.md +277 -0
- SHA256SUMS +29 -0
- checkpoint_evidence.json +45 -0
- comparison_groups.csv +14 -0
- comparison_v1_1.json +222 -0
- comparison_v1_1_per_tag.csv +0 -0
- config.json +77 -0
- custom_drop_path.py +22 -0
- download.py +66 -0
- frozen_evaluation_contract.json +52 -0
- infer.py +244 -0
- inference_checks.json +70 -0
- mask_utils.py +96 -0
- model.onnx +3 -0
- model.safetensors +3 -0
- model_architecture.py +860 -0
- preprocessing.json +32 -0
- release_manifest.json +20 -0
- requirements-gpu.txt +4 -0
- requirements-torch.txt +5 -0
- requirements.txt +4 -0
- selected_tags.csv +0 -0
- thresholds.json +6 -0
- training_buckets.csv +6 -0
- training_epochs.csv +10 -0
- training_metadata.json +273 -0
- validation.json +67 -0
- vocabulary.json +0 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,2 @@
|
|
| 1 |
-
*.
|
| 2 |
-
*.
|
| 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
|
|
|
|
| 1 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
LICENSE
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Educational Use Only License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025
|
| 4 |
+
|
| 5 |
+
## Grant of License
|
| 6 |
+
|
| 7 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, merge, and distribute the Software solely for **educational purposes**, subject to the following conditions:
|
| 8 |
+
|
| 9 |
+
## Educational Purposes Definition
|
| 10 |
+
|
| 11 |
+
"Educational purposes" means use of the Software for:
|
| 12 |
+
- Personal learning and skill development
|
| 13 |
+
- Academic research and study
|
| 14 |
+
- Teaching in educational institutions (schools, universities, colleges)
|
| 15 |
+
- Non-commercial educational workshops and tutorials
|
| 16 |
+
- Student projects and assignments
|
| 17 |
+
- Open-source educational content creation
|
| 18 |
+
|
| 19 |
+
## Restrictions
|
| 20 |
+
|
| 21 |
+
The Software may **NOT** be used for:
|
| 22 |
+
- Commercial purposes of any kind
|
| 23 |
+
- Revenue-generating activities
|
| 24 |
+
- Production environments in for-profit organizations
|
| 25 |
+
- Consulting or professional services
|
| 26 |
+
- Any purpose that directly or indirectly generates monetary compensation
|
| 27 |
+
|
| 28 |
+
## Attribution
|
| 29 |
+
|
| 30 |
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
| 31 |
+
|
| 32 |
+
Any public use or distribution of work derived from this Software must include clear attribution to the original author and a link to the original repository.
|
| 33 |
+
|
| 34 |
+
## Disclaimer
|
| 35 |
+
|
| 36 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 37 |
+
|
| 38 |
+
## Additional Terms
|
| 39 |
+
|
| 40 |
+
1. **Derivative Works**: Any modifications or derivative works must also be licensed under these same terms and clearly marked as modified versions.
|
| 41 |
+
|
| 42 |
+
2. **No Endorsement**: Use of this Software does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor.
|
| 43 |
+
|
| 44 |
+
3. **Termination**: This license automatically terminates if you violate any of these restrictions and may be terminated by the copyright holder at any time.
|
| 45 |
+
|
| 46 |
+
## Contact
|
| 47 |
+
|
| 48 |
+
For commercial licensing inquiries or permissions beyond the scope of this license, please contact: on telegram @grio43
|
README.md
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
pipeline_tag: image-classification
|
| 3 |
+
license: other
|
| 4 |
+
license_name: educational-use-only
|
| 5 |
+
license_link: LICENSE
|
| 6 |
+
language:
|
| 7 |
+
- en
|
| 8 |
+
base_model: Grio43/OppaiOracle-experimental-X1
|
| 9 |
+
base_model_relation: finetune
|
| 10 |
+
tags:
|
| 11 |
+
- pytorch
|
| 12 |
+
- onnx
|
| 13 |
+
- safetensors
|
| 14 |
+
- anime
|
| 15 |
+
- image-tagging
|
| 16 |
+
- multi-label
|
| 17 |
+
- vision-transformer
|
| 18 |
+
- experimental
|
| 19 |
+
---
|
| 20 |
+
|
| 21 |
+
# OppaiOracle experimental-X1.1
|
| 22 |
+
|
| 23 |
+
**experimental-X1.1 is the 448×448 continuation of
|
| 24 |
+
[experimental-X1](https://huggingface.co/Grio43/OppaiOracle-experimental-X1).**
|
| 25 |
+
It is a multi-label anime and illustration tagger, released in **FP32 ONNX and
|
| 26 |
+
FP32 safetensors** formats. Both contain the selected **448px epoch 9, update
|
| 27 |
+
55,719** weights. This continuation completed its nine-epoch schedule and WSD
|
| 28 |
+
cooldown to zero learning rate on September 17, 2026.
|
| 29 |
+
|
| 30 |
+
This remains an experimental release. The shared-tag comparison below shows higher
|
| 31 |
+
micro-F1 than V1.1 at development-tuned thresholds, with slightly lower overall
|
| 32 |
+
exact mAP. Performance varies by tag and frequency; there is no blanket claim that
|
| 33 |
+
X1.1 replaces V1.1 for every use.
|
| 34 |
+
|
| 35 |
+
## Download and run
|
| 36 |
+
|
| 37 |
+
The complete download includes both weight formats, about **1.18 GB** in total.
|
| 38 |
+
Use a fresh Python environment, download the repository, then install the
|
| 39 |
+
dependencies for your chosen format:
|
| 40 |
+
|
| 41 |
+
```bash
|
| 42 |
+
python -m pip install huggingface_hub
|
| 43 |
+
python -c "from huggingface_hub import snapshot_download; snapshot_download('Grio43/OppaiOracle-experimental-X1.1', local_dir='OppaiOracle-experimental-X1.1')"
|
| 44 |
+
cd OppaiOracle-experimental-X1.1
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
For ONNX Runtime on CPU:
|
| 48 |
+
|
| 49 |
+
```bash
|
| 50 |
+
python -m pip install -r requirements.txt
|
| 51 |
+
python infer.py image.jpg --format onnx --device cpu --output predictions.json
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
For safetensors with PyTorch:
|
| 55 |
+
|
| 56 |
+
```bash
|
| 57 |
+
python -m pip install -r requirements-torch.txt
|
| 58 |
+
python infer.py image.jpg --format safetensors --device cpu --output predictions.json
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
For a smaller download, obtain just [download.py](download.py), install
|
| 62 |
+
`huggingface_hub`, and run one of the following from the directory containing it:
|
| 63 |
+
|
| 64 |
+
```bash
|
| 65 |
+
python download.py OppaiOracle-experimental-X1.1 --format onnx
|
| 66 |
+
python download.py OppaiOracle-experimental-X1.1 --format safetensors
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
Each command downloads the selected weights and required support files. The
|
| 70 |
+
helper resolves `--revision` (default `main`) to one commit before downloading,
|
| 71 |
+
keeping the configuration, vocabulary and weights aligned. Use `--format all`
|
| 72 |
+
to obtain both formats. The resulting directory supports the same install and
|
| 73 |
+
inference commands above.
|
| 74 |
+
|
| 75 |
+
The supplied PyTorch model code loads `model.safetensors` and the real architecture
|
| 76 |
+
configuration in `config.json`. These are inference weights; the safetensors file
|
| 77 |
+
does not contain a pickled training checkpoint or optimizer state. The release
|
| 78 |
+
preserves FP32 values without quantization or conversion to BF16/FP16.
|
| 79 |
+
|
| 80 |
+
Both inference paths apply the same preprocessing and return tag names with sigmoid
|
| 81 |
+
scores, excluding PAD and UNK. The default threshold is **0.7085427045822144**, the
|
| 82 |
+
best micro-F1 threshold on the shared-tag development comparison's 200-point grid.
|
| 83 |
+
It is a useful starting point, **not final deployment calibration**, and was not
|
| 84 |
+
optimized independently for every tag or for the complete vocabulary. Set
|
| 85 |
+
`--threshold` for your application. Scores are not calibrated probabilities that a
|
| 86 |
+
label is correct.
|
| 87 |
+
|
| 88 |
+
To inspect the 30 highest scores regardless of the default threshold:
|
| 89 |
+
|
| 90 |
+
```bash
|
| 91 |
+
python infer.py image.jpg --format onnx --threshold 0 --top-k 30
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
Multiple image paths and `--batch-size` are supported. `--device cuda` requires
|
| 95 |
+
the corresponding CUDA-capable PyTorch or ONNX Runtime environment. Local
|
| 96 |
+
inference does not make network requests.
|
| 97 |
+
|
| 98 |
+
## Training
|
| 99 |
+
|
| 100 |
+
The model lineage was trained **from scratch**, without an externally pretrained
|
| 101 |
+
backbone. X1.1 starts from the released 320px X1 weights and adapts **all model
|
| 102 |
+
weights**, rather than freezing the backbone.
|
| 103 |
+
|
| 104 |
+
| Stage | Recorded recipe |
|
| 105 |
+
|---|---|
|
| 106 |
+
| 320px source | V2 Phase 1, epoch 21, update 127,974; the checkpoint released as experimental-X1 |
|
| 107 |
+
| 320px optimization | Effective batch 1,024; AdamW8bit with FP32 head/position optimizer states; BF16 AMP; weight decay 0.05; 10,000-update warmup; stable LR 5.4e-4 |
|
| 108 |
+
| Source loss history | ASL negative gamma 4 initially, explicitly changed to 3 at update 41,223; positive gamma 0, probability clip 0.05, alpha 1, no label smoothing, detached focal weights |
|
| 109 |
+
| 448px continuation | Nine phase-local epochs, 55,719 optimizer updates, September 13–17, 2026 |
|
| 110 |
+
| Initialization | One-time bicubic positional-grid interpolation from 20×20 to 28×28; fresh optimizer, scheduler and phase-local history; learned weights and frozen vocabulary retained |
|
| 111 |
+
| 448px batch and precision | Microbatch 48 × 21 accumulation = 1,008; BF16 AMP; no activation checkpointing |
|
| 112 |
+
| 448px optimizer | AdamW8bit; FP32 head/position optimizer states; weight decay 0.05; gradient norm clipping 1.0 |
|
| 113 |
+
| 448px schedule | Base LR 1.5e-5, square-root scaling from batch 256 gives peak LR 2.97647022495e-5; one warmup epoch; budget-driven WSD cooldown begins at update 48,451, lasts 7,268 updates and ends at zero LR |
|
| 114 |
+
| 448px loss | Fixed ASL negative/positive gamma 3/0, probability clip 0.05, alpha 1, no label smoothing, detached focal weights; PAD/UNK excluded |
|
| 115 |
+
| Selection | Highest recorded cooldown validation binned mAP; epoch 9 is both the selected best and final checkpoint |
|
| 116 |
+
|
| 117 |
+
The 320px source did not complete its original planned cooldown. The separate
|
| 118 |
+
448px experiment did complete its own cooldown; it does not imply completion of a
|
| 119 |
+
later 512px phase. Plateau-triggered cooldown was disabled for the nine-epoch
|
| 120 |
+
continuation; its total budget included warmup and cooldown.
|
| 121 |
+
|
| 122 |
+
At 448px, drop-path was 0.15 and ordinary dropout was zero. Augmentation used
|
| 123 |
+
brightness/contrast/saturation jitter of 0.22/0.15/0.06, each at probability 0.5;
|
| 124 |
+
2–5-degree rotation at probability 0.30; Gaussian blur sigma 0.1–1.0 at probability
|
| 125 |
+
0.15; and horizontal flipping at probability 0.5. Images carrying any of
|
| 126 |
+
`gen:left-handed`, `gen:left-to-right_manga`, `gen:right-over-left_kimono` or
|
| 127 |
+
`gen:right-to-left_comic` were excluded from horizontal flips. Random erasing was
|
| 128 |
+
disabled.
|
| 129 |
+
|
| 130 |
+
## Dataset and tag updates
|
| 131 |
+
|
| 132 |
+
The frozen prepared corpus contains **6,269,823 image/metadata pairs**, split into
|
| 133 |
+
**6,239,823 TRAIN** and **30,000 EVAL** images. The 448px continuation retained the
|
| 134 |
+
same split, labels and vocabulary as X1.
|
| 135 |
+
|
| 136 |
+
| Source | Pairs |
|
| 137 |
+
|---|---:|
|
| 138 |
+
| Danbooru | 6,142,955 |
|
| 139 |
+
| Real-life photos without people | 123,335 |
|
| 140 |
+
| Requested images and tags | 3,533 |
|
| 141 |
+
|
| 142 |
+
**Dataset-maintainer provenance:** the author reports that the Danbooru metadata
|
| 143 |
+
was synchronized in **August 2026**, deprecated tags were removed, and renamed
|
| 144 |
+
tags were updated to their correct canonical names. This describes the metadata
|
| 145 |
+
preparation, not an assertion that every image was uploaded in August. These
|
| 146 |
+
cleanup statements are supplied by the dataset maintainer; the release checks
|
| 147 |
+
verify the frozen artifacts and model alignment.
|
| 148 |
+
|
| 149 |
+
General tags have a **450-image minimum**, down from V1.1's **500**. Counts are
|
| 150 |
+
distinct images in the combined prepared corpus. Character, copyright, artist and
|
| 151 |
+
metadata tags use a 550-image minimum; the four rating tags are mandatory.
|
| 152 |
+
Despite the lower general-tag floor, the cleaned vocabulary has **14,994 tags**
|
| 153 |
+
versus **19,292 in V1.1**. The overall reduction accompanies deprecated-tag removal,
|
| 154 |
+
canonical-name updates and the changed vocabulary policy; it is not a count of
|
| 155 |
+
deprecated tags alone.
|
| 156 |
+
|
| 157 |
+
| Category | Tags |
|
| 158 |
+
|---|---:|
|
| 159 |
+
| General | 9,920 |
|
| 160 |
+
| Character | 3,069 |
|
| 161 |
+
| Copyright | 1,139 |
|
| 162 |
+
| Artist | 745 |
|
| 163 |
+
| Metadata | 117 |
|
| 164 |
+
| Rating | 4 |
|
| 165 |
+
| **Total real tags** | **14,994** |
|
| 166 |
+
|
| 167 |
+
The model has **14,996 output positions**, including PAD at index 0 and UNK at
|
| 168 |
+
index 1. Keep the supplied vocabulary ordering: V1.1 indices and tag counts are
|
| 169 |
+
not compatible with this model.
|
| 170 |
+
|
| 171 |
+
## Evaluation against V1.1
|
| 172 |
+
|
| 173 |
+
Both models were evaluated at **448px in FP32 ONNX**, on the same frozen **30,000
|
| 174 |
+
EVAL images** and recorded labels, with identical preprocessing and TF32 disabled.
|
| 175 |
+
All source-sidecar hashes were verified. One-to-one shared names yielded **13,569
|
| 176 |
+
tags**, with **13,394 supported tags** contributing to macro AP. V1.1 results were
|
| 177 |
+
reused from a verified archived evaluation of the same images and support.
|
| 178 |
+
|
| 179 |
+
| Shared-tag measurement | experimental-X1.1 | V1.1 |
|
| 180 |
+
|---|---:|---:|
|
| 181 |
+
| Exact mAP | 58.7513% | 59.2716% |
|
| 182 |
+
| 200-bin mAP | 58.5193% | 58.8611% |
|
| 183 |
+
| Best grid micro-F1 | 68.6410% | 66.5864% |
|
| 184 |
+
| Threshold for that micro-F1 | 0.708543 | 0.798995 |
|
| 185 |
+
| General-tag exact mAP, 8,676 supported shared tags | 43.7549% | 43.9700% |
|
| 186 |
+
|
| 187 |
+
X1.1's overall shared-tag exact mAP is **0.5203 percentage points lower**; its
|
| 188 |
+
micro-F1 is **2.0546 points higher** at independently selected grid thresholds.
|
| 189 |
+
Across supported shared tags, X1.1 has higher AP on 5,425, V1.1 on 6,271, and 1,698
|
| 190 |
+
tie. For general tags with at least 50 EVAL positives, X1.1's exact mAP is 54.893%
|
| 191 |
+
versus 53.134%; V1.1 retains an advantage among the sparsest general tags.
|
| 192 |
+
|
| 193 |
+
These are **development-set measurements**, not an independent clean-label test.
|
| 194 |
+
This EVAL set has informed training decisions and threshold selection. V1.1's
|
| 195 |
+
training overlap with these images is unverified. Recorded labels may be missing
|
| 196 |
+
or incorrect, and aggregate gains do not establish a gain for every tag.
|
| 197 |
+
See [comparison details](comparison_v1_1.json) and
|
| 198 |
+
[shared-tag AP results](comparison_v1_1_per_tag.csv).
|
| 199 |
+
|
| 200 |
+
For the complete X1.1 supported vocabulary, the FP32 export's exact mAP was
|
| 201 |
+
**57.7464%** over **14,767 tags**. The final BF16 training validation recorded exact
|
| 202 |
+
mAP **57.7166%**, binned mAP **57.5063%**, and micro-F1 **66.2121%** at the inherited
|
| 203 |
+
training diagnostic threshold 0.7927. These differ in numerical precision and/or
|
| 204 |
+
tag scope from the shared-tag table; they should not be substituted for one
|
| 205 |
+
another.
|
| 206 |
+
The archived full FP32 measurement is in [validation.json](validation.json);
|
| 207 |
+
[training_metadata.json](training_metadata.json) records the training provenance.
|
| 208 |
+
|
| 209 |
+
## Architecture and preprocessing
|
| 210 |
+
|
| 211 |
+
The **147,706,932-parameter** ViT uses patch size 16, width 896, 18 transformer
|
| 212 |
+
layers, 14 attention heads, MLP width 2,320, QK normalization and LayerScale
|
| 213 |
+
initialization 0.1. Its positional grid is 28×28 at 448px.
|
| 214 |
+
|
| 215 |
+
| ONNX input/output | Type | Shape |
|
| 216 |
+
|---|---|---|
|
| 217 |
+
| `pixel_values` | float32 | `[batch, 3, 448, 448]` |
|
| 218 |
+
| `padding_mask` | bool | `[batch, 448, 448]` |
|
| 219 |
+
| `probabilities` | float32 | `[batch, 14996]` |
|
| 220 |
+
|
| 221 |
+
Batch size is dynamic; spatial dimensions are fixed. Apply EXIF orientation,
|
| 222 |
+
convert to RGB, composite transparency onto `[114,114,114]`, and downscale with
|
| 223 |
+
LANCZOS while preserving aspect ratio, **without upscaling**. Center the result on
|
| 224 |
+
a 448×448 canvas with the same pad color. Padding-mask values are **True for
|
| 225 |
+
padding**. Divide pixels by 255, normalize using mean and standard deviation
|
| 226 |
+
`[0.5,0.5,0.5]`, and arrange as NCHW. The supplied script implements this contract.
|
| 227 |
+
|
| 228 |
+
The ONNX output already includes sigmoid. The safetensors inference helper
|
| 229 |
+
applies sigmoid to the PyTorch logits. Four rating scores are independent sigmoid
|
| 230 |
+
outputs, not a softmax. A frontend must support the two inputs and this
|
| 231 |
+
preprocessing; a raw-image, single-input tagger interface needs an adapter.
|
| 232 |
+
|
| 233 |
+
## Files, integrity and download statistics
|
| 234 |
+
|
| 235 |
+
| File | Purpose |
|
| 236 |
+
|---|---|
|
| 237 |
+
| `model.onnx` | FP32 inference graph, about 591 MB, with embedded vocabulary/preprocessing metadata |
|
| 238 |
+
| `model.safetensors` | FP32 tensor-only weights, about 591 MB |
|
| 239 |
+
| `config.json` | Real architecture and release configuration consumed by the supplied loader |
|
| 240 |
+
| `preprocessing.json` | Image transform and input contract |
|
| 241 |
+
| `vocabulary.json`, `selected_tags.csv` | Exact output ordering and tag categories |
|
| 242 |
+
| `infer.py`, `download.py` | Standalone inference and selective release download helpers |
|
| 243 |
+
| `requirements.txt`, `requirements-torch.txt` | Dependencies for CPU ONNX and PyTorch/safetensors inference |
|
| 244 |
+
| `training_metadata.json`, `validation.json` | Training provenance and archived full FP32 evaluation |
|
| 245 |
+
| `inference_checks.json` | Release-package checks using a bounded set of fixtures; not another full evaluation |
|
| 246 |
+
| `release_manifest.json`, `SHA256SUMS` | Provenance and file-integrity records |
|
| 247 |
+
|
| 248 |
+
The normal repository download includes `config.json`, which the supplied loader
|
| 249 |
+
actually uses. [Hugging Face download statistics](https://huggingface.co/docs/hub/models-download-stats)
|
| 250 |
+
count requests to supported query files such as `config.json`. The public counter
|
| 251 |
+
is not a unique-user count; direct downloads of weights alone may not be reflected.
|
| 252 |
+
There are no extra telemetry calls or artificial counting requests in the
|
| 253 |
+
inference helper.
|
| 254 |
+
|
| 255 |
+
The selected source checkpoint SHA256 is:
|
| 256 |
+
|
| 257 |
+
```text
|
| 258 |
+
d400c2e460e7287357f86083f84c187790a45b1d47985e908c52825373f888c3
|
| 259 |
+
```
|
| 260 |
+
|
| 261 |
+
This identifies the preserved local training source. The release manifest and
|
| 262 |
+
checksums identify the distributed files; optimizer state and the pickled training
|
| 263 |
+
checkpoint are not included. The release ONNX has refreshed descriptive metadata;
|
| 264 |
+
its inference graph and learned weights are unchanged from the evaluated artifact.
|
| 265 |
+
The manifest records both artifact identities.
|
| 266 |
+
|
| 267 |
+
## Scope and license
|
| 268 |
+
|
| 269 |
+
X1.1 is intended for experimental tagging, comparison and feedback within the
|
| 270 |
+
license terms. Training annotations are noisy and incomplete; rare concepts and
|
| 271 |
+
visually similar tags can be unreliable. Review suggestions before applying bulk
|
| 272 |
+
dataset edits. The vocabulary includes sexual-content labels and independent
|
| 273 |
+
rating tags; this model has not been validated as a safety classifier.
|
| 274 |
+
|
| 275 |
+
The release retains the **[Educational Use Only License](LICENSE)**. It permits
|
| 276 |
+
the educational uses described in that file and prohibits commercial use. Read
|
| 277 |
+
the full license for attribution, redistribution and other conditions.
|
SHA256SUMS
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
5a4c917343b3f76df936dbd7f23ad4693aea7f96ad4c1029cdfd70ac3c78cb9b .gitattributes
|
| 2 |
+
322447c97c7fc1b266286efd31b7a9aefd21a111fdbde60090ebfa0601cca497 LICENSE
|
| 3 |
+
4023de4f067e0cf475dfc80d0d038477bb1d4bbea4c5edf9f23d24913e575206 README.md
|
| 4 |
+
dfc682758528177f0d96a0a1230add733f537dd40932239095893c6c585eef5e checkpoint_evidence.json
|
| 5 |
+
565667f7bd86c3b61c4fda46cec38e8838ad0719401b991513fedaba9961e3f8 comparison_groups.csv
|
| 6 |
+
ad522ba09cd16227f88215a62c4f71f64f0af9fe930ea39f93a0c406f6d9a279 comparison_v1_1.json
|
| 7 |
+
bf6ce722e843f4246d42fa86bb17a665eb0de514a378bd7aed9ba36b1f9c47e3 comparison_v1_1_per_tag.csv
|
| 8 |
+
39e218e1674f5fb4e00023aa4bcea778c09704d856f9f833ef01bb251d1415fd config.json
|
| 9 |
+
c83f832a32b6a8b8802dd9207f2f9be2ebef2a6d169d6daf1cdb2530cfe287ab custom_drop_path.py
|
| 10 |
+
ab4c1c04c03f5cefe3153bede72878a0d2df9e8378724922ed1571338b38d3c6 download.py
|
| 11 |
+
23f6dd10da65f031e5aae31fe88aefa35c6aa34e33d31986d45c9f9d57c705b8 frozen_evaluation_contract.json
|
| 12 |
+
79b15ad63de33354bc0a39176cd9eddd915361bd48b6c27b885009aac77185ea infer.py
|
| 13 |
+
f828ef9205af946e5633e416044fd84d6568292bfc1e02cb0852f484368426da inference_checks.json
|
| 14 |
+
373cc75ed9975e58864e4ed73a34316a696b6091b83971207677a6dae924fcd2 mask_utils.py
|
| 15 |
+
49ba0f66f83ba88a101b6edac54ec39f0a12eab66743e13ae42efc3570745bf7 model.onnx
|
| 16 |
+
ec07d0034f48ba3ceff8624993baf3ea30fa3a2e3cc8908682eb3e4f84eda766 model.safetensors
|
| 17 |
+
bce3773157a4c4dfc153c633d489c1e7c18f544b7fac6f51bd318ebe0fb7c6aa model_architecture.py
|
| 18 |
+
1c9af089a8c150b5270bc933fe4053a913f284a4242b53cdb94d108c529b2ef9 preprocessing.json
|
| 19 |
+
b058043b832bbb02c4139d3766dc8f1aac1fb781f90c897cdcf9230694cc2126 release_manifest.json
|
| 20 |
+
d604ef2e5271edac5d5b4bba2eeccf11102e69dd82667f40ca0be417b806ea62 requirements-gpu.txt
|
| 21 |
+
9d4beb51ff7c74d2ff68981496e6c1b37f1ab9cc85a53e692cb22c345c1ee3d7 requirements-torch.txt
|
| 22 |
+
abfcf5a28c6c88fa86822c1d7bcbf7afca92a893b8a6a4a65d3267a8786c3b28 requirements.txt
|
| 23 |
+
ec2b028ae5c8355bc7ccbab51ef967ef4bec940b686fe3e564e0177a865c9799 selected_tags.csv
|
| 24 |
+
3b20a011b5acf7732c1546dab99d6a56f48730eb54cadf30434f1dba62053436 thresholds.json
|
| 25 |
+
1a9ba147bc89a2eef54f2e80ae406f8652332fb91c9f2e3cf7fb05556388ad44 training_buckets.csv
|
| 26 |
+
54ef172f7c21c8cb414bef220e6ca45a33871134c99a4b3a1532c425f8ebc989 training_epochs.csv
|
| 27 |
+
6d4af05a7ee8d54fd09aae488b0fd470a39c2cb16430e0f31597a3b9a867800a training_metadata.json
|
| 28 |
+
fd2475144e7583842f4c733ad77d770749964b147a41e732a58c868516fc4e6f validation.json
|
| 29 |
+
e3e6ccc137d9152d8a17fbd19e0c1ae595b2317a6c55893fd25779620b4b8087 vocabulary.json
|
checkpoint_evidence.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"checkpoint": "checkpoint_epoch_9_step_55719.pt",
|
| 3 |
+
"checkpoint_sha256": "d400c2e460e7287357f86083f84c187790a45b1d47985e908c52825373f888c3",
|
| 4 |
+
"epoch": 9,
|
| 5 |
+
"step": 55719,
|
| 6 |
+
"scheduler": {
|
| 7 |
+
"warmup_steps": 6191,
|
| 8 |
+
"total_steps": 55719,
|
| 9 |
+
"cooldown_fraction": 0.15,
|
| 10 |
+
"budget_cooldown_start": 48451,
|
| 11 |
+
"min_lr": 0.0,
|
| 12 |
+
"base_max_lr": 2.9764702249476646e-05,
|
| 13 |
+
"cooldown_start": 48451,
|
| 14 |
+
"cooldown_steps": 7268,
|
| 15 |
+
"cooldown_reason": "budget",
|
| 16 |
+
"cooldown_best_metric": 0.5750626921653748,
|
| 17 |
+
"base_lrs": [
|
| 18 |
+
2.9764702249476646e-05,
|
| 19 |
+
2.9764702249476646e-05
|
| 20 |
+
],
|
| 21 |
+
"last_epoch": 55719,
|
| 22 |
+
"_step_count": 55720,
|
| 23 |
+
"_is_initial": false,
|
| 24 |
+
"_get_lr_called_within_step": false,
|
| 25 |
+
"_last_lr": [
|
| 26 |
+
0.0,
|
| 27 |
+
0.0
|
| 28 |
+
]
|
| 29 |
+
},
|
| 30 |
+
"preprocessing": {
|
| 31 |
+
"normalize_mean": [
|
| 32 |
+
0.5,
|
| 33 |
+
0.5,
|
| 34 |
+
0.5
|
| 35 |
+
],
|
| 36 |
+
"normalize_std": [
|
| 37 |
+
0.5,
|
| 38 |
+
0.5,
|
| 39 |
+
0.5
|
| 40 |
+
],
|
| 41 |
+
"image_size": 448,
|
| 42 |
+
"patch_size": 16,
|
| 43 |
+
"color_order": "RGB"
|
| 44 |
+
}
|
| 45 |
+
}
|
comparison_groups.csv
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
group,tags,x1_ap,v11_ap,delta_points,x1_wins,v11_wins,ties
|
| 2 |
+
all shared supported,13394,58.75134140920024,59.27161263163755,-0.5202712200966514,5425,6271,1698
|
| 3 |
+
shared with >=50 EVAL positives,2028,57.690053587344046,55.98307259190696,1.7069809948860188,1161,867,0
|
| 4 |
+
category gen,8676,43.75492858232571,43.96996116217886,-0.21503257453254906,4069,4420,187
|
| 5 |
+
category char,2870,91.87811929600326,91.97710045881874,-0.0989811631562091,815,965,1090
|
| 6 |
+
category copyright,1050,78.31961239122653,79.32927887092089,-1.0096664829623132,378,524,148
|
| 7 |
+
category artist,695,83.73359510134034,89.3767281228395,-5.643133035251241,109,314,272
|
| 8 |
+
category meta,99,28.44267269873385,27.431288556721427,1.0113841334870555,52,46,1
|
| 9 |
+
category rating,4,90.30946791172028,74.51295033097267,15.796517580747604,2,2,0
|
| 10 |
+
frequency 300-499,63,45.1914965982608,51.18916831404295,-5.997671779348618,14,36,13
|
| 11 |
+
frequency 500-999,3962,63.82945302063262,66.1829312171874,-2.3534781905738833,1093,1742,1127
|
| 12 |
+
frequency 1000-4999,6049,57.39406408068191,57.68059412367721,-0.286530040883122,2509,2986,554
|
| 13 |
+
frequency 5000-9999,1249,52.204597668254,51.39728918997086,0.8073084776866152,638,607,4
|
| 14 |
+
frequency 10000-plus,2071,57.361599985368,55.69153087854903,1.6700691066390936,1171,900,0
|
comparison_v1_1.json
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"x1": {
|
| 3 |
+
"model": "X1 448 final epoch 9",
|
| 4 |
+
"checkpoint": {
|
| 5 |
+
"checkpoint": "checkpoint_epoch_9_step_55719.pt",
|
| 6 |
+
"checkpoint_sha256": "d400c2e460e7287357f86083f84c187790a45b1d47985e908c52825373f888c3",
|
| 7 |
+
"epoch": 9,
|
| 8 |
+
"step": 55719,
|
| 9 |
+
"scheduler": {
|
| 10 |
+
"warmup_steps": 6191,
|
| 11 |
+
"total_steps": 55719,
|
| 12 |
+
"cooldown_fraction": 0.15,
|
| 13 |
+
"budget_cooldown_start": 48451,
|
| 14 |
+
"min_lr": 0.0,
|
| 15 |
+
"base_max_lr": 2.9764702249476646e-05,
|
| 16 |
+
"cooldown_start": 48451,
|
| 17 |
+
"cooldown_steps": 7268,
|
| 18 |
+
"cooldown_reason": "budget",
|
| 19 |
+
"cooldown_best_metric": 0.5750626921653748,
|
| 20 |
+
"base_lrs": [
|
| 21 |
+
2.9764702249476646e-05,
|
| 22 |
+
2.9764702249476646e-05
|
| 23 |
+
],
|
| 24 |
+
"last_epoch": 55719,
|
| 25 |
+
"_step_count": 55720,
|
| 26 |
+
"_is_initial": false,
|
| 27 |
+
"_get_lr_called_within_step": false,
|
| 28 |
+
"_last_lr": [
|
| 29 |
+
0.0,
|
| 30 |
+
0.0
|
| 31 |
+
]
|
| 32 |
+
},
|
| 33 |
+
"preprocessing": {
|
| 34 |
+
"normalize_mean": [
|
| 35 |
+
0.5,
|
| 36 |
+
0.5,
|
| 37 |
+
0.5
|
| 38 |
+
],
|
| 39 |
+
"normalize_std": [
|
| 40 |
+
0.5,
|
| 41 |
+
0.5,
|
| 42 |
+
0.5
|
| 43 |
+
],
|
| 44 |
+
"image_size": 448,
|
| 45 |
+
"patch_size": 16,
|
| 46 |
+
"color_order": "RGB"
|
| 47 |
+
}
|
| 48 |
+
},
|
| 49 |
+
"model_sha256": "c5fcab0fb53e07aee437f3ef29e19a1e60b3dc18380048fa6d99baf35fa6c8f3",
|
| 50 |
+
"native_image_size": 448,
|
| 51 |
+
"samples": 30000,
|
| 52 |
+
"sample_sha256": "20a60e7a747cdf9a698a464c9d324612aad2ca6b43a541db0813554eb9af8694",
|
| 53 |
+
"shared_tags": 13569,
|
| 54 |
+
"supported_shared_tags": 13394,
|
| 55 |
+
"binned_mAP": 0.5851927682386426,
|
| 56 |
+
"exact_mAP": 0.5875134140920024,
|
| 57 |
+
"best_grid_micro_f1": 0.6864101262842809,
|
| 58 |
+
"best_grid_threshold": 0.7085427045822144,
|
| 59 |
+
"precision_at_best_grid": 0.7065020665350971,
|
| 60 |
+
"recall_at_best_grid": 0.6674293596992867,
|
| 61 |
+
"all_supported_tags": 14767,
|
| 62 |
+
"all_tag_exact_mAP": 0.5774638913821051,
|
| 63 |
+
"all_tag_binned_mAP": 0.5750950549967043,
|
| 64 |
+
"inference_seconds": 347.91008079855237,
|
| 65 |
+
"total_seconds": 487.7315649986267,
|
| 66 |
+
"preprocessing": "FP32 RGB, native resolution, downscale-only centered LANCZOS letterbox, mean/std 0.5, pad 114, True=padding",
|
| 67 |
+
"verified_sidecars": 30000
|
| 68 |
+
},
|
| 69 |
+
"v11": {
|
| 70 |
+
"model": "V1.1",
|
| 71 |
+
"model_sha256": "8567852deb135eccfe4b8445d48e4476ee8846436486679adc0642cfeda07d13",
|
| 72 |
+
"native_image_size": 448,
|
| 73 |
+
"samples": 30000,
|
| 74 |
+
"sample_sha256": "20a60e7a747cdf9a698a464c9d324612aad2ca6b43a541db0813554eb9af8694",
|
| 75 |
+
"shared_tags": 13569,
|
| 76 |
+
"supported_shared_tags": 13394,
|
| 77 |
+
"binned_mAP": 0.5886105397423468,
|
| 78 |
+
"exact_mAP": 0.5927161263163755,
|
| 79 |
+
"best_grid_micro_f1": 0.6658638534826367,
|
| 80 |
+
"best_grid_threshold": 0.7989949584007263,
|
| 81 |
+
"precision_at_best_grid": 0.6852139862873703,
|
| 82 |
+
"recall_at_best_grid": 0.6475765838848551,
|
| 83 |
+
"inference_seconds": 416.6925043994561,
|
| 84 |
+
"total_seconds": 522.6292808055878,
|
| 85 |
+
"preprocessing": "FP32 RGB, native resolution, downscale-only centered LANCZOS letterbox, mean/std 0.5, pad 114, True=padding"
|
| 86 |
+
},
|
| 87 |
+
"shared_tag_results": [
|
| 88 |
+
{
|
| 89 |
+
"group": "all shared supported",
|
| 90 |
+
"tags": 13394,
|
| 91 |
+
"x1_ap": 58.75134140920024,
|
| 92 |
+
"v11_ap": 59.27161263163755,
|
| 93 |
+
"delta_points": -0.5202712200966514,
|
| 94 |
+
"x1_wins": 5425,
|
| 95 |
+
"v11_wins": 6271,
|
| 96 |
+
"ties": 1698
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
"group": "shared with >=50 EVAL positives",
|
| 100 |
+
"tags": 2028,
|
| 101 |
+
"x1_ap": 57.690053587344046,
|
| 102 |
+
"v11_ap": 55.98307259190696,
|
| 103 |
+
"delta_points": 1.7069809948860188,
|
| 104 |
+
"x1_wins": 1161,
|
| 105 |
+
"v11_wins": 867,
|
| 106 |
+
"ties": 0
|
| 107 |
+
},
|
| 108 |
+
{
|
| 109 |
+
"group": "category gen",
|
| 110 |
+
"tags": 8676,
|
| 111 |
+
"x1_ap": 43.75492858232571,
|
| 112 |
+
"v11_ap": 43.96996116217886,
|
| 113 |
+
"delta_points": -0.21503257453254906,
|
| 114 |
+
"x1_wins": 4069,
|
| 115 |
+
"v11_wins": 4420,
|
| 116 |
+
"ties": 187
|
| 117 |
+
},
|
| 118 |
+
{
|
| 119 |
+
"group": "category char",
|
| 120 |
+
"tags": 2870,
|
| 121 |
+
"x1_ap": 91.87811929600326,
|
| 122 |
+
"v11_ap": 91.97710045881874,
|
| 123 |
+
"delta_points": -0.0989811631562091,
|
| 124 |
+
"x1_wins": 815,
|
| 125 |
+
"v11_wins": 965,
|
| 126 |
+
"ties": 1090
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"group": "category copyright",
|
| 130 |
+
"tags": 1050,
|
| 131 |
+
"x1_ap": 78.31961239122653,
|
| 132 |
+
"v11_ap": 79.32927887092089,
|
| 133 |
+
"delta_points": -1.0096664829623132,
|
| 134 |
+
"x1_wins": 378,
|
| 135 |
+
"v11_wins": 524,
|
| 136 |
+
"ties": 148
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"group": "category artist",
|
| 140 |
+
"tags": 695,
|
| 141 |
+
"x1_ap": 83.73359510134034,
|
| 142 |
+
"v11_ap": 89.3767281228395,
|
| 143 |
+
"delta_points": -5.643133035251241,
|
| 144 |
+
"x1_wins": 109,
|
| 145 |
+
"v11_wins": 314,
|
| 146 |
+
"ties": 272
|
| 147 |
+
},
|
| 148 |
+
{
|
| 149 |
+
"group": "category meta",
|
| 150 |
+
"tags": 99,
|
| 151 |
+
"x1_ap": 28.44267269873385,
|
| 152 |
+
"v11_ap": 27.431288556721427,
|
| 153 |
+
"delta_points": 1.0113841334870555,
|
| 154 |
+
"x1_wins": 52,
|
| 155 |
+
"v11_wins": 46,
|
| 156 |
+
"ties": 1
|
| 157 |
+
},
|
| 158 |
+
{
|
| 159 |
+
"group": "category rating",
|
| 160 |
+
"tags": 4,
|
| 161 |
+
"x1_ap": 90.30946791172028,
|
| 162 |
+
"v11_ap": 74.51295033097267,
|
| 163 |
+
"delta_points": 15.796517580747604,
|
| 164 |
+
"x1_wins": 2,
|
| 165 |
+
"v11_wins": 2,
|
| 166 |
+
"ties": 0
|
| 167 |
+
},
|
| 168 |
+
{
|
| 169 |
+
"group": "frequency 300-499",
|
| 170 |
+
"tags": 63,
|
| 171 |
+
"x1_ap": 45.1914965982608,
|
| 172 |
+
"v11_ap": 51.18916831404295,
|
| 173 |
+
"delta_points": -5.997671779348618,
|
| 174 |
+
"x1_wins": 14,
|
| 175 |
+
"v11_wins": 36,
|
| 176 |
+
"ties": 13
|
| 177 |
+
},
|
| 178 |
+
{
|
| 179 |
+
"group": "frequency 500-999",
|
| 180 |
+
"tags": 3962,
|
| 181 |
+
"x1_ap": 63.82945302063262,
|
| 182 |
+
"v11_ap": 66.1829312171874,
|
| 183 |
+
"delta_points": -2.3534781905738833,
|
| 184 |
+
"x1_wins": 1093,
|
| 185 |
+
"v11_wins": 1742,
|
| 186 |
+
"ties": 1127
|
| 187 |
+
},
|
| 188 |
+
{
|
| 189 |
+
"group": "frequency 1000-4999",
|
| 190 |
+
"tags": 6049,
|
| 191 |
+
"x1_ap": 57.39406408068191,
|
| 192 |
+
"v11_ap": 57.68059412367721,
|
| 193 |
+
"delta_points": -0.286530040883122,
|
| 194 |
+
"x1_wins": 2509,
|
| 195 |
+
"v11_wins": 2986,
|
| 196 |
+
"ties": 554
|
| 197 |
+
},
|
| 198 |
+
{
|
| 199 |
+
"group": "frequency 5000-9999",
|
| 200 |
+
"tags": 1249,
|
| 201 |
+
"x1_ap": 52.204597668254,
|
| 202 |
+
"v11_ap": 51.39728918997086,
|
| 203 |
+
"delta_points": 0.8073084776866152,
|
| 204 |
+
"x1_wins": 638,
|
| 205 |
+
"v11_wins": 607,
|
| 206 |
+
"ties": 4
|
| 207 |
+
},
|
| 208 |
+
{
|
| 209 |
+
"group": "frequency 10000-plus",
|
| 210 |
+
"tags": 2071,
|
| 211 |
+
"x1_ap": 57.361599985368,
|
| 212 |
+
"v11_ap": 55.69153087854903,
|
| 213 |
+
"delta_points": 1.6700691066390936,
|
| 214 |
+
"x1_wins": 1171,
|
| 215 |
+
"v11_wins": 900,
|
| 216 |
+
"ties": 0
|
| 217 |
+
}
|
| 218 |
+
],
|
| 219 |
+
"exact_map_delta_points": -0.5202712224373074,
|
| 220 |
+
"micro_f1_delta_points": 2.0546272801644228,
|
| 221 |
+
"comparability": "Same frozen 30,000 EVAL identities and labels; every source sidecar hash verified; identical one-to-one shared tags and support; both 448 FP32 ONNX CUDA with TF32 disabled and identical preprocessing. V1.1 metrics reused from verified archived evaluation. Development-set comparison; V1.1 training overlap is unverified. F1 thresholds independently optimized on the same 200-point grid; not deployment calibration."
|
| 222 |
+
}
|
comparison_v1_1_per_tag.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
config.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "oppaioracle_vit",
|
| 3 |
+
"architectures": [
|
| 4 |
+
"SimplifiedTagger"
|
| 5 |
+
],
|
| 6 |
+
"release": "experimental-X1.1",
|
| 7 |
+
"repo_id": "Grio43/OppaiOracle-experimental-X1.1",
|
| 8 |
+
"image_size": 448,
|
| 9 |
+
"num_labels": 14996,
|
| 10 |
+
"parameter_count": 147706932,
|
| 11 |
+
"torch_dtype": "float32",
|
| 12 |
+
"architecture": {
|
| 13 |
+
"image_size": 448,
|
| 14 |
+
"patch_size": 16,
|
| 15 |
+
"num_channels": 3,
|
| 16 |
+
"hidden_size": 896,
|
| 17 |
+
"num_hidden_layers": 18,
|
| 18 |
+
"num_attention_heads": 14,
|
| 19 |
+
"intermediate_size": 2320,
|
| 20 |
+
"num_tags": 14996,
|
| 21 |
+
"dropout": 0.0,
|
| 22 |
+
"pos_dropout": 0.0,
|
| 23 |
+
"attention_dropout": 0.0,
|
| 24 |
+
"qk_norm": true,
|
| 25 |
+
"layer_scale_init": 0.1,
|
| 26 |
+
"layer_norm_eps": 1e-06,
|
| 27 |
+
"use_flex_attention": false,
|
| 28 |
+
"flex_block_size": 128,
|
| 29 |
+
"attention_bias": true,
|
| 30 |
+
"token_ignore_threshold": 0.9,
|
| 31 |
+
"gradient_checkpointing": false,
|
| 32 |
+
"checkpoint_every_n_layers": 1,
|
| 33 |
+
"drop_path_rate": 0.15,
|
| 34 |
+
"check_numerical_stability": false,
|
| 35 |
+
"logit_clamp_value": 15.0,
|
| 36 |
+
"use_fp32_layernorm": false
|
| 37 |
+
},
|
| 38 |
+
"preprocessing": {
|
| 39 |
+
"normalize_mean": [
|
| 40 |
+
0.5,
|
| 41 |
+
0.5,
|
| 42 |
+
0.5
|
| 43 |
+
],
|
| 44 |
+
"normalize_std": [
|
| 45 |
+
0.5,
|
| 46 |
+
0.5,
|
| 47 |
+
0.5
|
| 48 |
+
],
|
| 49 |
+
"image_size": 448,
|
| 50 |
+
"patch_size": 16,
|
| 51 |
+
"color_order": "RGB",
|
| 52 |
+
"pad_color": [
|
| 53 |
+
114,
|
| 54 |
+
114,
|
| 55 |
+
114
|
| 56 |
+
],
|
| 57 |
+
"resize_filter": "LANCZOS",
|
| 58 |
+
"resize_mode": "letterbox_downscale_only",
|
| 59 |
+
"upscale": false,
|
| 60 |
+
"exif_transpose": true,
|
| 61 |
+
"alpha_composite": true,
|
| 62 |
+
"input_layout": "NCHW",
|
| 63 |
+
"padding_mask_true_means": "padding",
|
| 64 |
+
"output_activation": "sigmoid",
|
| 65 |
+
"skip_indices": [
|
| 66 |
+
0,
|
| 67 |
+
1
|
| 68 |
+
]
|
| 69 |
+
},
|
| 70 |
+
"vocab_file": "vocabulary.json",
|
| 71 |
+
"vocab_sha256": "e3e6ccc137d9152d8a17fbd19e0c1ae595b2317a6c55893fd25779620b4b8087",
|
| 72 |
+
"model_file_onnx": "model.onnx",
|
| 73 |
+
"model_file_safetensors": "model.safetensors",
|
| 74 |
+
"threshold": 0.7085427045822144,
|
| 75 |
+
"threshold_calibrated": false,
|
| 76 |
+
"threshold_source": "200-point micro-F1 grid on 13,569 shared development tags; not final deployment calibration"
|
| 77 |
+
}
|
custom_drop_path.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
|
| 4 |
+
class SafeDropPath(nn.Module):
|
| 5 |
+
def __init__(self, drop_prob: float = 0.0):
|
| 6 |
+
super().__init__()
|
| 7 |
+
drop_prob = float(drop_prob)
|
| 8 |
+
if not (0.0 <= drop_prob < 1.0):
|
| 9 |
+
raise ValueError(f"drop_prob must be in [0, 1), got {drop_prob}")
|
| 10 |
+
self.drop_prob = drop_prob
|
| 11 |
+
|
| 12 |
+
def forward(self, x):
|
| 13 |
+
if not self.training or self.drop_prob == 0.0:
|
| 14 |
+
return x
|
| 15 |
+
keep_prob = 1.0 - self.drop_prob
|
| 16 |
+
# keep_prob is always > 0 due to __init__ validation
|
| 17 |
+
shape = (x.shape[0],) + (1,) * (x.ndim - 1)
|
| 18 |
+
# Draw in float32: drawing in x.dtype (e.g. bf16) quantizes keep_prob
|
| 19 |
+
# and skews the realized keep rate. The 0/1 mask casts back exactly.
|
| 20 |
+
random_tensor = keep_prob + torch.rand(shape, dtype=torch.float32, device=x.device)
|
| 21 |
+
random_tensor.floor_()
|
| 22 |
+
return x.div(keep_prob) * random_tensor.to(x.dtype)
|
download.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Download an immutable OppaiOracle release snapshot using Hugging Face Hub."""
|
| 3 |
+
import argparse
|
| 4 |
+
import json
|
| 5 |
+
from pathlib import Path, PurePosixPath
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
DEFAULT_REPO_ID = 'Grio43/OppaiOracle-experimental-X1.1'
|
| 9 |
+
SUPPORT_FILES = [
|
| 10 |
+
'README.md', 'LICENSE', 'config.json', 'selected_tags.csv', 'thresholds.json',
|
| 11 |
+
'infer.py', 'download.py', 'model_architecture.py', 'custom_drop_path.py',
|
| 12 |
+
'mask_utils.py', 'requirements*.txt', 'release_manifest.json',
|
| 13 |
+
'preprocessing.json', 'training_metadata.json', 'validation.json',
|
| 14 |
+
'comparison_v1_1.json', 'comparison_v1_1_per_tag.csv', 'comparison_groups.csv',
|
| 15 |
+
'checkpoint_evidence.json', 'frozen_evaluation_contract.json',
|
| 16 |
+
'training_epochs.csv', 'training_buckets.csv', 'inference_checks.json', 'SHA256SUMS',
|
| 17 |
+
]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def release_filename(name):
|
| 21 |
+
"""Require ordinary relative Hub filenames, without glob expansion."""
|
| 22 |
+
path = PurePosixPath(name)
|
| 23 |
+
if (not name or path.is_absolute() or '..' in path.parts
|
| 24 |
+
or any(char in name for char in ('\\', ':', '*', '?', '[', ']'))):
|
| 25 |
+
raise ValueError(f'Invalid release filename: {name!r}')
|
| 26 |
+
return name
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def download_release(destination, repo_id=DEFAULT_REPO_ID, model_format='onnx', revision='main'):
|
| 30 |
+
from huggingface_hub import HfApi, hf_hub_download, snapshot_download
|
| 31 |
+
|
| 32 |
+
# Resolve once, so config and model files cannot straddle a moving main.
|
| 33 |
+
commit = HfApi().model_info(repo_id=repo_id, revision=revision).sha
|
| 34 |
+
if not commit:
|
| 35 |
+
raise RuntimeError('Hugging Face did not return an immutable commit SHA')
|
| 36 |
+
destination = Path(destination)
|
| 37 |
+
config_path = hf_hub_download(repo_id=repo_id, filename='config.json',
|
| 38 |
+
revision=commit, local_dir=destination)
|
| 39 |
+
config = json.loads(Path(config_path).read_text(encoding='utf-8-sig'))
|
| 40 |
+
formats = ('onnx', 'safetensors') if model_format == 'all' else (model_format,)
|
| 41 |
+
models = [release_filename(config[f'model_file_{kind}']) for kind in formats]
|
| 42 |
+
vocab = release_filename(config.get('vocab_file', 'vocabulary.json'))
|
| 43 |
+
patterns = SUPPORT_FILES + [vocab] + models
|
| 44 |
+
snapshot_download(repo_id=repo_id, revision=commit, local_dir=destination,
|
| 45 |
+
allow_patterns=patterns)
|
| 46 |
+
record = {'repo_id': repo_id, 'commit': commit, 'format': model_format,
|
| 47 |
+
'model_files': models}
|
| 48 |
+
(destination / 'download_info.json').write_text(
|
| 49 |
+
json.dumps(record, indent=2) + '\n', encoding='utf-8')
|
| 50 |
+
return record
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def main():
|
| 54 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 55 |
+
parser.add_argument('destination', nargs='?', type=Path,
|
| 56 |
+
default=Path('OppaiOracle-experimental-X1.1'))
|
| 57 |
+
parser.add_argument('--repo-id', default=DEFAULT_REPO_ID)
|
| 58 |
+
parser.add_argument('--revision', default='main', help='Branch, tag, or commit; resolved to a pinned commit')
|
| 59 |
+
parser.add_argument('--format', choices=('onnx', 'safetensors', 'all'), default='onnx')
|
| 60 |
+
args = parser.parse_args()
|
| 61 |
+
record = download_release(args.destination, args.repo_id, args.format, args.revision)
|
| 62 |
+
print(json.dumps({'directory': str(args.destination.resolve()), **record}, indent=2))
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
if __name__ == '__main__':
|
| 66 |
+
main()
|
frozen_evaluation_contract.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"manifest": "./logs/v2_phase1/dataset_manifest.json",
|
| 3 |
+
"frozen_ignore_hash": "2194183cb84a03b1701e2aa1833176633223e906055a8e6d031ba13a64c1b021",
|
| 4 |
+
"current_ignore_hash": "b8fa4041e90e598c8ffcde983500b849606e93d349d5810bac1762f4a812bd60",
|
| 5 |
+
"frozen_vocab_file_sha256": "a168f4cb343e75fdbacea13de0c3ec2d970829ee0df68a193b4b18cea6a1ed81",
|
| 6 |
+
"newly_ignored_existing_tags": [
|
| 7 |
+
"meta:alt_text",
|
| 8 |
+
"meta:alternate_language",
|
| 9 |
+
"meta:annotated",
|
| 10 |
+
"meta:archived_source",
|
| 11 |
+
"meta:bad_bcy_id",
|
| 12 |
+
"meta:bad_blogspot_link",
|
| 13 |
+
"meta:bad_discord_link",
|
| 14 |
+
"meta:bad_facebook_link",
|
| 15 |
+
"meta:bad_fc2_link",
|
| 16 |
+
"meta:bad_hentai_foundry_id",
|
| 17 |
+
"meta:bad_imgur_id",
|
| 18 |
+
"meta:bad_instagram_id",
|
| 19 |
+
"meta:bad_lofter_id",
|
| 20 |
+
"meta:bad_nico_seiga_link",
|
| 21 |
+
"meta:bad_nijie_id",
|
| 22 |
+
"meta:bad_poipiku_id",
|
| 23 |
+
"meta:bad_source",
|
| 24 |
+
"meta:bad_tumblr_link",
|
| 25 |
+
"meta:bad_twitter_link",
|
| 26 |
+
"meta:bad_weibo_id",
|
| 27 |
+
"meta:check_artist",
|
| 28 |
+
"meta:check_character",
|
| 29 |
+
"meta:cropped",
|
| 30 |
+
"meta:derivative_work",
|
| 31 |
+
"meta:detexted",
|
| 32 |
+
"meta:fancy_embedded_notes",
|
| 33 |
+
"meta:hard-translated",
|
| 34 |
+
"meta:has_bad_revision",
|
| 35 |
+
"meta:has_censored_revision",
|
| 36 |
+
"meta:has_downscaled_revision",
|
| 37 |
+
"meta:imageboard_desourced",
|
| 38 |
+
"meta:inactive_account",
|
| 39 |
+
"meta:large_variant_set",
|
| 40 |
+
"meta:pixel-perfect_duplicate",
|
| 41 |
+
"meta:protected_link",
|
| 42 |
+
"meta:redrawn",
|
| 43 |
+
"meta:second-party_source",
|
| 44 |
+
"meta:source_larger",
|
| 45 |
+
"meta:source_mismatch",
|
| 46 |
+
"meta:source_smaller",
|
| 47 |
+
"meta:third-party_source",
|
| 48 |
+
"meta:variant_set"
|
| 49 |
+
],
|
| 50 |
+
"verified_sidecars": 30000,
|
| 51 |
+
"method": "Direct frozen-EVAL sidecars and original prepared vocabulary. Original ignored tags were excluded when this vocabulary was built; all original output labels remain evaluated. No preparation or training startup is performed."
|
| 52 |
+
}
|
infer.py
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Offline ONNX or safetensors inference for OppaiOracle experimental-X1.1."""
|
| 3 |
+
import argparse
|
| 4 |
+
import base64
|
| 5 |
+
import gzip
|
| 6 |
+
import hashlib
|
| 7 |
+
import json
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
from PIL import Image, ImageOps
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def read_json(path):
|
| 15 |
+
return json.loads(Path(path).read_text(encoding='utf-8-sig'))
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def local_file(directory, name):
|
| 19 |
+
"""Resolve a release-relative file without escaping the package directory."""
|
| 20 |
+
root = Path(directory).resolve()
|
| 21 |
+
path = (root / name).resolve()
|
| 22 |
+
if not path.is_relative_to(root):
|
| 23 |
+
raise ValueError(f'Release file must be inside {root}: {name}')
|
| 24 |
+
return path
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def vocabulary_sha256(vocabulary):
|
| 28 |
+
canonical = json.dumps(vocabulary, ensure_ascii=False, sort_keys=True,
|
| 29 |
+
separators=(',', ':')).encode('utf-8')
|
| 30 |
+
return hashlib.sha256(canonical).hexdigest()
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def load_config(config_path):
|
| 34 |
+
config = read_json(config_path)
|
| 35 |
+
pre = config['preprocessing']
|
| 36 |
+
if (int(pre['image_size']) != 448 or pre.get('color_order', 'RGB') != 'RGB'
|
| 37 |
+
or list(pre['pad_color']) != [114, 114, 114]
|
| 38 |
+
or list(pre['normalize_mean']) != [0.5, 0.5, 0.5]
|
| 39 |
+
or list(pre['normalize_std']) != [0.5, 0.5, 0.5]
|
| 40 |
+
or pre.get('upscale', False)):
|
| 41 |
+
raise ValueError('Expected the X1.1 448px RGB/downscale-only preprocessing contract')
|
| 42 |
+
if pre.get('resize_filter', 'LANCZOS').upper() != 'LANCZOS':
|
| 43 |
+
raise ValueError('X1.1 requires LANCZOS resampling')
|
| 44 |
+
if int(config['architecture']['image_size']) != int(pre['image_size']):
|
| 45 |
+
raise ValueError('Architecture and preprocessing image sizes differ')
|
| 46 |
+
directory = Path(config_path).resolve().parent
|
| 47 |
+
vocab = read_json(local_file(directory, config.get('vocab_file', 'vocabulary.json')))
|
| 48 |
+
digest = vocabulary_sha256(vocab)
|
| 49 |
+
if digest != config['vocab_sha256']:
|
| 50 |
+
raise ValueError('Vocabulary checksum does not match config.json')
|
| 51 |
+
names = vocab['index_to_tag']
|
| 52 |
+
num_tags = int(config['architecture']['num_tags'])
|
| 53 |
+
if set(names) != {str(i) for i in range(num_tags)}:
|
| 54 |
+
raise ValueError('Vocabulary indices must cover every model output exactly')
|
| 55 |
+
if names['0'] != '<PAD>' or names['1'] != '<UNK>':
|
| 56 |
+
raise ValueError('Expected PAD=0 and UNK=1')
|
| 57 |
+
if vocab['tag_to_index'] != {tag: int(index) for index, tag in names.items()}:
|
| 58 |
+
raise ValueError('Vocabulary index maps disagree')
|
| 59 |
+
return config, vocab
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def preprocess(path, metadata):
|
| 63 |
+
"""EXIF transpose, alpha composite, downscale-only LANCZOS, centered pad."""
|
| 64 |
+
size = int(metadata['image_size'])
|
| 65 |
+
pad = tuple(metadata['pad_color'])
|
| 66 |
+
with Image.open(path) as source:
|
| 67 |
+
img = ImageOps.exif_transpose(source)
|
| 68 |
+
if img.mode in ('RGBA', 'LA') or 'transparency' in img.info:
|
| 69 |
+
rgba = img.convert('RGBA')
|
| 70 |
+
background = Image.new('RGB', img.size, pad)
|
| 71 |
+
background.paste(rgba.convert('RGB'), mask=rgba.getchannel('A'))
|
| 72 |
+
img = background
|
| 73 |
+
else:
|
| 74 |
+
img = img.convert('RGB')
|
| 75 |
+
scale = min(1.0, size / img.width, size / img.height)
|
| 76 |
+
width = max(1, round(img.width * scale))
|
| 77 |
+
height = max(1, round(img.height * scale))
|
| 78 |
+
resized = img.resize((width, height), Image.Resampling.LANCZOS)
|
| 79 |
+
canvas = Image.new('RGB', (size, size), pad)
|
| 80 |
+
left, top = (size - width) // 2, (size - height) // 2
|
| 81 |
+
canvas.paste(resized, (left, top))
|
| 82 |
+
array = np.asarray(canvas, dtype=np.float32)
|
| 83 |
+
mean = np.asarray(metadata['normalize_mean'], dtype=np.float32)
|
| 84 |
+
std = np.asarray(metadata['normalize_std'], dtype=np.float32)
|
| 85 |
+
array = (array / 255.0 - mean) / std
|
| 86 |
+
mask = np.ones((size, size), dtype=bool)
|
| 87 |
+
mask[top:top + height, left:left + width] = False
|
| 88 |
+
return np.ascontiguousarray(array.transpose(2, 0, 1)), mask
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class ONNXRuntime:
|
| 92 |
+
def __init__(self, model_path, config, device):
|
| 93 |
+
import onnxruntime as ort
|
| 94 |
+
|
| 95 |
+
if device == 'cuda':
|
| 96 |
+
# Reuse CUDA DLLs from PyTorch when it is installed.
|
| 97 |
+
try:
|
| 98 |
+
import torch # noqa: F401
|
| 99 |
+
except ImportError:
|
| 100 |
+
pass
|
| 101 |
+
if hasattr(ort, 'preload_dlls'):
|
| 102 |
+
ort.preload_dlls()
|
| 103 |
+
if 'CUDAExecutionProvider' not in ort.get_available_providers():
|
| 104 |
+
raise RuntimeError('Install compatible onnxruntime-gpu or use --device cpu')
|
| 105 |
+
providers = [('CUDAExecutionProvider', {'use_tf32': False}), 'CPUExecutionProvider']
|
| 106 |
+
else:
|
| 107 |
+
providers = ['CPUExecutionProvider']
|
| 108 |
+
options = ort.SessionOptions()
|
| 109 |
+
options.intra_op_num_threads = 8
|
| 110 |
+
self.session = ort.InferenceSession(str(model_path), sess_options=options,
|
| 111 |
+
providers=providers)
|
| 112 |
+
if device == 'cuda' and 'CUDAExecutionProvider' not in self.session.get_providers():
|
| 113 |
+
raise RuntimeError('CUDA provider initialization failed')
|
| 114 |
+
metadata = self.session.get_modelmeta().custom_metadata_map
|
| 115 |
+
if metadata.get('preprocessing') != 'external':
|
| 116 |
+
raise ValueError('Expected the release ONNX model with external preprocessing')
|
| 117 |
+
if metadata.get('vocab_sha256') != config['vocab_sha256']:
|
| 118 |
+
raise ValueError('ONNX vocabulary checksum does not match config.json')
|
| 119 |
+
embedded = json.loads(gzip.decompress(base64.b64decode(metadata['vocab_b64_gzip'])))
|
| 120 |
+
if vocabulary_sha256(embedded) != config['vocab_sha256']:
|
| 121 |
+
raise ValueError('ONNX embedded vocabulary checksum mismatch')
|
| 122 |
+
for key in ('image_size', 'pad_color', 'normalize_mean', 'normalize_std'):
|
| 123 |
+
if json.loads(metadata[key]) != config['preprocessing'][key]:
|
| 124 |
+
raise ValueError(f'ONNX preprocessing differs from config.json: {key}')
|
| 125 |
+
if metadata.get('color_order', 'RGB') != config['preprocessing'].get('color_order', 'RGB'):
|
| 126 |
+
raise ValueError('ONNX color order differs from config.json')
|
| 127 |
+
inputs = {item.name: item.type for item in self.session.get_inputs()}
|
| 128 |
+
if set(inputs) != {'pixel_values', 'padding_mask'} or inputs['padding_mask'] != 'tensor(bool)':
|
| 129 |
+
raise ValueError('Unexpected ONNX input contract')
|
| 130 |
+
if inputs['pixel_values'] not in ('tensor(float)', 'tensor(float16)'):
|
| 131 |
+
raise ValueError('Unsupported ONNX input precision')
|
| 132 |
+
self.dtype = np.float16 if inputs['pixel_values'] == 'tensor(float16)' else np.float32
|
| 133 |
+
|
| 134 |
+
def predict(self, pixels, masks):
|
| 135 |
+
# The exported ONNX graph already includes sigmoid.
|
| 136 |
+
return self.session.run(['probabilities'], {
|
| 137 |
+
'pixel_values': pixels.astype(self.dtype, copy=False),
|
| 138 |
+
'padding_mask': masks,
|
| 139 |
+
})[0]
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
class SafetensorsRuntime:
|
| 143 |
+
def __init__(self, model_path, config, device):
|
| 144 |
+
import torch
|
| 145 |
+
from safetensors import safe_open
|
| 146 |
+
from safetensors.torch import load_file
|
| 147 |
+
from model_architecture import SimplifiedTagger, VisionTransformerConfig
|
| 148 |
+
|
| 149 |
+
if device == 'cuda' and not torch.cuda.is_available():
|
| 150 |
+
raise RuntimeError('CUDA is unavailable; use --device cpu')
|
| 151 |
+
torch.set_num_threads(8)
|
| 152 |
+
if device == 'cuda':
|
| 153 |
+
torch.backends.cuda.matmul.allow_tf32 = False
|
| 154 |
+
torch.backends.cudnn.allow_tf32 = False
|
| 155 |
+
architecture = dict(config['architecture'])
|
| 156 |
+
architecture.update(use_flex_attention=False, gradient_checkpointing=False)
|
| 157 |
+
self.model = SimplifiedTagger(VisionTransformerConfig(**architecture))
|
| 158 |
+
# This architecture selects eager SDPA through set_onnx_mode; changing
|
| 159 |
+
# use_flex_attention alone does not switch its forward implementation.
|
| 160 |
+
self.model.set_onnx_mode(True)
|
| 161 |
+
with safe_open(str(model_path), framework='pt', device='cpu') as handle:
|
| 162 |
+
metadata = handle.metadata() or {}
|
| 163 |
+
if metadata.get('vocab_sha256') != config['vocab_sha256']:
|
| 164 |
+
raise ValueError('Safetensors vocabulary checksum does not match config.json')
|
| 165 |
+
# No pickle loading or remote code downloads. Strict loading preserves
|
| 166 |
+
# every output index and catches incompatible architectures.
|
| 167 |
+
state = load_file(str(model_path), device='cpu')
|
| 168 |
+
self.model.load_state_dict(state, strict=True)
|
| 169 |
+
del state
|
| 170 |
+
self.model = self.model.float().to(device).eval()
|
| 171 |
+
self.device = device
|
| 172 |
+
|
| 173 |
+
def predict(self, pixels, masks):
|
| 174 |
+
import torch
|
| 175 |
+
|
| 176 |
+
with torch.inference_mode():
|
| 177 |
+
logits = self.model(torch.from_numpy(pixels).to(self.device),
|
| 178 |
+
padding_mask=torch.from_numpy(masks).to(self.device))['tag_logits']
|
| 179 |
+
return torch.sigmoid(logits.float()).cpu().numpy()
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def select_tags(scores, names, threshold, top_k):
|
| 183 |
+
indices = np.flatnonzero(scores >= threshold)
|
| 184 |
+
indices = indices[indices >= 2] # PAD and UNK are never predictions.
|
| 185 |
+
indices = indices[np.argsort(-scores[indices], kind='stable')]
|
| 186 |
+
if top_k:
|
| 187 |
+
indices = indices[:top_k]
|
| 188 |
+
return [{'name': names[str(int(i))], 'score': float(scores[i])} for i in indices]
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def main():
|
| 192 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 193 |
+
parser.add_argument('images', nargs='+', type=Path)
|
| 194 |
+
parser.add_argument('--format', choices=('onnx', 'safetensors'), default='onnx')
|
| 195 |
+
parser.add_argument('--model', type=Path, help='Override the local model file')
|
| 196 |
+
parser.add_argument('--config', type=Path, default=Path(__file__).resolve().parent / 'config.json')
|
| 197 |
+
parser.add_argument('--device', choices=('cpu', 'cuda'), default='cpu')
|
| 198 |
+
parser.add_argument('--threshold', type=float, help='Override the decision threshold in config.json')
|
| 199 |
+
parser.add_argument('--top-k', type=int, default=0, help='0 returns every tag above threshold')
|
| 200 |
+
parser.add_argument('--batch-size', type=int, default=1)
|
| 201 |
+
parser.add_argument('--output', type=Path)
|
| 202 |
+
args = parser.parse_args()
|
| 203 |
+
if not 1 <= args.batch_size <= 128:
|
| 204 |
+
parser.error('--batch-size must be between 1 and 128')
|
| 205 |
+
if args.top_k < 0 or (args.threshold is not None and not 0 <= args.threshold <= 1):
|
| 206 |
+
parser.error('Use a nonnegative --top-k and a threshold between 0 and 1')
|
| 207 |
+
config, vocab = load_config(args.config)
|
| 208 |
+
model_path = args.model or local_file(args.config.parent, config[f'model_file_{args.format}'])
|
| 209 |
+
threshold = args.threshold if args.threshold is not None else float(config['threshold'])
|
| 210 |
+
if not 0 <= threshold <= 1:
|
| 211 |
+
raise ValueError('Config threshold must be between 0 and 1')
|
| 212 |
+
runtime_type = ONNXRuntime if args.format == 'onnx' else SafetensorsRuntime
|
| 213 |
+
runtime = runtime_type(model_path, config, args.device)
|
| 214 |
+
results = []
|
| 215 |
+
for offset in range(0, len(args.images), args.batch_size):
|
| 216 |
+
paths = args.images[offset:offset + args.batch_size]
|
| 217 |
+
batch = [preprocess(path, config['preprocessing']) for path in paths]
|
| 218 |
+
pixels = np.stack([item[0] for item in batch])
|
| 219 |
+
masks = np.stack([item[1] for item in batch])
|
| 220 |
+
probabilities = runtime.predict(pixels, masks)
|
| 221 |
+
if probabilities.shape != (len(paths), int(config['architecture']['num_tags'])):
|
| 222 |
+
raise RuntimeError('Model output shape does not match vocabulary')
|
| 223 |
+
if not np.isfinite(probabilities).all() or ((probabilities < 0) | (probabilities > 1)).any():
|
| 224 |
+
raise RuntimeError('Model returned invalid probabilities')
|
| 225 |
+
for path, scores in zip(paths, probabilities):
|
| 226 |
+
results.append({'image': str(path), 'tags': select_tags(
|
| 227 |
+
scores, vocab['index_to_tag'], threshold, args.top_k)})
|
| 228 |
+
output = {
|
| 229 |
+
'model': model_path.name,
|
| 230 |
+
'format': args.format,
|
| 231 |
+
'threshold': threshold,
|
| 232 |
+
'threshold_source': 'user override' if args.threshold is not None else config.get('threshold_source', 'config.json'),
|
| 233 |
+
'vocab_sha256': config['vocab_sha256'],
|
| 234 |
+
'predictions': results,
|
| 235 |
+
}
|
| 236 |
+
rendered = json.dumps(output, indent=2, ensure_ascii=False)
|
| 237 |
+
if args.output:
|
| 238 |
+
args.output.write_text(rendered + '\n', encoding='utf-8')
|
| 239 |
+
else:
|
| 240 |
+
print(rendered)
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
if __name__ == '__main__':
|
| 244 |
+
main()
|
inference_checks.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"passed": true,
|
| 3 |
+
"scope": "Four deterministic synthetic image fixtures; full development AP is archived in validation.json",
|
| 4 |
+
"atol": 0.0005,
|
| 5 |
+
"rtol": 0.001,
|
| 6 |
+
"checks": [
|
| 7 |
+
{
|
| 8 |
+
"format": "onnx",
|
| 9 |
+
"device": "cpu",
|
| 10 |
+
"batch": 1,
|
| 11 |
+
"max_absolute_error_vs_cpu_safetensors": 1.1324882507324219e-06,
|
| 12 |
+
"mean_absolute_error": 6.287191922638158e-08
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
"format": "onnx",
|
| 16 |
+
"device": "cpu",
|
| 17 |
+
"batch": 2,
|
| 18 |
+
"max_absolute_error_vs_cpu_safetensors": 1.3113021850585938e-06,
|
| 19 |
+
"mean_absolute_error": 5.217425069758974e-08
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"format": "onnx",
|
| 23 |
+
"device": "cpu",
|
| 24 |
+
"batch": 4,
|
| 25 |
+
"max_absolute_error_vs_cpu_safetensors": 1.3113021850585938e-06,
|
| 26 |
+
"mean_absolute_error": 6.336669855500077e-08
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"device": "cpu",
|
| 30 |
+
"padding_mask_max_effect": 0.6925351023674011
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
"format": "onnx",
|
| 34 |
+
"device": "cuda",
|
| 35 |
+
"batch": 1,
|
| 36 |
+
"max_absolute_error_vs_cpu_safetensors": 7.748603820800781e-07,
|
| 37 |
+
"mean_absolute_error": 6.658750351107301e-08
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"format": "onnx",
|
| 41 |
+
"device": "cuda",
|
| 42 |
+
"batch": 2,
|
| 43 |
+
"max_absolute_error_vs_cpu_safetensors": 1.043081283569336e-06,
|
| 44 |
+
"mean_absolute_error": 5.9538443508699856e-08
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"format": "onnx",
|
| 48 |
+
"device": "cuda",
|
| 49 |
+
"batch": 4,
|
| 50 |
+
"max_absolute_error_vs_cpu_safetensors": 9.238719940185547e-07,
|
| 51 |
+
"mean_absolute_error": 6.518111206332833e-08
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"device": "cuda",
|
| 55 |
+
"padding_mask_max_effect": 0.6925351023674011
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"format": "safetensors",
|
| 59 |
+
"device": "cuda",
|
| 60 |
+
"batch": 2,
|
| 61 |
+
"max_absolute_error_vs_cpu_safetensors": 1.1920928955078125e-06
|
| 62 |
+
}
|
| 63 |
+
],
|
| 64 |
+
"versions": {
|
| 65 |
+
"torch": "2.14.0+cu130",
|
| 66 |
+
"numpy": "2.2.6"
|
| 67 |
+
},
|
| 68 |
+
"model_sha256": "49ba0f66f83ba88a101b6edac54ec39f0a12eab66743e13ae42efc3570745bf7",
|
| 69 |
+
"safetensors_sha256": "ec07d0034f48ba3ceff8624993baf3ea30fa3a2e3cc8908682eb3e4f84eda766"
|
| 70 |
+
}
|
mask_utils.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Mask migration shims.
|
| 4 |
+
Standardize pixel padding masks to (B,1,H,W) bool with True=PAD,
|
| 5 |
+
and pool to token-level ignore masks (B,L) with True=IGNORE.
|
| 6 |
+
"""
|
| 7 |
+
from typing import Optional
|
| 8 |
+
import torch
|
| 9 |
+
import torch.nn.functional as F
|
| 10 |
+
|
| 11 |
+
@torch.no_grad()
|
| 12 |
+
def ensure_pixel_padding_mask(
|
| 13 |
+
mask: torch.Tensor,
|
| 14 |
+
mask_semantics: str = 'auto'
|
| 15 |
+
) -> Optional[torch.Tensor]:
|
| 16 |
+
"""
|
| 17 |
+
(mask) -> (B,1,H,W,bool, True=PAD)
|
| 18 |
+
Accepts (B,H,W) or (B,1,H,W) of {bool,uint8,int,float}.
|
| 19 |
+
|
| 20 |
+
Args:
|
| 21 |
+
mask: Input mask tensor
|
| 22 |
+
mask_semantics: Interpretation of True values:
|
| 23 |
+
- 'auto': Heuristic detection (legacy, unreliable)
|
| 24 |
+
- 'pad': True means padding (no inversion needed)
|
| 25 |
+
- 'valid': True means valid pixels (will be inverted)
|
| 26 |
+
|
| 27 |
+
Returns:
|
| 28 |
+
Standardized mask with True=PAD semantics
|
| 29 |
+
"""
|
| 30 |
+
if mask is None:
|
| 31 |
+
return None
|
| 32 |
+
|
| 33 |
+
# Use tensor-based assertions that work during tracing
|
| 34 |
+
original_dims = mask.dim()
|
| 35 |
+
if mask.dim() == 3:
|
| 36 |
+
mask = mask.unsqueeze(1) # (B,1,H,W)
|
| 37 |
+
|
| 38 |
+
# Trace-friendly validation
|
| 39 |
+
if not torch.jit.is_tracing():
|
| 40 |
+
assert mask.dim() == 4, \
|
| 41 |
+
f"padding_mask must be (B,H,W) or (B,1,H,W) after expansion; got {original_dims}D -> {mask.dim()}D with shape {tuple(mask.shape)}"
|
| 42 |
+
|
| 43 |
+
# Binarize + cast
|
| 44 |
+
if mask.dtype != torch.bool:
|
| 45 |
+
mask = (mask > 0.5)
|
| 46 |
+
|
| 47 |
+
# Apply semantics
|
| 48 |
+
if mask_semantics == 'valid':
|
| 49 |
+
# Invert: True=valid -> True=pad
|
| 50 |
+
mask = ~mask
|
| 51 |
+
elif mask_semantics == 'pad':
|
| 52 |
+
# Already correct semantics
|
| 53 |
+
pass
|
| 54 |
+
elif mask_semantics == 'auto':
|
| 55 |
+
# Legacy heuristic (unreliable, deprecated) - per-sample decision
|
| 56 |
+
frac_true = mask.flatten(2).to(torch.float16).mean(dim=2) # (B, 1)
|
| 57 |
+
should_invert = frac_true > 0.5 # (B, 1)
|
| 58 |
+
mask = torch.where(should_invert.unsqueeze(-1).unsqueeze(-1), ~mask, mask)
|
| 59 |
+
# Log warning in non-tracing mode
|
| 60 |
+
if not torch.jit.is_tracing():
|
| 61 |
+
import logging
|
| 62 |
+
logging.getLogger(__name__).warning(
|
| 63 |
+
"Using 'auto' mask semantics detection is unreliable. "
|
| 64 |
+
"Please specify mask_semantics='pad' or 'valid' explicitly."
|
| 65 |
+
)
|
| 66 |
+
else:
|
| 67 |
+
raise ValueError(f"Invalid mask_semantics: {mask_semantics}. Use 'auto', 'pad', or 'valid'.")
|
| 68 |
+
|
| 69 |
+
return mask
|
| 70 |
+
|
| 71 |
+
@torch.no_grad()
|
| 72 |
+
def pixel_to_token_ignore(mask_b1hw: torch.Tensor, patch: int, threshold: float = 0.9) -> torch.Tensor:
|
| 73 |
+
"""
|
| 74 |
+
(B,1,H,W,bool True=PAD) -> (B,L,bool True=IGNORE)
|
| 75 |
+
Pools pad-fraction per patch via avg_pool2d(kernel=stride=patch) and marks
|
| 76 |
+
tokens IGNORE when pad_fraction >= threshold.
|
| 77 |
+
"""
|
| 78 |
+
B, C, H, W = mask_b1hw.shape
|
| 79 |
+
|
| 80 |
+
# Trace-friendly validation
|
| 81 |
+
if not torch.jit.is_tracing():
|
| 82 |
+
assert mask_b1hw.dim() == 4, \
|
| 83 |
+
f"Expected 4D tensor (B,1,H,W), got {mask_b1hw.dim()}D with shape {tuple(mask_b1hw.shape)}"
|
| 84 |
+
assert C == 1, \
|
| 85 |
+
f"Expected channel dimension of 1, got {C} with shape {tuple(mask_b1hw.shape)}"
|
| 86 |
+
assert mask_b1hw.dtype == torch.bool, \
|
| 87 |
+
f"mask_b1hw must be bool (True=PAD), got dtype {mask_b1hw.dtype}"
|
| 88 |
+
assert H % patch == 0, \
|
| 89 |
+
f"Image height {H} must be divisible by patch size {patch}"
|
| 90 |
+
assert W % patch == 0, \
|
| 91 |
+
f"Image width {W} must be divisible by patch size {patch}"
|
| 92 |
+
|
| 93 |
+
# pad_fraction per patch
|
| 94 |
+
pad_frac = F.avg_pool2d(mask_b1hw.to(torch.float16), kernel_size=patch, stride=patch) # (B,1,GH,GW)
|
| 95 |
+
ignore = (pad_frac.squeeze(1) >= threshold) # (B,GH,GW)
|
| 96 |
+
return ignore.flatten(1) # (B,L)
|
model.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:49ba0f66f83ba88a101b6edac54ec39f0a12eab66743e13ae42efc3570745bf7
|
| 3 |
+
size 592413547
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ec07d0034f48ba3ceff8624993baf3ea30fa3a2e3cc8908682eb3e4f84eda766
|
| 3 |
+
size 590858968
|
model_architecture.py
ADDED
|
@@ -0,0 +1,860 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Model Architecture for Anime Image Tagger - Direct Training (modified)
|
| 4 |
+
Vision Transformer for anime image tagging
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from abc import ABC, abstractmethod
|
| 8 |
+
import logging
|
| 9 |
+
import math
|
| 10 |
+
import warnings
|
| 11 |
+
from dataclasses import dataclass, fields
|
| 12 |
+
from typing import Any, Dict, Optional, Tuple
|
| 13 |
+
|
| 14 |
+
logger = logging.getLogger(__name__)
|
| 15 |
+
|
| 16 |
+
import torch
|
| 17 |
+
import torch.nn as nn
|
| 18 |
+
import torch.nn.functional as F
|
| 19 |
+
from mask_utils import ensure_pixel_padding_mask, pixel_to_token_ignore
|
| 20 |
+
from custom_drop_path import SafeDropPath
|
| 21 |
+
|
| 22 |
+
# Import Flex Attention (PyTorch 2.5+)
|
| 23 |
+
from torch.nn.attention.flex_attention import flex_attention, create_block_mask, BlockMask
|
| 24 |
+
|
| 25 |
+
logger.info("Using PyTorch Flex Attention")
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _check_triton_available() -> bool:
|
| 29 |
+
"""Check if Triton is available for compiled block mask creation."""
|
| 30 |
+
try:
|
| 31 |
+
import triton
|
| 32 |
+
return True
|
| 33 |
+
except ImportError:
|
| 34 |
+
return False
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
# Cache the Triton availability check at module load time
|
| 38 |
+
_TRITON_AVAILABLE = _check_triton_available()
|
| 39 |
+
# PyTorch 2.14 deprecates create_block_mask(_compile=True). Reuse the public
|
| 40 |
+
# compiled callable; compilation remains lazy until the first forward pass.
|
| 41 |
+
_CREATE_BLOCK_MASK = torch.compile(create_block_mask) if _TRITON_AVAILABLE else create_block_mask
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class BaseTagger(ABC, nn.Module):
|
| 45 |
+
"""Abstract base class for image tagging models."""
|
| 46 |
+
|
| 47 |
+
@abstractmethod
|
| 48 |
+
def forward(
|
| 49 |
+
self,
|
| 50 |
+
pixel_values: torch.Tensor,
|
| 51 |
+
padding_mask: Optional[torch.Tensor] = None,
|
| 52 |
+
) -> Dict[str, torch.Tensor]:
|
| 53 |
+
"""Forward pass returning tag_logits dict."""
|
| 54 |
+
pass
|
| 55 |
+
|
| 56 |
+
@abstractmethod
|
| 57 |
+
def set_onnx_mode(self, enabled: bool = True) -> None:
|
| 58 |
+
"""Enable/disable ONNX-compatible attention mode."""
|
| 59 |
+
pass
|
| 60 |
+
|
| 61 |
+
@property
|
| 62 |
+
@abstractmethod
|
| 63 |
+
def config(self):
|
| 64 |
+
"""Return model configuration."""
|
| 65 |
+
pass
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def initialize_tag_head_bias(
|
| 69 |
+
model: BaseTagger,
|
| 70 |
+
index_to_tag: Dict[int, str],
|
| 71 |
+
tag_frequencies: Dict[str, int],
|
| 72 |
+
total_samples: int,
|
| 73 |
+
min_prior: float = 1e-5,
|
| 74 |
+
max_prior: float = 0.99,
|
| 75 |
+
rated_samples: Optional[int] = None,
|
| 76 |
+
) -> None:
|
| 77 |
+
"""Initialize tag_head bias with log-prior for focal loss (RetinaNet technique).
|
| 78 |
+
|
| 79 |
+
Sets each tag's bias to log(prior / (1 - prior)) based on empirical tag frequency.
|
| 80 |
+
This makes the model start by predicting "mostly negative" for sparse labels,
|
| 81 |
+
providing meaningful gradients from the first step instead of random 0.5 predictions.
|
| 82 |
+
|
| 83 |
+
Must be called AFTER model creation (which zeros the bias) and BEFORE checkpoint
|
| 84 |
+
loading (which will overwrite the bias if resuming).
|
| 85 |
+
"""
|
| 86 |
+
with torch.no_grad():
|
| 87 |
+
bias = model.tag_head.bias
|
| 88 |
+
num_tags = bias.shape[0]
|
| 89 |
+
for idx in range(num_tags):
|
| 90 |
+
tag = index_to_tag.get(idx, "")
|
| 91 |
+
freq = tag_frequencies.get(tag, 0)
|
| 92 |
+
denominator = (rated_samples if tag.startswith('rating:') and rated_samples is not None
|
| 93 |
+
else total_samples)
|
| 94 |
+
# An entirely unrated corpus provides no rating prior evidence.
|
| 95 |
+
empirical = 0.25 if denominator == 0 and tag.startswith('rating:') else freq / max(1, denominator)
|
| 96 |
+
prior = max(min_prior, min(max_prior, empirical))
|
| 97 |
+
bias[idx] = math.log(prior / (1 - prior))
|
| 98 |
+
|
| 99 |
+
bias_vals = bias.tolist()
|
| 100 |
+
logger.info(
|
| 101 |
+
"Tag head bias initialized with log-prior: min=%.2f, max=%.2f, mean=%.2f",
|
| 102 |
+
min(bias_vals), max(bias_vals), sum(bias_vals) / len(bias_vals),
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
class LayerNormFp32(nn.LayerNorm):
|
| 107 |
+
"""
|
| 108 |
+
LayerNorm that optionally casts to float32 before calling the original LayerNorm.
|
| 109 |
+
This is to improve stability when using mixed precision training.
|
| 110 |
+
|
| 111 |
+
Args:
|
| 112 |
+
normalized_shape: Input shape from an expected input of size
|
| 113 |
+
eps: A value added to the denominator for numerical stability
|
| 114 |
+
elementwise_affine: A boolean value that when set to True, gives learnable parameters
|
| 115 |
+
use_fp32: If True, cast to float32 before LayerNorm (better stability).
|
| 116 |
+
If False, use native dtype (faster but potentially less stable).
|
| 117 |
+
"""
|
| 118 |
+
def __init__(self, normalized_shape, eps=1e-5, elementwise_affine=True, use_fp32=True):
|
| 119 |
+
super().__init__(normalized_shape, eps=eps, elementwise_affine=elementwise_affine)
|
| 120 |
+
self.use_fp32 = use_fp32
|
| 121 |
+
|
| 122 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 123 |
+
if self.use_fp32:
|
| 124 |
+
return super().forward(x.float()).type_as(x)
|
| 125 |
+
else:
|
| 126 |
+
return super().forward(x)
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
@dataclass
|
| 130 |
+
class VisionTransformerConfig:
|
| 131 |
+
"""Configuration for the Vision Transformer used in direct training."""
|
| 132 |
+
image_size: int = 448
|
| 133 |
+
# Patch size must divide image_size evenly to avoid losing border information (validated in __post_init__)
|
| 134 |
+
patch_size: int = 16
|
| 135 |
+
num_channels: int = 3
|
| 136 |
+
hidden_size: int = 1024
|
| 137 |
+
num_hidden_layers: int = 18
|
| 138 |
+
num_attention_heads: int = 16
|
| 139 |
+
intermediate_size: int = 4096
|
| 140 |
+
num_tags: int = 100000 # This should be overridden with actual vocab size (includes rating tags)
|
| 141 |
+
dropout: float = 0.1
|
| 142 |
+
pos_dropout: float = 0.0 # Position embedding dropout (0.0 = modern standard; drop_path handles regularization)
|
| 143 |
+
attention_dropout: float = 0.1
|
| 144 |
+
qk_norm: bool = False # Legacy checkpoints omit this; V2 explicitly enables it.
|
| 145 |
+
layer_scale_init: float = 0.0 # Zero disables LayerScale for legacy checkpoints.
|
| 146 |
+
layer_norm_eps: float = 1e-6
|
| 147 |
+
use_flex_attention: bool = True # Use Flex Attention (PyTorch 2.5+)
|
| 148 |
+
flex_block_size: int = 128 # Block size for Flex Attention sparse computation
|
| 149 |
+
attention_bias: bool = True # Use bias in attention QKV and projection layers
|
| 150 |
+
# Token ignore threshold: a token is ignored if >= this fraction of its pixels are PAD
|
| 151 |
+
token_ignore_threshold: float = 0.9
|
| 152 |
+
# Enable gradient checkpointing by default to reduce memory usage
|
| 153 |
+
gradient_checkpointing: bool = True
|
| 154 |
+
# Checkpoint every N-th layer (1=all layers, 2=every 2nd, 4=every 4th, etc.)
|
| 155 |
+
checkpoint_every_n_layers: int = 1
|
| 156 |
+
drop_path_rate: float = 0.0
|
| 157 |
+
# Enable numerical stability checking (for debugging only)
|
| 158 |
+
check_numerical_stability: bool = False
|
| 159 |
+
# Logit clamping for inference/export outputs (None to disable)
|
| 160 |
+
# exp(15) ~ 3.3M which is safe for softmax in float16/bfloat16
|
| 161 |
+
# Applied only in eval mode: training logits are left unclamped because
|
| 162 |
+
# clamp has zero gradient outside the bounds (see forward()).
|
| 163 |
+
logit_clamp_value: Optional[float] = 15.0
|
| 164 |
+
# Precision configuration
|
| 165 |
+
use_fp32_layernorm: bool = False # Use FP32 for LayerNorm (better stability, slight speed cost). Set to False for full bfloat16.
|
| 166 |
+
|
| 167 |
+
def __post_init__(self):
|
| 168 |
+
# Validate drop_path_rate
|
| 169 |
+
assert 0.0 <= self.drop_path_rate < 1.0, (
|
| 170 |
+
f"drop_path_rate must be in [0, 1), got {self.drop_path_rate}"
|
| 171 |
+
)
|
| 172 |
+
if self.drop_path_rate > 0.5:
|
| 173 |
+
warnings.warn(
|
| 174 |
+
f"drop_path_rate={self.drop_path_rate} is unusually high; "
|
| 175 |
+
"values between 0.08 and 0.3 are typical for ViT."
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
# Validate image_size and patch_size
|
| 179 |
+
if self.image_size <= 0:
|
| 180 |
+
raise ValueError(
|
| 181 |
+
f"image_size must be positive, got {self.image_size}"
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
if self.patch_size <= 0:
|
| 185 |
+
raise ValueError(
|
| 186 |
+
f"patch_size must be positive, got {self.patch_size}"
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
if self.image_size % self.patch_size != 0:
|
| 190 |
+
# Calculate valid alternatives
|
| 191 |
+
valid_sizes = [
|
| 192 |
+
s for s in [224, 256, 384, 448, 512, 576, 640, 768, 896, 1024]
|
| 193 |
+
if s % self.patch_size == 0 and abs(s - self.image_size) < 200
|
| 194 |
+
]
|
| 195 |
+
|
| 196 |
+
raise ValueError(
|
| 197 |
+
f"image_size ({self.image_size}) must be evenly divisible by "
|
| 198 |
+
f"patch_size ({self.patch_size}). "
|
| 199 |
+
f"Current: {self.image_size} % {self.patch_size} = {self.image_size % self.patch_size}. "
|
| 200 |
+
f"\n\nSuggested fixes:"
|
| 201 |
+
f"\n 1. Use a standard image size: {valid_sizes if valid_sizes else 'N/A'}"
|
| 202 |
+
f"\n 2. Change patch_size to a factor of {self.image_size}: "
|
| 203 |
+
f"{[d for d in [8, 14, 16, 20, 32] if self.image_size % d == 0]}"
|
| 204 |
+
f"\n 3. Adjust image_size to nearest multiple of {self.patch_size}: "
|
| 205 |
+
f"{(self.image_size // self.patch_size) * self.patch_size} or "
|
| 206 |
+
f"{((self.image_size // self.patch_size) + 1) * self.patch_size}"
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
# Validate computed values make sense
|
| 210 |
+
num_patches = (self.image_size // self.patch_size) ** 2
|
| 211 |
+
if num_patches < 4:
|
| 212 |
+
warnings.warn(
|
| 213 |
+
f"Very few patches ({num_patches}) with image_size={self.image_size} "
|
| 214 |
+
f"and patch_size={self.patch_size}. Model may underperform."
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
if num_patches > 10000:
|
| 218 |
+
warnings.warn(
|
| 219 |
+
f"Very many patches ({num_patches}) with image_size={self.image_size} "
|
| 220 |
+
f"and patch_size={self.patch_size}. May cause memory issues."
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
# Validate hidden_size is divisible by num_attention_heads
|
| 224 |
+
if self.hidden_size % self.num_attention_heads != 0:
|
| 225 |
+
raise ValueError(
|
| 226 |
+
f"hidden_size ({self.hidden_size}) must be divisible by "
|
| 227 |
+
f"num_attention_heads ({self.num_attention_heads}). "
|
| 228 |
+
f"Current remainder: {self.hidden_size % self.num_attention_heads}. "
|
| 229 |
+
f"head_dim would be {self.hidden_size / self.num_attention_heads:.2f} (must be integer)."
|
| 230 |
+
)
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
class TransformerBlock(nn.Module):
|
| 234 |
+
"""Single transformer block using Flex Attention."""
|
| 235 |
+
|
| 236 |
+
def __init__(self, config: VisionTransformerConfig, drop_path: float = 0.):
|
| 237 |
+
super().__init__()
|
| 238 |
+
self.config = config
|
| 239 |
+
self.norm1 = LayerNormFp32(config.hidden_size, eps=config.layer_norm_eps, use_fp32=config.use_fp32_layernorm)
|
| 240 |
+
|
| 241 |
+
# Flex Attention setup
|
| 242 |
+
self.num_heads = config.num_attention_heads
|
| 243 |
+
self.head_dim = config.hidden_size // config.num_attention_heads
|
| 244 |
+
self.scale = self.head_dim ** -0.5
|
| 245 |
+
self.flex_block_size = config.flex_block_size
|
| 246 |
+
|
| 247 |
+
# QKV projection (fused linear for efficiency)
|
| 248 |
+
self.qkv = nn.Linear(config.hidden_size, 3 * config.hidden_size, bias=config.attention_bias)
|
| 249 |
+
self.proj = nn.Linear(config.hidden_size, config.hidden_size, bias=config.attention_bias)
|
| 250 |
+
self.attn_dropout = config.attention_dropout
|
| 251 |
+
self.q_norm = LayerNormFp32(self.head_dim, eps=config.layer_norm_eps, use_fp32=True) if config.qk_norm else nn.Identity()
|
| 252 |
+
self.k_norm = LayerNormFp32(self.head_dim, eps=config.layer_norm_eps, use_fp32=True) if config.qk_norm else nn.Identity()
|
| 253 |
+
self.ls1 = nn.Parameter(torch.full((config.hidden_size,), config.layer_scale_init)) if config.layer_scale_init > 0 else None
|
| 254 |
+
self.ls2 = nn.Parameter(torch.full((config.hidden_size,), config.layer_scale_init)) if config.layer_scale_init > 0 else None
|
| 255 |
+
self.capture_attention_stats = False
|
| 256 |
+
self.max_attention_logit = None
|
| 257 |
+
|
| 258 |
+
# Drop path and MLP
|
| 259 |
+
self.drop_path = SafeDropPath(drop_path) if drop_path > 0.0 else nn.Identity()
|
| 260 |
+
self.norm2 = LayerNormFp32(config.hidden_size, eps=config.layer_norm_eps, use_fp32=config.use_fp32_layernorm)
|
| 261 |
+
self.mlp = nn.Sequential(
|
| 262 |
+
nn.Linear(config.hidden_size, config.intermediate_size),
|
| 263 |
+
nn.GELU(),
|
| 264 |
+
nn.Dropout(config.dropout),
|
| 265 |
+
nn.Linear(config.intermediate_size, config.hidden_size),
|
| 266 |
+
nn.Dropout(config.dropout)
|
| 267 |
+
)
|
| 268 |
+
|
| 269 |
+
def _normalize_qk(self, q, k):
|
| 270 |
+
q, k = self.q_norm(q), self.k_norm(k)
|
| 271 |
+
if self.capture_attention_stats:
|
| 272 |
+
# Chunked exact maximum: bounded scratch rather than retaining L x L
|
| 273 |
+
# scores for every layer. Only enabled for the diagnostic forward.
|
| 274 |
+
with torch.no_grad(), torch.autocast(device_type=q.device.type, enabled=False):
|
| 275 |
+
maximum = q.new_zeros((), dtype=torch.float32)
|
| 276 |
+
for start in range(0, q.shape[-2], 64):
|
| 277 |
+
scores = q[:, :, start:start + 64].float() @ k.float().transpose(-2, -1)
|
| 278 |
+
maximum = torch.maximum(maximum, scores.abs().amax() * self.scale)
|
| 279 |
+
self.max_attention_logit = maximum
|
| 280 |
+
return q, k
|
| 281 |
+
|
| 282 |
+
def _residual(self, x, attn_out):
|
| 283 |
+
attention = self.proj(attn_out)
|
| 284 |
+
x = x + self.drop_path(attention if self.ls1 is None else attention * self.ls1.to(attention.dtype))
|
| 285 |
+
mlp = self.mlp(self.norm2(x))
|
| 286 |
+
return x + self.drop_path(mlp if self.ls2 is None else mlp * self.ls2.to(mlp.dtype))
|
| 287 |
+
|
| 288 |
+
def forward(self, x: torch.Tensor, block_mask: Optional[BlockMask] = None) -> torch.Tensor:
|
| 289 |
+
"""Forward pass using Flex Attention.
|
| 290 |
+
|
| 291 |
+
Args:
|
| 292 |
+
x: Input tensor of shape (batch_size, seq_len, hidden_size)
|
| 293 |
+
block_mask: Optional pre-computed BlockMask for padding-aware attention.
|
| 294 |
+
Created once by SimplifiedTagger and shared across all layers.
|
| 295 |
+
|
| 296 |
+
Returns:
|
| 297 |
+
torch.Tensor: Output tensor of shape (batch_size, seq_len, hidden_size)
|
| 298 |
+
|
| 299 |
+
Note:
|
| 300 |
+
Uses pre-layer normalization (LayerNorm -> Attention -> Residual).
|
| 301 |
+
"""
|
| 302 |
+
normed_x = self.norm1(x)
|
| 303 |
+
B, L, D = normed_x.shape
|
| 304 |
+
|
| 305 |
+
# QKV projection and reshape
|
| 306 |
+
qkv = self.qkv(normed_x).reshape(B, L, 3, self.num_heads, self.head_dim)
|
| 307 |
+
q, k, v = qkv.unbind(2) # Each: (B, L, num_heads, head_dim)
|
| 308 |
+
|
| 309 |
+
# Transpose to (B, num_heads, L, head_dim) for flex_attention
|
| 310 |
+
# IMPORTANT: Make tensors contiguous after transpose to ensure proper memory layout.
|
| 311 |
+
# Non-contiguous tensors from reshape->unbind->transpose cause stride assertion
|
| 312 |
+
# failures when torch.compile traces the graph (inductor expects contiguous strides).
|
| 313 |
+
q = q.transpose(1, 2).contiguous()
|
| 314 |
+
k = k.transpose(1, 2).contiguous()
|
| 315 |
+
v = v.transpose(1, 2).contiguous()
|
| 316 |
+
q, k = self._normalize_qk(q, k)
|
| 317 |
+
|
| 318 |
+
# flex_attention has no native attention-weight dropout, so apply V-dropout
|
| 319 |
+
# before the call. Zeroing random value tokens removes their contribution to
|
| 320 |
+
# all queries — a documented approximation of attention dropout used in T5
|
| 321 |
+
# and several BERT variants. Replaces the previous post-attention output
|
| 322 |
+
# dropout, which did not regularize attention weights as the config name
|
| 323 |
+
# suggested.
|
| 324 |
+
if self.training and self.attn_dropout > 0:
|
| 325 |
+
v = F.dropout(v, p=self.attn_dropout, training=True)
|
| 326 |
+
|
| 327 |
+
# Flex Attention computation (block_mask is pre-computed and shared across layers)
|
| 328 |
+
attn_out = flex_attention(
|
| 329 |
+
q, k, v,
|
| 330 |
+
block_mask=block_mask,
|
| 331 |
+
scale=self.scale,
|
| 332 |
+
)
|
| 333 |
+
|
| 334 |
+
# Reshape back to (B, L, D) - ensure contiguous for torch.compile compatibility
|
| 335 |
+
attn_out = attn_out.transpose(1, 2).reshape(B, L, D).contiguous()
|
| 336 |
+
|
| 337 |
+
# Residual connections
|
| 338 |
+
x = self._residual(x, attn_out)
|
| 339 |
+
|
| 340 |
+
return x
|
| 341 |
+
|
| 342 |
+
def forward_sdpa(self, x: torch.Tensor, attn_mask: Optional[torch.Tensor] = None) -> torch.Tensor:
|
| 343 |
+
"""Forward pass using scaled_dot_product_attention (ONNX-compatible).
|
| 344 |
+
|
| 345 |
+
This method provides an ONNX-exportable alternative to the flex_attention path.
|
| 346 |
+
It uses F.scaled_dot_product_attention which is supported by ONNX opset 14+.
|
| 347 |
+
|
| 348 |
+
Args:
|
| 349 |
+
x: Input tensor of shape (batch_size, seq_len, hidden_size)
|
| 350 |
+
attn_mask: Optional boolean attention mask (B, L) with True=ATTEND, False=IGNORE.
|
| 351 |
+
Passed directly to SDPA as a boolean key-padding mask — avoids the
|
| 352 |
+
additive float mask path that forces PyTorch off the Flash/efficient
|
| 353 |
+
backends.
|
| 354 |
+
|
| 355 |
+
Returns:
|
| 356 |
+
torch.Tensor: Output tensor of shape (batch_size, seq_len, hidden_size)
|
| 357 |
+
|
| 358 |
+
Note:
|
| 359 |
+
Uses pre-layer normalization (LayerNorm -> Attention -> Residual).
|
| 360 |
+
For ONNX export, this path is used instead of flex_attention.
|
| 361 |
+
"""
|
| 362 |
+
normed_x = self.norm1(x)
|
| 363 |
+
B, L, D = normed_x.shape
|
| 364 |
+
|
| 365 |
+
# QKV projection and reshape
|
| 366 |
+
qkv = self.qkv(normed_x).reshape(B, L, 3, self.num_heads, self.head_dim)
|
| 367 |
+
q, k, v = qkv.unbind(2) # Each: (B, L, num_heads, head_dim)
|
| 368 |
+
|
| 369 |
+
# Transpose to (B, num_heads, L, head_dim) for scaled_dot_product_attention
|
| 370 |
+
q = q.transpose(1, 2).contiguous()
|
| 371 |
+
k = k.transpose(1, 2).contiguous()
|
| 372 |
+
v = v.transpose(1, 2).contiguous()
|
| 373 |
+
q, k = self._normalize_qk(q, k)
|
| 374 |
+
|
| 375 |
+
# Pass the boolean mask straight through. SDPA accepts a bool key-padding mask
|
| 376 |
+
# broadcastable to (B, num_heads, L_q, L_k); we broadcast (B, L) -> (B, 1, 1, L).
|
| 377 |
+
# Bool semantics for SDPA: True=ATTEND, False=IGNORE — already matches `attn_mask`.
|
| 378 |
+
sdpa_mask: Optional[torch.Tensor] = None
|
| 379 |
+
if attn_mask is not None:
|
| 380 |
+
if attn_mask.ndim != 2 or attn_mask.shape[0] != B or attn_mask.shape[1] != L:
|
| 381 |
+
raise ValueError(
|
| 382 |
+
f"attn_mask shape {attn_mask.shape} mismatch. "
|
| 383 |
+
f"Expected ({B}, {L}) for input shape ({B}, {L}, {D})"
|
| 384 |
+
)
|
| 385 |
+
sdpa_mask = attn_mask.unsqueeze(1).unsqueeze(2) # (B, 1, 1, L) bool
|
| 386 |
+
|
| 387 |
+
# Scaled dot-product attention (ONNX-compatible)
|
| 388 |
+
attn_out = F.scaled_dot_product_attention(
|
| 389 |
+
q, k, v,
|
| 390 |
+
attn_mask=sdpa_mask,
|
| 391 |
+
dropout_p=self.attn_dropout if self.training else 0.0,
|
| 392 |
+
scale=self.scale,
|
| 393 |
+
)
|
| 394 |
+
|
| 395 |
+
# Reshape back to (B, L, D)
|
| 396 |
+
attn_out = attn_out.transpose(1, 2).reshape(B, L, D).contiguous()
|
| 397 |
+
|
| 398 |
+
# Residual connections
|
| 399 |
+
x = self._residual(x, attn_out)
|
| 400 |
+
|
| 401 |
+
return x
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
@torch.no_grad()
|
| 405 |
+
def sample_attention_logits(model, images, padding_mask=None):
|
| 406 |
+
"""Exact absolute score maximum on a small diagnostic batch, without RNG drift."""
|
| 407 |
+
model = getattr(model, '_orig_mod', model)
|
| 408 |
+
old_mode = model._onnx_mode
|
| 409 |
+
devices = [images.device.index] if images.is_cuda else []
|
| 410 |
+
try:
|
| 411 |
+
model._onnx_mode = True # Eager SDPA avoids compiling a diagnostic graph.
|
| 412 |
+
for block in model.blocks:
|
| 413 |
+
block.capture_attention_stats = True
|
| 414 |
+
with torch.random.fork_rng(devices=devices):
|
| 415 |
+
model(images, padding_mask=padding_mask)
|
| 416 |
+
return torch.stack([block.max_attention_logit for block in model.blocks]).amax().item()
|
| 417 |
+
finally:
|
| 418 |
+
model._onnx_mode = old_mode
|
| 419 |
+
for block in model.blocks:
|
| 420 |
+
block.capture_attention_stats = False
|
| 421 |
+
block.max_attention_logit = None
|
| 422 |
+
|
| 423 |
+
|
| 424 |
+
class SimplifiedTagger(BaseTagger):
|
| 425 |
+
"""Vision Transformer based tagger for anime images."""
|
| 426 |
+
def __init__(self, config: VisionTransformerConfig):
|
| 427 |
+
super().__init__()
|
| 428 |
+
self._config = config
|
| 429 |
+
self._onnx_mode = False # When True, use SDPA instead of flex_attention
|
| 430 |
+
# Patch embedding layer
|
| 431 |
+
self.patch_embed = nn.Conv2d(
|
| 432 |
+
config.num_channels,
|
| 433 |
+
config.hidden_size,
|
| 434 |
+
kernel_size=config.patch_size,
|
| 435 |
+
stride=config.patch_size
|
| 436 |
+
)
|
| 437 |
+
# Position embeddings
|
| 438 |
+
num_patches = (config.image_size // config.patch_size) ** 2
|
| 439 |
+
self.pos_embed = nn.Parameter(
|
| 440 |
+
torch.zeros(1, num_patches + 1, config.hidden_size)
|
| 441 |
+
)
|
| 442 |
+
self.cls_token = nn.Parameter(
|
| 443 |
+
torch.zeros(1, 1, config.hidden_size)
|
| 444 |
+
)
|
| 445 |
+
# Initialize pos_embed and cls_token with truncated normal (std=0.02)
|
| 446 |
+
# These are nn.Parameter objects, not nn.Module, so _init_weights() doesn't handle them.
|
| 447 |
+
# Standard ViT initialization follows timm/HuggingFace convention.
|
| 448 |
+
nn.init.trunc_normal_(self.pos_embed, std=0.02)
|
| 449 |
+
nn.init.trunc_normal_(self.cls_token, std=0.02)
|
| 450 |
+
self.pos_drop = nn.Dropout(p=config.pos_dropout)
|
| 451 |
+
# Transformer blocks
|
| 452 |
+
rate = getattr(config, 'drop_path_rate', 0.0)
|
| 453 |
+
dpr = torch.linspace(0.0, rate, config.num_hidden_layers)
|
| 454 |
+
dpr = dpr.clamp_max(1.0 - 1e-6).tolist()
|
| 455 |
+
self.blocks = nn.ModuleList([
|
| 456 |
+
TransformerBlock(config, drop_path=float(p))
|
| 457 |
+
for p in dpr
|
| 458 |
+
])
|
| 459 |
+
# Final layer norm
|
| 460 |
+
self.norm = LayerNormFp32(config.hidden_size, eps=config.layer_norm_eps, use_fp32=config.use_fp32_layernorm)
|
| 461 |
+
# Classification head (ratings are now part of the tag vocabulary)
|
| 462 |
+
self.tag_head = nn.Linear(config.hidden_size, config.num_tags)
|
| 463 |
+
# Weight initialization
|
| 464 |
+
self.apply(self._init_weights)
|
| 465 |
+
# Override patch_embed init: standard ViT uses trunc_normal(std=0.02).
|
| 466 |
+
# The Conv2d branch in _init_weights sets std = sqrt(2/(k*k*out_channels))
|
| 467 |
+
# ≈ 0.0028 for our k=16, out_channels=1024 — ~7x smaller than ViT convention,
|
| 468 |
+
# which produces vanishing patch-embed activations early in from-scratch
|
| 469 |
+
# training. Re-init here so the apply() pass above doesn't overwrite us.
|
| 470 |
+
nn.init.trunc_normal_(self.patch_embed.weight, std=0.02)
|
| 471 |
+
if self.patch_embed.bias is not None:
|
| 472 |
+
nn.init.constant_(self.patch_embed.bias, 0)
|
| 473 |
+
|
| 474 |
+
def _init_weights(self, module):
|
| 475 |
+
if isinstance(module, nn.Linear):
|
| 476 |
+
torch.nn.init.trunc_normal_(module.weight, std=0.02)
|
| 477 |
+
if module.bias is not None:
|
| 478 |
+
nn.init.constant_(module.bias, 0)
|
| 479 |
+
elif isinstance(module, (nn.LayerNorm, LayerNormFp32)):
|
| 480 |
+
nn.init.constant_(module.bias, 0)
|
| 481 |
+
nn.init.constant_(module.weight, 1.0)
|
| 482 |
+
elif isinstance(module, nn.Conv2d):
|
| 483 |
+
fan_out = module.kernel_size[0] * module.kernel_size[1] * module.out_channels
|
| 484 |
+
fan_out //= module.groups
|
| 485 |
+
module.weight.data.normal_(0, math.sqrt(2.0 / fan_out))
|
| 486 |
+
if module.bias is not None:
|
| 487 |
+
module.bias.data.zero_()
|
| 488 |
+
|
| 489 |
+
def set_onnx_mode(self, enabled: bool = True) -> None:
|
| 490 |
+
"""Enable or disable ONNX-compatible mode.
|
| 491 |
+
|
| 492 |
+
When enabled, the model uses F.scaled_dot_product_attention instead of
|
| 493 |
+
flex_attention. This is required for ONNX export since flex_attention
|
| 494 |
+
is a PyTorch 2.5+ CUDA kernel that cannot be traced by ONNX.
|
| 495 |
+
|
| 496 |
+
Args:
|
| 497 |
+
enabled: If True, use SDPA (ONNX-compatible). If False, use flex_attention.
|
| 498 |
+
|
| 499 |
+
Note:
|
| 500 |
+
This only affects the attention computation path. Model weights are unchanged.
|
| 501 |
+
The SDPA path produces numerically equivalent results to flex_attention.
|
| 502 |
+
"""
|
| 503 |
+
self._onnx_mode = enabled
|
| 504 |
+
logger.info(f"ONNX mode {'enabled' if enabled else 'disabled'} - using {'SDPA' if enabled else 'flex_attention'}")
|
| 505 |
+
|
| 506 |
+
@property
|
| 507 |
+
def config(self):
|
| 508 |
+
"""Return model configuration."""
|
| 509 |
+
return self._config
|
| 510 |
+
|
| 511 |
+
def _get_pos_embed(self, grid_h: int, grid_w: int) -> torch.Tensor:
|
| 512 |
+
"""Return position embeddings matched to the runtime token grid.
|
| 513 |
+
|
| 514 |
+
Fast path (the common case, and the ONNX-export resolution): the runtime
|
| 515 |
+
grid equals the stored square grid, so the stored parameter is returned
|
| 516 |
+
unchanged. Otherwise the 2D patch position embeddings are bicubically
|
| 517 |
+
interpolated to (grid_h, grid_w) — same approach as the checkpoint-resume
|
| 518 |
+
interpolation in training_utils.py — keeping the CLS embedding as-is.
|
| 519 |
+
Without this, inputs at a different resolution would silently add the
|
| 520 |
+
raster-order *prefix* of the stored grid.
|
| 521 |
+
"""
|
| 522 |
+
stored_len = self.pos_embed.shape[1] # 1 (CLS) + stored_grid**2
|
| 523 |
+
stored_grid = int(math.isqrt(stored_len - 1))
|
| 524 |
+
if grid_h == stored_grid and grid_w == stored_grid:
|
| 525 |
+
return self.pos_embed
|
| 526 |
+
cls_pos = self.pos_embed[:, :1, :]
|
| 527 |
+
patch_pos = self.pos_embed[:, 1:, :]
|
| 528 |
+
hidden_size = patch_pos.shape[-1]
|
| 529 |
+
# Reshape to 2D spatial grid, interpolate, flatten back
|
| 530 |
+
patch_pos = patch_pos.reshape(1, stored_grid, stored_grid, hidden_size).permute(0, 3, 1, 2).float()
|
| 531 |
+
patch_pos = F.interpolate(
|
| 532 |
+
patch_pos, size=(grid_h, grid_w), mode='bicubic', align_corners=False
|
| 533 |
+
)
|
| 534 |
+
patch_pos = patch_pos.permute(0, 2, 3, 1).reshape(1, grid_h * grid_w, hidden_size)
|
| 535 |
+
patch_pos = patch_pos.to(self.pos_embed.dtype)
|
| 536 |
+
return torch.cat([cls_pos, patch_pos], dim=1)
|
| 537 |
+
|
| 538 |
+
def _check_numerical_stability(
|
| 539 |
+
self,
|
| 540 |
+
tag_logits: torch.Tensor,
|
| 541 |
+
) -> None:
|
| 542 |
+
"""Check for NaN/Inf in logits and log statistics.
|
| 543 |
+
|
| 544 |
+
This method is only called when config.check_numerical_stability=True.
|
| 545 |
+
It helps diagnose numerical instability issues during training/inference.
|
| 546 |
+
"""
|
| 547 |
+
tag_has_nan = torch.isnan(tag_logits).any().item()
|
| 548 |
+
tag_has_inf = torch.isinf(tag_logits).any().item()
|
| 549 |
+
|
| 550 |
+
if tag_has_nan or tag_has_inf:
|
| 551 |
+
warnings.warn(
|
| 552 |
+
f"Numerical instability in tag_logits: "
|
| 553 |
+
f"NaN={tag_has_nan}, Inf={tag_has_inf}. "
|
| 554 |
+
f"Stats: min={tag_logits.min():.2f}, max={tag_logits.max():.2f}, "
|
| 555 |
+
f"mean={tag_logits.mean():.2f}, std={tag_logits.std():.2f}"
|
| 556 |
+
)
|
| 557 |
+
|
| 558 |
+
# Diagnostic only: report how many logits exceed the *configured* clamp
|
| 559 |
+
# value (the real clamp happens later in forward() at logit_clamp_value,
|
| 560 |
+
# eval mode only — training logits are left unclamped).
|
| 561 |
+
clamp_threshold = self._config.logit_clamp_value
|
| 562 |
+
if clamp_threshold is not None:
|
| 563 |
+
tag_over_clamp = (tag_logits.abs() > clamp_threshold).any().item()
|
| 564 |
+
if tag_over_clamp:
|
| 565 |
+
num_over = (tag_logits.abs() > clamp_threshold).sum().item()
|
| 566 |
+
warnings.warn(
|
| 567 |
+
f"{num_over} tag logits exceed logit_clamp_value={clamp_threshold} "
|
| 568 |
+
f"(clamped downstream in eval mode only; max abs value: {tag_logits.abs().max():.2f})"
|
| 569 |
+
)
|
| 570 |
+
|
| 571 |
+
@torch.compiler.disable(recursive=False)
|
| 572 |
+
def _create_block_mask(self, key_padding_mask: torch.Tensor, seq_len: int) -> BlockMask:
|
| 573 |
+
"""Create BlockMask for padding-aware attention.
|
| 574 |
+
|
| 575 |
+
This method creates the mask once per forward pass, which is then shared
|
| 576 |
+
across all transformer layers for efficiency (instead of creating per-layer).
|
| 577 |
+
Keep its host dispatch outside the model graph: PyTorch 2.14 can raise
|
| 578 |
+
Inductor CantSplit when block-mask reductions are fused with dynamic image
|
| 579 |
+
shapes. recursive=False lets the inner mask builder remain compiled.
|
| 580 |
+
|
| 581 |
+
Args:
|
| 582 |
+
key_padding_mask: (B, L) bool, True=IGNORE (padding tokens)
|
| 583 |
+
seq_len: Sequence length
|
| 584 |
+
|
| 585 |
+
Returns:
|
| 586 |
+
BlockMask for use with flex_attention
|
| 587 |
+
"""
|
| 588 |
+
B = key_padding_mask.shape[0]
|
| 589 |
+
attend_mask = ~key_padding_mask # (B, L) True=ATTEND
|
| 590 |
+
|
| 591 |
+
def mask_mod(b, h, q_idx, kv_idx):
|
| 592 |
+
# Both query and key must be non-padding for attention
|
| 593 |
+
# This prevents padding tokens from attending (wasteful) and being attended to
|
| 594 |
+
# Use bitwise & instead of logical 'and' for Triton 3.4+ compatibility
|
| 595 |
+
return attend_mask[b, q_idx] & attend_mask[b, kv_idx]
|
| 596 |
+
|
| 597 |
+
# Use first block's config for flex_block_size
|
| 598 |
+
flex_block_size = self._config.flex_block_size
|
| 599 |
+
|
| 600 |
+
return _CREATE_BLOCK_MASK(
|
| 601 |
+
mask_mod,
|
| 602 |
+
B=B,
|
| 603 |
+
H=None, # Broadcast across heads - mask is head-independent (saves memory)
|
| 604 |
+
Q_LEN=seq_len,
|
| 605 |
+
KV_LEN=seq_len,
|
| 606 |
+
device=key_padding_mask.device,
|
| 607 |
+
BLOCK_SIZE=min(flex_block_size, seq_len),
|
| 608 |
+
)
|
| 609 |
+
|
| 610 |
+
def forward(
|
| 611 |
+
self,
|
| 612 |
+
pixel_values: torch.Tensor,
|
| 613 |
+
padding_mask: Optional[torch.Tensor] = None, # (B,H,W) or (B,1,H,W), auto-detected semantics
|
| 614 |
+
) -> Dict[str, torch.Tensor]:
|
| 615 |
+
B = pixel_values.shape[0]
|
| 616 |
+
# Patch embedding (optionally force fp32 for numerical stability under AMP)
|
| 617 |
+
if self._config.use_fp32_layernorm and pixel_values.dtype in (torch.float16, torch.bfloat16):
|
| 618 |
+
# Detect device type dynamically to support cuda/cpu/mps
|
| 619 |
+
device_type = pixel_values.device.type
|
| 620 |
+
# Autocast is only supported on certain device types
|
| 621 |
+
supported_devices = {'cuda', 'cpu', 'mps', 'xpu'}
|
| 622 |
+
|
| 623 |
+
if device_type in supported_devices:
|
| 624 |
+
# Use autocast to disable AMP for this operation
|
| 625 |
+
with torch.autocast(device_type=device_type, enabled=False):
|
| 626 |
+
x = self.patch_embed(pixel_values.float())
|
| 627 |
+
else:
|
| 628 |
+
# Fallback: Just convert to float32 without autocast context
|
| 629 |
+
# This works on all devices but doesn't interact with AMP
|
| 630 |
+
warnings.warn(
|
| 631 |
+
f"Device type '{device_type}' doesn't support autocast. "
|
| 632 |
+
f"Using fallback path for patch embedding."
|
| 633 |
+
)
|
| 634 |
+
x = self.patch_embed(pixel_values.float())
|
| 635 |
+
|
| 636 |
+
x = x.to(pixel_values.dtype)
|
| 637 |
+
else:
|
| 638 |
+
x = self.patch_embed(pixel_values)
|
| 639 |
+
# Capture the runtime token grid before flattening (for pos-embed matching)
|
| 640 |
+
grid_h, grid_w = x.shape[-2], x.shape[-1]
|
| 641 |
+
x = x.flatten(2).transpose(1, 2).contiguous()
|
| 642 |
+
# Add CLS token
|
| 643 |
+
cls_tokens = self.cls_token.expand(B, -1, -1)
|
| 644 |
+
x = torch.cat([cls_tokens, x], dim=1)
|
| 645 |
+
# Add position embeddings (bicubically interpolated to the runtime grid
|
| 646 |
+
# if it differs from the stored grid; no-op at the trained resolution)
|
| 647 |
+
x = x + self._get_pos_embed(grid_h, grid_w)
|
| 648 |
+
x = self.pos_drop(x)
|
| 649 |
+
# =======================================================================
|
| 650 |
+
# MASK SEMANTICS DOCUMENTATION
|
| 651 |
+
# =======================================================================
|
| 652 |
+
# This section handles padding masks with multiple transformations.
|
| 653 |
+
# Understanding the semantics at each step is crucial for correctness.
|
| 654 |
+
#
|
| 655 |
+
# STEP 1: Input padding_mask (pixel-level)
|
| 656 |
+
# - Shape: (B, H, W) or (B, 1, H, W)
|
| 657 |
+
# - Semantics: True = PADDING pixel (should be ignored)
|
| 658 |
+
#
|
| 659 |
+
# STEP 2: attn_kpm (token-level key padding mask)
|
| 660 |
+
# - Shape: (B, 1+Lp) where Lp = number of patch tokens
|
| 661 |
+
# - Semantics: True = IGNORE this token (it's mostly padding)
|
| 662 |
+
# - CLS token is always False (never ignored)
|
| 663 |
+
#
|
| 664 |
+
# STEP 3a: For ONNX/SDPA mode -> sdpa_attn_mask
|
| 665 |
+
# - We invert: sdpa_attn_mask = ~attn_kpm
|
| 666 |
+
# - Semantics: True = ATTEND to this token
|
| 667 |
+
# - forward_sdpa() passes this directly to F.scaled_dot_product_attention as
|
| 668 |
+
# a boolean key-padding mask (broadcast to (B, 1, 1, L)). SDPA's bool-mask
|
| 669 |
+
# contract matches: True=attend, False=ignore — so no additive conversion
|
| 670 |
+
# is needed, and PyTorch can keep using the Flash/efficient backends.
|
| 671 |
+
#
|
| 672 |
+
# STEP 3b: For flex_attention mode -> block_mask
|
| 673 |
+
# - _create_block_mask inverts internally: attend_mask = ~key_padding_mask
|
| 674 |
+
# - BlockMask uses: True = ATTEND (allow attention between these positions)
|
| 675 |
+
# - flex_attention natively handles this without further inversion
|
| 676 |
+
# =======================================================================
|
| 677 |
+
|
| 678 |
+
# Build key-padding mask (CLS + patch tokens) from pixel-level mask.
|
| 679 |
+
# Output: attn_kpm with semantics True=IGNORE (padding tokens to exclude)
|
| 680 |
+
attn_kpm: Optional[torch.Tensor] = None
|
| 681 |
+
if padding_mask is not None:
|
| 682 |
+
# Convert pixel mask to standard format: (B,1,H,W) bool, True=PAD
|
| 683 |
+
pm = ensure_pixel_padding_mask(padding_mask, mask_semantics='pad')
|
| 684 |
+
thr = getattr(self._config, "token_ignore_threshold", 0.9)
|
| 685 |
+
# Pool pixel mask to token-level: token is IGNORE if >= threshold fraction is padding
|
| 686 |
+
token_ignore = pixel_to_token_ignore(pm, patch=self._config.patch_size, threshold=thr) # (B,Lp)
|
| 687 |
+
# Prepend CLS token (always attend, never ignored)
|
| 688 |
+
cls_keep = torch.zeros(B, 1, dtype=torch.bool, device=token_ignore.device) # False = don't ignore
|
| 689 |
+
attn_kpm = torch.cat([cls_keep, token_ignore], dim=1) # (B, 1+Lp), True=IGNORE
|
| 690 |
+
|
| 691 |
+
if attn_kpm is not None:
|
| 692 |
+
# Only validate in debug mode to avoid expensive GPU-CPU synchronization
|
| 693 |
+
# (skip inside tracing to avoid control flow issues)
|
| 694 |
+
if self._config.check_numerical_stability and not torch.jit.is_tracing() and not self._onnx_mode:
|
| 695 |
+
if attn_kpm.all(dim=1).any().item():
|
| 696 |
+
raise RuntimeError("attn_kpm masks all keys for at least one sample.")
|
| 697 |
+
|
| 698 |
+
# Create attention mask based on mode (ONNX vs flex_attention)
|
| 699 |
+
# Each path converts attn_kpm (True=IGNORE) to the format expected by the attention mechanism
|
| 700 |
+
block_mask: Optional[BlockMask] = None
|
| 701 |
+
sdpa_attn_mask: Optional[torch.Tensor] = None
|
| 702 |
+
|
| 703 |
+
if self._onnx_mode:
|
| 704 |
+
# ONNX mode: use SDPA with simple boolean mask
|
| 705 |
+
# For ONNX export, images are padded to square by InferenceWrapper,
|
| 706 |
+
# so typically no masking is needed. But support it for completeness.
|
| 707 |
+
if attn_kpm is not None:
|
| 708 |
+
# Invert semantics: attn_kpm True=IGNORE -> sdpa_attn_mask True=ATTEND
|
| 709 |
+
# forward_sdpa() passes this boolean mask straight to SDPA (True=ATTEND,
|
| 710 |
+
# False=IGNORE); no additive -inf conversion, so the Flash/efficient backend stays in use.
|
| 711 |
+
# Always apply (no .any() guard) so the mask branch is captured during export.
|
| 712 |
+
# An all-False attn_kpm produces an all-True sdpa_attn_mask (all-attend),
|
| 713 |
+
# which is mathematically identical to no mask.
|
| 714 |
+
sdpa_attn_mask = ~attn_kpm
|
| 715 |
+
else:
|
| 716 |
+
# Normal mode: use flex_attention with BlockMask
|
| 717 |
+
# _create_block_mask handles the inversion internally:
|
| 718 |
+
# attend_mask = ~key_padding_mask (True=ATTEND)
|
| 719 |
+
# BlockMask allows attention where attend_mask[q] & attend_mask[kv] is True
|
| 720 |
+
# No .any() guard: it forces a GPU->CPU sync every forward and with
|
| 721 |
+
# letterboxed data the mask is almost always non-empty anyway. An
|
| 722 |
+
# all-False attn_kpm yields an all-attend BlockMask (same result).
|
| 723 |
+
if attn_kpm is not None:
|
| 724 |
+
block_mask = self._create_block_mask(attn_kpm, x.size(1))
|
| 725 |
+
|
| 726 |
+
# Transformer blocks with optional selective gradient checkpointing
|
| 727 |
+
# checkpoint_every_n_layers controls granularity: 1=all, 2=every 2nd, 4=every 4th
|
| 728 |
+
checkpoint_interval = getattr(self._config, 'checkpoint_every_n_layers', 1)
|
| 729 |
+
for idx, block in enumerate(self.blocks):
|
| 730 |
+
should_checkpoint = (
|
| 731 |
+
self._config.gradient_checkpointing
|
| 732 |
+
and self.training
|
| 733 |
+
and (idx % checkpoint_interval == 0)
|
| 734 |
+
)
|
| 735 |
+
|
| 736 |
+
if self._onnx_mode:
|
| 737 |
+
# ONNX mode: use SDPA path (no checkpointing during export)
|
| 738 |
+
x = block.forward_sdpa(x, attn_mask=sdpa_attn_mask)
|
| 739 |
+
elif should_checkpoint:
|
| 740 |
+
# use_reentrant=False is recommended for PyTorch 2.x and works better with
|
| 741 |
+
# complex inputs like attention masks. It's also more memory efficient.
|
| 742 |
+
# See: https://pytorch.org/docs/stable/checkpoint.html
|
| 743 |
+
|
| 744 |
+
def create_block_forward(b, mask):
|
| 745 |
+
"""Create a closure that captures block and mask for checkpointing."""
|
| 746 |
+
def block_forward(hidden_states):
|
| 747 |
+
return b(hidden_states, block_mask=mask)
|
| 748 |
+
return block_forward
|
| 749 |
+
|
| 750 |
+
x = torch.utils.checkpoint.checkpoint(
|
| 751 |
+
create_block_forward(block, block_mask),
|
| 752 |
+
x,
|
| 753 |
+
use_reentrant=False
|
| 754 |
+
)
|
| 755 |
+
else:
|
| 756 |
+
x = block(x, block_mask=block_mask)
|
| 757 |
+
# Final norm
|
| 758 |
+
x = self.norm(x)
|
| 759 |
+
# Use CLS token for classification
|
| 760 |
+
cls_output = x[:, 0]
|
| 761 |
+
# Predictions (ratings are included as tags in the vocabulary)
|
| 762 |
+
tag_logits = self.tag_head(cls_output)
|
| 763 |
+
|
| 764 |
+
# Monitor for numerical issues (optional, controlled by config)
|
| 765 |
+
if self._config.check_numerical_stability:
|
| 766 |
+
self._check_numerical_stability(tag_logits)
|
| 767 |
+
|
| 768 |
+
# Clamp logits for inference/export outputs only. During training the
|
| 769 |
+
# clamp is skipped: torch.clamp has zero gradient outside the bounds,
|
| 770 |
+
# which would kill the learning signal exactly for confidently-wrong
|
| 771 |
+
# predictions. The loss (loss_functions.py) computes in fp32 with its
|
| 772 |
+
# own log/exp clamps, so unclamped training logits are numerically safe.
|
| 773 |
+
if self._config.logit_clamp_value is not None and not self.training:
|
| 774 |
+
clamp_val = self._config.logit_clamp_value
|
| 775 |
+
tag_logits = torch.clamp(tag_logits, min=-clamp_val, max=clamp_val)
|
| 776 |
+
|
| 777 |
+
return {
|
| 778 |
+
'tag_logits': tag_logits,
|
| 779 |
+
'logits': tag_logits
|
| 780 |
+
}
|
| 781 |
+
|
| 782 |
+
def cleanup(self):
|
| 783 |
+
"""Explicitly release GPU memory and clear cached tensors."""
|
| 784 |
+
import gc
|
| 785 |
+
|
| 786 |
+
# Move all parameters to CPU to release GPU memory
|
| 787 |
+
self.cpu()
|
| 788 |
+
|
| 789 |
+
# Clear any cached gradients
|
| 790 |
+
for param in self.parameters():
|
| 791 |
+
if param.grad is not None:
|
| 792 |
+
param.grad = None
|
| 793 |
+
|
| 794 |
+
# Force garbage collection
|
| 795 |
+
gc.collect()
|
| 796 |
+
|
| 797 |
+
# Clear CUDA cache if available
|
| 798 |
+
if torch.cuda.is_available():
|
| 799 |
+
# Synchronize before clearing cache to ensure all GPU operations complete
|
| 800 |
+
torch.cuda.synchronize()
|
| 801 |
+
torch.cuda.empty_cache()
|
| 802 |
+
|
| 803 |
+
def __del__(self):
|
| 804 |
+
"""Cleanup on deletion."""
|
| 805 |
+
try:
|
| 806 |
+
self.cleanup()
|
| 807 |
+
except Exception:
|
| 808 |
+
pass
|
| 809 |
+
|
| 810 |
+
def __enter__(self):
|
| 811 |
+
"""Context manager support."""
|
| 812 |
+
return self
|
| 813 |
+
|
| 814 |
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
| 815 |
+
"""Ensure cleanup on exit."""
|
| 816 |
+
self.cleanup()
|
| 817 |
+
return False
|
| 818 |
+
|
| 819 |
+
|
| 820 |
+
def create_model(config: Optional[VisionTransformerConfig] = None, **kwargs) -> BaseTagger:
|
| 821 |
+
"""Create the ViT tagger model (SimplifiedTagger) from configuration.
|
| 822 |
+
|
| 823 |
+
Args:
|
| 824 |
+
config: VisionTransformerConfig instance. If provided, config fields in kwargs are ignored.
|
| 825 |
+
**kwargs: Alternative to config - specify config fields as keyword arguments.
|
| 826 |
+
See VisionTransformerConfig for details on each parameter.
|
| 827 |
+
|
| 828 |
+
Returns:
|
| 829 |
+
SimplifiedTagger instance with the specified configuration.
|
| 830 |
+
|
| 831 |
+
Examples:
|
| 832 |
+
# Using config object
|
| 833 |
+
config = VisionTransformerConfig(image_size=512, num_tags=10000)
|
| 834 |
+
model = create_model(config=config)
|
| 835 |
+
|
| 836 |
+
# Using kwargs
|
| 837 |
+
model = create_model(image_size=512, num_tags=10000)
|
| 838 |
+
"""
|
| 839 |
+
# Pop the legacy architecture selector so it doesn't reach the config-field filter
|
| 840 |
+
# below. Only the ViT path (SimplifiedTagger) is supported now; the value is ignored.
|
| 841 |
+
kwargs.pop('architecture_type', None)
|
| 842 |
+
|
| 843 |
+
if config is None:
|
| 844 |
+
# Get the names of the fields in the VisionTransformerConfig dataclass
|
| 845 |
+
config_fields = {f.name for f in fields(VisionTransformerConfig)}
|
| 846 |
+
|
| 847 |
+
# Filter kwargs to only include keys that are in the config_fields
|
| 848 |
+
filtered_kwargs = {k: v for k, v in kwargs.items() if k in config_fields}
|
| 849 |
+
|
| 850 |
+
# Warn if kwargs contains invalid keys (excluding already-extracted keys)
|
| 851 |
+
invalid_keys = set(kwargs.keys()) - config_fields
|
| 852 |
+
if invalid_keys:
|
| 853 |
+
warnings.warn(
|
| 854 |
+
f"Ignoring unknown configuration parameters: {sorted(invalid_keys)}. "
|
| 855 |
+
f"Valid parameters: {sorted(config_fields)}"
|
| 856 |
+
)
|
| 857 |
+
|
| 858 |
+
config = VisionTransformerConfig(**filtered_kwargs)
|
| 859 |
+
|
| 860 |
+
return SimplifiedTagger(config)
|
preprocessing.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"normalize_mean": [
|
| 3 |
+
0.5,
|
| 4 |
+
0.5,
|
| 5 |
+
0.5
|
| 6 |
+
],
|
| 7 |
+
"normalize_std": [
|
| 8 |
+
0.5,
|
| 9 |
+
0.5,
|
| 10 |
+
0.5
|
| 11 |
+
],
|
| 12 |
+
"image_size": 448,
|
| 13 |
+
"patch_size": 16,
|
| 14 |
+
"color_order": "RGB",
|
| 15 |
+
"pad_color": [
|
| 16 |
+
114,
|
| 17 |
+
114,
|
| 18 |
+
114
|
| 19 |
+
],
|
| 20 |
+
"resize_filter": "LANCZOS",
|
| 21 |
+
"resize_mode": "letterbox_downscale_only",
|
| 22 |
+
"upscale": false,
|
| 23 |
+
"exif_transpose": true,
|
| 24 |
+
"alpha_composite": true,
|
| 25 |
+
"input_layout": "NCHW",
|
| 26 |
+
"padding_mask_true_means": "padding",
|
| 27 |
+
"output_activation": "sigmoid",
|
| 28 |
+
"skip_indices": [
|
| 29 |
+
0,
|
| 30 |
+
1
|
| 31 |
+
]
|
| 32 |
+
}
|
release_manifest.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"release": "OppaiOracle experimental-X1.1",
|
| 3 |
+
"repo_id": "Grio43/OppaiOracle-experimental-X1.1",
|
| 4 |
+
"created_at": "2026-10-12T09:59:58.688810+00:00",
|
| 5 |
+
"image_size": 448,
|
| 6 |
+
"epoch": 9,
|
| 7 |
+
"step": 55719,
|
| 8 |
+
"source_checkpoint_sha256": "d400c2e460e7287357f86083f84c187790a45b1d47985e908c52825373f888c3",
|
| 9 |
+
"validated_onnx_source_sha256": "c5fcab0fb53e07aee437f3ef29e19a1e60b3dc18380048fa6d99baf35fa6c8f3",
|
| 10 |
+
"onnx_graph_sha256": "bbb2fd38ed9fdfe5977b0fd79ca2c7820a1eab5839b416aab5579109e6c0fa74",
|
| 11 |
+
"onnx_change": "descriptive metadata only; graph identical to fully evaluated artifact",
|
| 12 |
+
"safetensors": "all 332 FP32 tensors exact round-trip; no optimizer or pickle",
|
| 13 |
+
"model_sha256": "49ba0f66f83ba88a101b6edac54ec39f0a12eab66743e13ae42efc3570745bf7",
|
| 14 |
+
"safetensors_sha256": "ec07d0034f48ba3ceff8624993baf3ea30fa3a2e3cc8908682eb3e4f84eda766",
|
| 15 |
+
"vocab_sha256": "e3e6ccc137d9152d8a17fbd19e0c1ae595b2317a6c55893fd25779620b4b8087",
|
| 16 |
+
"storage": "Hugging Face Hub / Xet",
|
| 17 |
+
"license": "educational-use-only",
|
| 18 |
+
"download_count_query_file": "config.json",
|
| 19 |
+
"validation": "validation.json"
|
| 20 |
+
}
|
requirements-gpu.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
numpy>=1.26
|
| 2 |
+
Pillow>=10.0
|
| 3 |
+
onnxruntime-gpu>=1.20
|
| 4 |
+
huggingface_hub>=1.0
|
requirements-torch.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
numpy>=1.26
|
| 2 |
+
Pillow>=10.0
|
| 3 |
+
torch>=2.5
|
| 4 |
+
safetensors>=0.4.5
|
| 5 |
+
huggingface_hub>=1.0
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
numpy>=1.26
|
| 2 |
+
Pillow>=10.0
|
| 3 |
+
onnxruntime>=1.20
|
| 4 |
+
huggingface_hub>=1.0
|
selected_tags.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
thresholds.json
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"default": 0.7085427045822144,
|
| 3 |
+
"source": "200-point micro-F1 grid on 13,569 shared development tags; not final deployment calibration",
|
| 4 |
+
"score_calibration": false,
|
| 5 |
+
"training_diagnostic_threshold": 0.7927
|
| 6 |
+
}
|
training_buckets.csv
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
bucket,epoch7,epoch8,epoch9,epoch7_to_9_points
|
| 2 |
+
300-499,37.72737681865692,37.53387928009033,37.342289090156555,-0.3850877285003662
|
| 3 |
+
500-999,61.54675483703613,61.733537912368774,61.75047755241394,0.20372271537780762
|
| 4 |
+
1000-4999,57.105010747909546,57.1607232093811,57.215815782547,0.11080503463745117
|
| 5 |
+
5000-9999,52.05754041671753,52.18653082847595,52.18939185142517,0.1318514347076416
|
| 6 |
+
10000+,57.25409388542175,57.3200523853302,57.348477840423584,0.09438395500183105
|
training_epochs.csv
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
epoch,validation_time,exact_map,binned_map,macro_f1,micro_f1,val_loss,train_loss,samples,skipped
|
| 2 |
+
1,2026-09-13T18:19:02.022533-04:00,55.92384934425354,55.73147535324097,43.22007596492767,64.86003398895264,0.001663603587076068,0.0016142689855769277,30000,0
|
| 3 |
+
2,2026-09-14T04:16:38.378663-04:00,56.59424662590027,56.39519691467285,44.07239556312561,65.32300114631653,0.0016429137904196978,0.0015640060883015394,30000,0
|
| 4 |
+
3,2026-09-14T13:52:19.915251-04:00,56.98235034942627,56.77585005760193,44.495999813079834,65.55598378181458,0.001634233514778316,0.0015406454913318157,30000,0
|
| 5 |
+
4,2026-09-14T23:42:07.983341-04:00,57.2227418422699,57.01723098754883,44.791001081466675,65.70025682449341,0.0016256555682048202,0.0015247202245518565,30000,0
|
| 6 |
+
5,2026-09-15T09:26:10.020244-04:00,57.34878182411194,57.14470148086548,44.962278008461,65.80770015716553,0.0016207325970754027,0.0015117722796276212,30000,0
|
| 7 |
+
6,2026-09-15T19:23:48.602481-04:00,57.48898983001709,57.290494441986084,45.18154859542847,65.89643359184265,0.0016172741306945682,0.0015005783643573523,30000,0
|
| 8 |
+
7,2026-09-16T05:10:51.323995-04:00,57.589566707611084,57.38818645477295,45.37670016288757,66.06296300888062,0.0016201940597966313,0.00149048981256783,30000,0
|
| 9 |
+
8,2026-09-16T14:55:16.420660-04:00,57.68805742263794,57.48524069786072,45.54440975189209,66.12292528152466,0.0016188551671802998,0.0014811164001002908,30000,0
|
| 10 |
+
9,2026-09-17T01:43:38.368238-04:00,57.71656036376953,57.506269216537476,45.742398500442505,66.21212959289551,0.0016158618964254856,0.0014653843827545643,30000,0
|
training_metadata.json
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"source_checkpoint_sha256": "d400c2e460e7287357f86083f84c187790a45b1d47985e908c52825373f888c3",
|
| 3 |
+
"parent_release": "Grio43/OppaiOracle-experimental-X1",
|
| 4 |
+
"parent_epoch": 21,
|
| 5 |
+
"parent_step": 127974,
|
| 6 |
+
"phase": 2,
|
| 7 |
+
"epoch": 9,
|
| 8 |
+
"step": 55719,
|
| 9 |
+
"completed_at": "2026-09-17T01:43:41.793971",
|
| 10 |
+
"model": {
|
| 11 |
+
"architecture_type": "vit",
|
| 12 |
+
"hidden_size": 896,
|
| 13 |
+
"num_hidden_layers": 18,
|
| 14 |
+
"num_attention_heads": 14,
|
| 15 |
+
"intermediate_size": 2320,
|
| 16 |
+
"image_size": 448,
|
| 17 |
+
"patch_size": 16,
|
| 18 |
+
"num_channels": 3,
|
| 19 |
+
"hidden_dropout_prob": 0.0,
|
| 20 |
+
"pos_dropout": 0.0,
|
| 21 |
+
"attention_dropout": 0.0,
|
| 22 |
+
"qk_norm": true,
|
| 23 |
+
"layer_scale_init": 0.1,
|
| 24 |
+
"drop_path_rate": 0.15,
|
| 25 |
+
"initializer_range": 0.02,
|
| 26 |
+
"layer_norm_eps": 1e-06,
|
| 27 |
+
"use_fp32_layernorm": false,
|
| 28 |
+
"use_flex_attention": true,
|
| 29 |
+
"attention_bias": true,
|
| 30 |
+
"flex_block_size": 128,
|
| 31 |
+
"token_ignore_threshold": 0.9,
|
| 32 |
+
"num_labels": 14996,
|
| 33 |
+
"num_groups": 20,
|
| 34 |
+
"tags_per_group": 10000,
|
| 35 |
+
"gradient_checkpointing": false,
|
| 36 |
+
"checkpoint_every_n_layers": 1
|
| 37 |
+
},
|
| 38 |
+
"training": {
|
| 39 |
+
"memory_format": "contiguous",
|
| 40 |
+
"num_epochs": 9,
|
| 41 |
+
"learning_rate": 1.5e-05,
|
| 42 |
+
"lr_scaling_mode": "sqrt",
|
| 43 |
+
"lr_base_batch_size": 256,
|
| 44 |
+
"weight_decay": 0.05,
|
| 45 |
+
"gradient_accumulation_steps": 21,
|
| 46 |
+
"world_size": 1,
|
| 47 |
+
"optimizer": "adamw8bit",
|
| 48 |
+
"adam_beta1": 0.9,
|
| 49 |
+
"adam_beta2": 0.999,
|
| 50 |
+
"adan_beta3": 0.99,
|
| 51 |
+
"adam_epsilon": 1e-07,
|
| 52 |
+
"scheduler": "wsd",
|
| 53 |
+
"warmup_epochs": 1,
|
| 54 |
+
"warmup_steps": null,
|
| 55 |
+
"wsd_cooldown_fraction": 0.15,
|
| 56 |
+
"wsd_plateau_enabled": false,
|
| 57 |
+
"fp32_head_optimizer": true,
|
| 58 |
+
"num_cycles": 1,
|
| 59 |
+
"cycle_decay": 0.9,
|
| 60 |
+
"lr_end": 0.0,
|
| 61 |
+
"use_amp": true,
|
| 62 |
+
"amp_dtype": "bfloat16",
|
| 63 |
+
"enable_anomaly_detection": false,
|
| 64 |
+
"max_grad_norm": 1.0,
|
| 65 |
+
"gradient_clipping": {
|
| 66 |
+
"enabled": true,
|
| 67 |
+
"max_norm": 1.0
|
| 68 |
+
},
|
| 69 |
+
"save_steps": 1500,
|
| 70 |
+
"save_total_limit": 3,
|
| 71 |
+
"save_best_only": false,
|
| 72 |
+
"resume_from": "latest",
|
| 73 |
+
"eval_steps": 0,
|
| 74 |
+
"logging_steps": 50,
|
| 75 |
+
"resume_reseed": true,
|
| 76 |
+
"allow_unverified_vocab_resume": false,
|
| 77 |
+
"on_lr_schedule_change": "error",
|
| 78 |
+
"tag_loss": {
|
| 79 |
+
"alpha": 1.0,
|
| 80 |
+
"gamma_neg": 3.0,
|
| 81 |
+
"gamma_neg_override": null,
|
| 82 |
+
"gamma_pos": 0.0,
|
| 83 |
+
"detach_focal_weight": true,
|
| 84 |
+
"label_smoothing": 0.0,
|
| 85 |
+
"clip": 0.05,
|
| 86 |
+
"class_weights": null,
|
| 87 |
+
"class_weight_strategy": null,
|
| 88 |
+
"class_weight_clip_min": 0.05,
|
| 89 |
+
"class_weight_clip_max": 5.0,
|
| 90 |
+
"class_weight_beta": 0.9999
|
| 91 |
+
},
|
| 92 |
+
"phase": 2,
|
| 93 |
+
"asl_schedule": {
|
| 94 |
+
"enabled": false,
|
| 95 |
+
"min_dwell_epochs": 3,
|
| 96 |
+
"phase1": {
|
| 97 |
+
"gamma_neg_min": 3.0,
|
| 98 |
+
"gamma_neg_max": 3.0,
|
| 99 |
+
"hold_epochs": 8
|
| 100 |
+
},
|
| 101 |
+
"phase2": {
|
| 102 |
+
"gamma_neg_min": 3.0,
|
| 103 |
+
"gamma_neg_max": 3.0,
|
| 104 |
+
"hold_epochs": 2
|
| 105 |
+
},
|
| 106 |
+
"phase3": {
|
| 107 |
+
"gamma_neg_min": 3.0,
|
| 108 |
+
"gamma_neg_max": 3.0,
|
| 109 |
+
"hold_epochs": 0
|
| 110 |
+
}
|
| 111 |
+
},
|
| 112 |
+
"asl_telemetry": {
|
| 113 |
+
"enabled": true,
|
| 114 |
+
"interval_updates": 100,
|
| 115 |
+
"log_every_updates": 500,
|
| 116 |
+
"ema_beta": 0.98,
|
| 117 |
+
"topk_hard": 10,
|
| 118 |
+
"num_deciles": 10,
|
| 119 |
+
"hist_min": 0.05,
|
| 120 |
+
"hist_max": 0.95,
|
| 121 |
+
"hist_bins": 18,
|
| 122 |
+
"watch_band_low": 0.2,
|
| 123 |
+
"watch_band_high": 0.5,
|
| 124 |
+
"epr_alarm_rel_drop": 0.05,
|
| 125 |
+
"epr_alarm_window_epochs": 2,
|
| 126 |
+
"exclude_rating_tags": true,
|
| 127 |
+
"sibling_groups_path": "./configs/confusable_groups.json",
|
| 128 |
+
"shadow_controller_enabled": true,
|
| 129 |
+
"shadow_lambda": 0.05,
|
| 130 |
+
"shadow_delta_p_target": 0.2
|
| 131 |
+
},
|
| 132 |
+
"device": "cuda",
|
| 133 |
+
"use_compile": true,
|
| 134 |
+
"compile_mode": "max-autotune-no-cudagraphs",
|
| 135 |
+
"compile_fullgraph": false,
|
| 136 |
+
"compile_dynamic": true,
|
| 137 |
+
"use_tensorboard": true,
|
| 138 |
+
"seed": 42,
|
| 139 |
+
"deterministic": false,
|
| 140 |
+
"benchmark": true,
|
| 141 |
+
"selection_metric": "val_mAP",
|
| 142 |
+
"early_stopping_patience": 4,
|
| 143 |
+
"early_stopping_threshold": 5e-07,
|
| 144 |
+
"early_stopping_burn_in_epochs": 2,
|
| 145 |
+
"early_stopping_burn_in_strategy": "median",
|
| 146 |
+
"early_stopping_mode": "advise",
|
| 147 |
+
"early_stopping_min_delta": 0.003,
|
| 148 |
+
"early_stopping_plateau_slope_threshold": 0.0001,
|
| 149 |
+
"early_stopping_plateau_noise_multiplier": 2.0,
|
| 150 |
+
"early_stopping_window": 5,
|
| 151 |
+
"early_stopping_confirm": 2,
|
| 152 |
+
"early_stopping_min_epochs": 10,
|
| 153 |
+
"early_stopping_regression_delta": 0.006,
|
| 154 |
+
"early_stopping_regression_confirm": 2,
|
| 155 |
+
"early_stopping_overfit_confirm": 3,
|
| 156 |
+
"early_stopping_overfit_window": 8,
|
| 157 |
+
"early_stopping_overfit_min_relative_change": 0.01,
|
| 158 |
+
"early_stopping_overfit_noise_multiplier": 2.0,
|
| 159 |
+
"early_stopping_overfit_ranking_tolerance": 0.0001,
|
| 160 |
+
"wsd_deterioration_actions": {
|
| 161 |
+
"peak_decline": "preserve",
|
| 162 |
+
"overfit_loss": "warn"
|
| 163 |
+
},
|
| 164 |
+
"early_stopping_phases": {
|
| 165 |
+
"phase1": {
|
| 166 |
+
"mode": null,
|
| 167 |
+
"window": 5,
|
| 168 |
+
"confirm": 2,
|
| 169 |
+
"min_epochs": 10,
|
| 170 |
+
"plateau_slope_threshold": null,
|
| 171 |
+
"plateau_noise_multiplier": null
|
| 172 |
+
},
|
| 173 |
+
"phase2": {
|
| 174 |
+
"mode": "advise",
|
| 175 |
+
"window": 5,
|
| 176 |
+
"confirm": 2,
|
| 177 |
+
"min_epochs": 10,
|
| 178 |
+
"plateau_slope_threshold": null,
|
| 179 |
+
"plateau_noise_multiplier": null
|
| 180 |
+
},
|
| 181 |
+
"phase3": {
|
| 182 |
+
"mode": "off",
|
| 183 |
+
"window": null,
|
| 184 |
+
"confirm": null,
|
| 185 |
+
"min_epochs": null,
|
| 186 |
+
"plateau_slope_threshold": null,
|
| 187 |
+
"plateau_noise_multiplier": null
|
| 188 |
+
}
|
| 189 |
+
},
|
| 190 |
+
"use_distillation": false,
|
| 191 |
+
"distillation_alpha": 0.7,
|
| 192 |
+
"distillation_temperature": 3.0
|
| 193 |
+
},
|
| 194 |
+
"data_recipe": {
|
| 195 |
+
"vocab_min_frequency": 550,
|
| 196 |
+
"vocab_min_frequency_by_category": {
|
| 197 |
+
"artist": 550,
|
| 198 |
+
"char": 550,
|
| 199 |
+
"copyright": 550,
|
| 200 |
+
"gen": 450,
|
| 201 |
+
"meta": 550
|
| 202 |
+
},
|
| 203 |
+
"random_flip_prob": 0.5,
|
| 204 |
+
"horizontal_flip_exclude_tags": [
|
| 205 |
+
"gen:left-handed",
|
| 206 |
+
"gen:left-to-right_manga",
|
| 207 |
+
"gen:right-over-left_kimono",
|
| 208 |
+
"gen:right-to-left_comic"
|
| 209 |
+
],
|
| 210 |
+
"color_jitter_enabled": true,
|
| 211 |
+
"color_jitter_brightness": 0.22,
|
| 212 |
+
"color_jitter_brightness_p": 0.5,
|
| 213 |
+
"color_jitter_contrast": 0.15,
|
| 214 |
+
"color_jitter_contrast_p": 0.5,
|
| 215 |
+
"color_jitter_saturation": 0.06,
|
| 216 |
+
"color_jitter_saturation_p": 0.5,
|
| 217 |
+
"random_erasing_enabled": false,
|
| 218 |
+
"random_erasing_p": 0.35,
|
| 219 |
+
"random_erasing_scale_min": 0.007,
|
| 220 |
+
"random_erasing_scale_max": 0.03,
|
| 221 |
+
"random_erasing_ratio_min": 0.3,
|
| 222 |
+
"random_erasing_ratio_max": 2.0,
|
| 223 |
+
"random_rotation_enabled": true,
|
| 224 |
+
"random_rotation_p": 0.3,
|
| 225 |
+
"random_rotation_min_degrees": 2.0,
|
| 226 |
+
"random_rotation_max_degrees": 5.0,
|
| 227 |
+
"gaussian_blur_enabled": true,
|
| 228 |
+
"gaussian_blur_p": 0.15,
|
| 229 |
+
"gaussian_blur_kernel_size": 3,
|
| 230 |
+
"gaussian_blur_sigma_min": 0.1,
|
| 231 |
+
"gaussian_blur_sigma_max": 1.0
|
| 232 |
+
},
|
| 233 |
+
"train_images": 6239823,
|
| 234 |
+
"eval_images": 30000,
|
| 235 |
+
"dataset_maintenance": {
|
| 236 |
+
"danbooru_metadata_sync": "2026-08",
|
| 237 |
+
"deprecated_tags_removed": true,
|
| 238 |
+
"renamed_tags_updated_to_current_names": true,
|
| 239 |
+
"provenance": "model author"
|
| 240 |
+
},
|
| 241 |
+
"metrics": {
|
| 242 |
+
"train_loss": 0.0014653843827545643,
|
| 243 |
+
"val_loss": 0.0016158618964254856,
|
| 244 |
+
"val_f1_macro": 0.45742398500442505,
|
| 245 |
+
"val_f1_micro": 0.6621212959289551,
|
| 246 |
+
"val_mAP": 0.5750626921653748,
|
| 247 |
+
"selection_metric": "val_mAP"
|
| 248 |
+
},
|
| 249 |
+
"scheduler": {
|
| 250 |
+
"warmup_steps": 6191,
|
| 251 |
+
"total_steps": 55719,
|
| 252 |
+
"cooldown_fraction": 0.15,
|
| 253 |
+
"budget_cooldown_start": 48451,
|
| 254 |
+
"min_lr": 0.0,
|
| 255 |
+
"base_max_lr": 2.9764702249476646e-05,
|
| 256 |
+
"cooldown_start": 48451,
|
| 257 |
+
"cooldown_steps": 7268,
|
| 258 |
+
"cooldown_reason": "budget",
|
| 259 |
+
"cooldown_best_metric": 0.5750626921653748,
|
| 260 |
+
"base_lrs": [
|
| 261 |
+
2.9764702249476646e-05,
|
| 262 |
+
2.9764702249476646e-05
|
| 263 |
+
],
|
| 264 |
+
"last_epoch": 55719,
|
| 265 |
+
"_step_count": 55720,
|
| 266 |
+
"_is_initial": false,
|
| 267 |
+
"_get_lr_called_within_step": false,
|
| 268 |
+
"_last_lr": [
|
| 269 |
+
0.0,
|
| 270 |
+
0.0
|
| 271 |
+
]
|
| 272 |
+
}
|
| 273 |
+
}
|
validation.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model": "X1 448 final epoch 9",
|
| 3 |
+
"checkpoint": {
|
| 4 |
+
"checkpoint": "checkpoint_epoch_9_step_55719.pt",
|
| 5 |
+
"checkpoint_sha256": "d400c2e460e7287357f86083f84c187790a45b1d47985e908c52825373f888c3",
|
| 6 |
+
"epoch": 9,
|
| 7 |
+
"step": 55719,
|
| 8 |
+
"scheduler": {
|
| 9 |
+
"warmup_steps": 6191,
|
| 10 |
+
"total_steps": 55719,
|
| 11 |
+
"cooldown_fraction": 0.15,
|
| 12 |
+
"budget_cooldown_start": 48451,
|
| 13 |
+
"min_lr": 0.0,
|
| 14 |
+
"base_max_lr": 2.9764702249476646e-05,
|
| 15 |
+
"cooldown_start": 48451,
|
| 16 |
+
"cooldown_steps": 7268,
|
| 17 |
+
"cooldown_reason": "budget",
|
| 18 |
+
"cooldown_best_metric": 0.5750626921653748,
|
| 19 |
+
"base_lrs": [
|
| 20 |
+
2.9764702249476646e-05,
|
| 21 |
+
2.9764702249476646e-05
|
| 22 |
+
],
|
| 23 |
+
"last_epoch": 55719,
|
| 24 |
+
"_step_count": 55720,
|
| 25 |
+
"_is_initial": false,
|
| 26 |
+
"_get_lr_called_within_step": false,
|
| 27 |
+
"_last_lr": [
|
| 28 |
+
0.0,
|
| 29 |
+
0.0
|
| 30 |
+
]
|
| 31 |
+
},
|
| 32 |
+
"preprocessing": {
|
| 33 |
+
"normalize_mean": [
|
| 34 |
+
0.5,
|
| 35 |
+
0.5,
|
| 36 |
+
0.5
|
| 37 |
+
],
|
| 38 |
+
"normalize_std": [
|
| 39 |
+
0.5,
|
| 40 |
+
0.5,
|
| 41 |
+
0.5
|
| 42 |
+
],
|
| 43 |
+
"image_size": 448,
|
| 44 |
+
"patch_size": 16,
|
| 45 |
+
"color_order": "RGB"
|
| 46 |
+
}
|
| 47 |
+
},
|
| 48 |
+
"model_sha256": "c5fcab0fb53e07aee437f3ef29e19a1e60b3dc18380048fa6d99baf35fa6c8f3",
|
| 49 |
+
"native_image_size": 448,
|
| 50 |
+
"samples": 30000,
|
| 51 |
+
"sample_sha256": "20a60e7a747cdf9a698a464c9d324612aad2ca6b43a541db0813554eb9af8694",
|
| 52 |
+
"shared_tags": 13569,
|
| 53 |
+
"supported_shared_tags": 13394,
|
| 54 |
+
"binned_mAP": 0.5851927682386426,
|
| 55 |
+
"exact_mAP": 0.5875134140920024,
|
| 56 |
+
"best_grid_micro_f1": 0.6864101262842809,
|
| 57 |
+
"best_grid_threshold": 0.7085427045822144,
|
| 58 |
+
"precision_at_best_grid": 0.7065020665350971,
|
| 59 |
+
"recall_at_best_grid": 0.6674293596992867,
|
| 60 |
+
"all_supported_tags": 14767,
|
| 61 |
+
"all_tag_exact_mAP": 0.5774638913821051,
|
| 62 |
+
"all_tag_binned_mAP": 0.5750950549967043,
|
| 63 |
+
"inference_seconds": 347.91008079855237,
|
| 64 |
+
"total_seconds": 487.7315649986267,
|
| 65 |
+
"preprocessing": "FP32 RGB, native resolution, downscale-only centered LANCZOS letterbox, mean/std 0.5, pad 114, True=padding",
|
| 66 |
+
"verified_sidecars": 30000
|
| 67 |
+
}
|
vocabulary.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|