PixAI Tagger v1.0 β ONNX (FP32 / FP16 / INT8)
Unofficial ONNX export of pixai-labs/pixai-tagger-v1.0 in FP32, FP16 and INT8. Runs with ONNX Runtime only.
Files
| File | Precision | Size |
|---|---|---|
model.onnx |
FP32 | 1.89 GB |
model_fp16.onnx |
FP16 (FP32 input/output) | 947 MB |
model_int8.onnx |
Dynamic INT8 (MatMul/Gemm, per-channel) | 523 MB |
selected_tags.csv |
tag_id,name,category,count |
|
preprocess.json |
Preprocessing steps |
- Input
input: float32[batch, 3, 1008, 1008], RGB, NCHW. - Output
prediction: float32[batch, 30877], probabilities (sigmoid is part of the graph). - Opset 17.
Test results
- Hardware: AMD Ryzen 5 9600X (6 cores / 12 threads, AVX-512 VNNI), 32 GB RAM, AMD Radeon RX 580X 4 GB.
- Software: Windows 11,
onnxruntime-directml1.24, batch 1. - Images: 6 anime illustrations. 5 of them are Danbooru posts uploaded in September 2026, after the model's training cutoff, and are also scored against their Danbooru tags.
- Thresholds: the per-category values listed under Categories and thresholds.
- FP16 is meant for GPUs. On CPU it is slower than FP32, because the CPU has no native FP16 math.
- FP32 fits on a 4 GB GPU only barely (3.2 GB VRAM), and its speed varies between runs.
- INT8 speed depends on CPU support for VNNI instructions. On older CPUs it can be slower than FP32.
- Peak RAM on CPU includes session initialization; FP32 briefly reaches 9.4 GB while loading.
Agreement with FP32 (CPU)
| Model | Device | Max prob. diff | Mean prob. diff | Tag-set similarity (Jaccard) | Tags lost / added (of 317) |
|---|---|---|---|---|---|
| FP32 | DirectML | 6 Γ 10β»βΆ | 2 Γ 10β»βΈ | 1.000 | 0 / 0 |
| FP16 | CPU | 0.002 | 7 Γ 10β»β· | 1.000 | 0 / 0 |
| FP16 | DirectML | 0.012 | 6 Γ 10β»βΆ | 0.997 | 1 / 0 |
| INT8 | CPU | 0.103 | 6 Γ 10β»β΅ | 0.951 | 4 / 13 |
Against Danbooru tags (5 posts)
| Model | General P / R / F1 | Character P / R | Copyright P / R | Rating |
|---|---|---|---|---|
| FP32 | 0.50 / 0.73 / 0.59 | 6/6 Β· 6/8 | 6/7 Β· 6/6 | 5/5 |
| FP16 (DirectML) | 0.50 / 0.72 / 0.59 | 6/6 Β· 6/8 | 6/7 Β· 6/6 | 5/5 |
| INT8 | 0.49 / 0.73 / 0.59 | 7/7 Β· 7/8 | 6/8 Β· 6/6 | 5/5 |
- P is precision (correct / predicted) and R is recall (found / expected).
- Danbooru tag lists are often incomplete, so the general precision shown is a lower bound.
- Ground-truth tags that are not in the model's vocabulary are excluded (3 general, 1 character, 1 copyright).
- With 5 images, a difference of one tag between variants is within noise.
Preprocessing
- Resize so the longest side is 1008 px (bicubic), keeping aspect ratio.
- Paste centered on a 1008 Γ 1008 black canvas.
- Scale to [0, 1], then normalize with mean 0.5 and std 0.5.
Categories and thresholds
category |
Name | Threshold |
|---|---|---|
| 0 | general | 0.17 |
| 1 | style | 0.15 |
| 3 | copyright | 0.24 |
| 4 | character | 0.27 |
| 5 | meta | 0.17 |
| 9 | rating | 0.41 |
Thresholds are the ones recommended in the original model card.
Usage
pip install onnxruntime pillow numpy huggingface_hub
# DirectML (Windows): pip install onnxruntime-directml pillow numpy huggingface_hub
import csv
import numpy as np
import onnxruntime as ort
from PIL import Image
from huggingface_hub import hf_hub_download
REPO = "Mexes/pixai-tagger-v1.0-onnx-fp32-fp16-int8"
CATEGORIES = {0: "general", 1: "style", 3: "copyright", 4: "character", 5: "meta", 9: "rating"}
THRESHOLDS = {"general": 0.17, "style": 0.15, "copyright": 0.24, "character": 0.27, "meta": 0.17, "rating": 0.41}
with open(hf_hub_download(REPO, "selected_tags.csv"), encoding="utf-8") as f:
tags = [(row["name"], CATEGORIES[int(row["category"])]) for row in csv.DictReader(f)]
def preprocess(path, size=1008):
img = Image.open(path).convert("RGB")
scale = size / max(img.size)
w, h = round(img.width * scale), round(img.height * scale)
img = img.resize((w, h), Image.Resampling.BICUBIC)
canvas = Image.new("RGB", (size, size))
canvas.paste(img, ((size - w) // 2, (size - h) // 2))
x = (np.asarray(canvas, dtype=np.float32) / 255.0 - 0.5) / 0.5
return x.transpose(2, 0, 1)[None]
session = ort.InferenceSession(
hf_hub_download(REPO, "model_fp16.onnx"),
providers=["CUDAExecutionProvider", "CPUExecutionProvider"],
)
probs = session.run(["prediction"], {"input": preprocess("image.jpg")})[0][0]
results = {}
for (name, category), p in zip(tags, probs):
if p >= THRESHOLDS[category]:
results.setdefault(category, {})[name] = float(p)
print(results)
DirectML
so = ort.SessionOptions()
so.enable_mem_pattern = False
so.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL
session = ort.InferenceSession(
"model_fp16.onnx", so,
providers=[("DmlExecutionProvider", {"device_id": 0}), "CPUExecutionProvider"],
)
Set device_id explicitly on systems with more than one GPU; without it the integrated GPU may be selected
(21.8 s vs 5.3 s per image in the test above).
Conversion
pip install torch safetensors numpy onnx onnxruntime onnxconverter-common
python scripts/export_pixai_v1_onnx.py out
python scripts/convert_pixai_v1_fp16.py --src out/model.onnx
python scripts/quantize_pixai_v1_int8.py --src out/model.onnx
The rotary position embeddings are rewritten with real-valued cos/sin, since the complex-number ops used by the original implementation cannot be exported to ONNX.
License
Apache-2.0, same as the original model.
Model tree for Mexes/pixai-tagger-v1.0-onnx-fp32-fp16-int8
Base model
pixai-labs/pixai-tagger-v1.0
