BooruNL-0.8B

A small, fast, uncensored model that converts a plain English description of a character or scene into a clean list of authorized danbooru tags. Fine-tuned from Qwen3.5-0.8B-Base on 124,000 human-labelled description → tag pairs, covering the full tag vocabulary including explicit content.

Model files

File Size Notes
BooruNL-0.8B-Q6_K.gguf 600 MB Recommended. ~15× faster than HF, within 1% quality.
BooruNL-0.8B-Q8_0.gguf 800 MB Highest fidelity GGUF; negligible quality gain over Q6_K.
BooruNL-0.8B-Q4_K_M.gguf 500 MB Smallest; slight quality loss on complex prompts.
BooruNL-0.8B-mmproj-f16.gguf 205 MB Vision encoder (F16), extracted from the base model. Not useful for tag generation — see note below.
model.safetensors (HF) 1.6 GB Full bfloat16 weights for fine-tuning or research.

Quick start

GGUF (recommended)

from llama_cpp import Llama

llm = Llama(model_path="BooruNL-0.8B-Q6_K.gguf", n_gpu_layers=-1, n_ctx=2048)

prompt = """### Input:
Tag this prompt: A gothic punk girl with two-tone black and blonde hair in twin buns, yellow eyes, \
spiked choker, frilly black sailor-collar shirt, two-tone cardigan, red plaid skirt, \
torn fishnet thigh-highs, lying on her back on rumpled bed sheets.
### Tags:
"""

result = llm(prompt, max_tokens=256, temperature=0.05, repeat_penalty=1.3, echo=False)
print(result["choices"][0]["text"])

Output:

1girl, solo_focus, goth_fashion, punk, black_hair, blonde_hair, multicolored_hair, twin_buns,
two-tone_hair, yellow_eyes, piercing, ear_piercing, earrings, spiked_choker, choker, cardigan,
frilled_shirt, plaid_clothes, plaid_skirt, red_skirt, fishnets, thighhighs, on_back, lying

HuggingFace transformers

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

model_id = "cooperdk/BooruNL-0.8B"  # or local path
tok   = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
    model_id, torch_dtype=torch.bfloat16, attn_implementation="sdpa"
).to("cuda").eval()

prompt = """### Input:
Tag this prompt: A solo slim red-haired girl with freckles and very small breasts, wearing a maid outfit.
### Tags:
"""

ids = tok(prompt, return_tensors="pt").to("cuda")
with torch.no_grad():
    out = model.generate(**ids, max_new_tokens=256, do_sample=False,
                         repetition_penalty=1.3)
tags = tok.decode(out[0][ids.input_ids.shape[1]:], skip_special_tokens=True).strip()
print(tags)

Output:

1girl, solo, maid, red_hair, breasts, freckles, small_breasts

Prompt format

The model accepts any natural instruction prefix. All of the following work:

Tag this prompt: <description>
Convert this to danbooru tags: <description>
danbooru tags for: <description>
<description>         ← bare description also works

The output is a clean comma-separated tag list with no preamble. Rating tags (rating:safe, rating:explicit, etc.) are not emitted — the model was trained without them in the target, since rating is metadata rather than appearance. Add your preferred rating tag separately.

JSON object output

The model can also emit a zone-keyed JSON object that groups tags by semantic category:

llm = Llama(model_path="BooruNL-0.8B-Q6_K.gguf", n_gpu_layers=-1, n_ctx=2048)

prompt = """### Input:
Output as JSON. Convert this to danbooru tags: A solo slim red-haired girl with freckles \
and very small breasts, wearing a maid outfit.
### Tags:
"""

result = llm(prompt, max_tokens=512, temperature=0.05, repeat_penalty=1.3, echo=False)
print(result["choices"][0]["text"])

Output:

{"RATING": ["s"], "SUBJECT": ["1girl", "solo"], "HAIR": ["red_hair"], "BODY": ["breasts", "freckles", "small_breasts"], "ATTIRE_STATE": ["slim"]}

A richer description populates more zones, including STYLE, SCENE, PROP, ACTION, and META:

### Input:
Turn this description into danbooru tags, output as JSON:
A sole female kneeling on a beach under a blue sky with clouds and a mountainous horizon during
the day, with a full body official art second-party source shot at a dutch angle. She has long
light purple hair in low twintails with a white bow, a hair bow, a white flower, a white
headwear, a yellow scrunchie, a purple ribbon, a white ribbon, a wrist scrunchie, and a leg
ribbon. She wears a frilled one-piece swimsuit with frills, a sun hat with a hat ribbon, and a
white bow. Her bare shoulders are exposed, her medium breasts are visible, her navel is covered,
and she is barefoot with a thigh shown. She has blue eyes and an open-mouth smile with a blush,
looking at viewer while doing a hat tug with her hands on hat. The scene includes a beach chair,
a beach umbrella, a bird, flower petals, a sand sculpture, a sandcastle, a ball, a beachball,
a bucket, and flowers.
### Tags:
{"RATING": ["s"], "SUBJECT": ["1girl", "solo"], "STYLE": ["swimsuit"], "HAIR": ["light_purple_hair", "long_hair", "low_twintails", "twintails"], "FACE": ["blue_eyes"], "BODY": ["breasts", "medium_breasts", "thighs"], "ATTIRE": ["bow", "hair_bow", "hat", "hat_ribbon", "purple_ribbon", "ribbon", "scrunchie", "sun_hat", "white_bow", "white_flower", "white_headwear", "yellow_scrunchie", "frilled_one-piece_swimsuit", "frills", "one-piece_swimsuit", "white_ribbon", "wrist_scrunchie", "leg_ribbon"], "ATTIRE_STATE": ["bare_shoulders", "barefoot", "covered_navel"], "STATE": ["hands_on_headwear", "kneeling"], "SCENE": ["beach_chair", "bird", "petals", "sand_sculpture", "sand_castle", "beach_umbrella", "blue_sky", "cloud", "day", "mountainous_horizon", "sky"], "PROP": ["ball", "beachball", "bucket", "flower", "umbrella"], "ACTION": ["hat_tug"], "EXPRESSION": [":d", "blush", "looking_at_viewer", "open_mouth", "smile"], "META": ["dutch_angle", "full_body", "official_art", "second-party_source"]}

The JSON object is generated model-natively — no post-processing or conversion is applied. The model outputs valid JSON directly.

Tags are sorted into up to 25 named zones covering the full danbooru taxonomy. Which keys appear depends entirely on the input — only populated zones are emitted. A fully described explicit scene might produce RATING, SUBJECT, STYLE, HAIR, FACE, BODY, ATTIRE, ATTIRE_STATE, STATE, SCENE, SEXUAL, and META; a simple character description may produce only a handful. Note that in JSON mode RATING is emitted as a single letter (g / s / q / e) — unlike flat mode where rating is omitted and must be appended manually.

This format is useful for downstream processing — filtering by zone, selectively overriding categories, or feeding structured data into other pipelines.

The flat comma-separated format remains the default; instruct the model to use JSON by including "Output as JSON." (or similar) in the instruction prefix.

Recommended settings

Setting Value Reason
temperature 0.05 Best validity (90.5% HF / 89.6% Q6_K). Greedy (0.0) is nearly identical.
repetition_penalty / repeat_penalty 1.3 Suppresses rare looping on edge-case prompts.
max_tokens / max_new_tokens 256 Sufficient for all but the most elaborate scene descriptions.

Prompt quality affects both output quality and speed

More detailed descriptions produce better tags and generate faster.

When the input is specific — named clothing items, exact colors, clear actions — the model resolves its first token with high confidence and proceeds quickly. A vague or underspecified prompt spreads probability mass across many plausible continuations, which slows down every generation step even when the final output is shorter.

Measured on the HF model (bfloat16):

Prompt TTFT Total Tags emitted
"A girl with blue hair." 1,196ms 1,196ms 1 (blue_hair only — missing 1girl, solo)
"A solo slim red-haired girl with freckles and very small breasts, wearing a maid outfit." 830ms 4,751ms 7
"A gothic punk girl…" (full 60-word description) 747ms 57,708ms 61

The two-word prompt is not only incomplete — it is also the slowest to first token because the model is uncertain what to commit to. The 60-word gothic punk description generates its first tag 37% faster despite the far longer output, because every detail narrows the search space immediately.

Practical tip: Always include subject count (a solo girl, two women), key physical traits (hair color/length, eye color, body type), clothing, and setting. The more you specify, the faster and more complete the result.

Benchmarks

Quality — HF (bfloat16) vs Q6_K GGUF

Measured on 10 prompts (2 short, 2 medium, 2 long, 4 explicit). Validity = percentage of emitted tags that exist in the authorized 137k-tag danbooru vocabulary. Repetition penalty 1.3, max 256 tokens, 2 samples per prompt at temp > 0.

Temp HF valid% Q6_K valid% HF TTFT Q6_K TTFT HF total Q6_K total
0.00 89.6% 88.1% 909ms 165ms 13,668ms 920ms
0.05 90.5% 89.6% 901ms 155ms 13,696ms 895ms
0.10 87.2% 88.9% 878ms 157ms 14,265ms 940ms
0.15 87.5% 88.3% 915ms 156ms 13,108ms 914ms

The Q6_K GGUF is within 1% of the full bfloat16 model at every temperature while being ~15× faster end-to-end and ~5–6× faster to first token. At temp ≥ 0.10 Q6_K slightly edges ahead. The HF model is slower because the base Qwen3.5-0.8B architecture includes a vision encoder (~200MB) that loads into VRAM even for text-only inference; the main GGUF strips it, keeping only the language model weights.

CPU vs GPU — Q6_K

Measured on a single representative long prompt, 5 runs each.

Backend TTFT Total Tags Valid%
CPU 381ms 3,708ms 40.0 80.0%
GPU (CUDA) 396ms 3,753ms 40.0 80.0%

CPU and GPU are essentially identical. In this run the GPU was marginally slower on time-to-first-token.

At 0.8B parameters and Q6_K quantization (600MB), the model fits in CPU cache on modern hardware. PCIe transfer overhead between host memory and GPU offsets the GPU's compute advantage, leaving no measurable benefit to GPU offload. This is consistent with Qwen3.5-0.8B's design target of edge and mobile deployment.

If you have no GPU, or want to keep your VRAM free for image generation, run the Q6_K on CPU — you pay no speed penalty.

Output examples

All examples: Q6_K, temp=0.05, rep_penalty=1.3.

Medium — maid outfit

Input:  A solo slim red-haired girl with freckles and very small breasts, wearing a maid outfit.
Output: 1girl, solo, maid, red_hair, breasts, freckles, small_breasts

Long — futuristic scene

Input:  A tall, athletic dark-skinned woman with short white hair and red eyes stands confidently
        in a futuristic city at night. She wears a sleeveless black bodysuit with gold trim,
        knee-high boots, and a long white coat draped over her shoulders. Neon lights reflect
        off the wet street behind her.

Output: 1girl, dark-skinned_female, solo_focus, short_hair, white_hair, red_eyes, black_bodysuit,
        bodysuit, coat, gold_trim, long_coat, sleeveless, boots, knee_boots, coat_on_shoulders,
        confident, standing, city, neon_lights, night, reflection, street, wet

Explicit — solo

Input:  A solo nude woman lying on her back, legs spread, fingering herself. A large dildo is
        inserted in her pussy, vibrator pressed against her clit.

Output: 1girl, solo_focus, pussy, spread_legs, fingering

Explicit — two characters

Input:  A man fucking a woman from behind in doggy style, gripping her hips. She gasps, breasts
        swinging. Vaginal penetration, creampie visible.

Output: 1boy, 1girl, breasts, pussy, penis, doggystyle, vaginal, from_behind, sex

Known limitations

  • No character or artist tags. The model was trained on visual appearance descriptions only. Tags like hatsune_miku or melkor_mancin are not emitted. A character/artist LoRA is planned.
  • Very short prompts are incomplete. Two-word inputs like "a girl with blue hair" may return only blue_hair, omitting 1girl and solo. Training data required a minimum of 10 tags per row, so single-feature inputs are outside the training distribution. Always include the subject count and at least a few additional details.
  • Image input does not work. The base model has a vision encoder and an mmproj can be extracted, but the fine-tuning was text-only. Feeding an image produces hallucinated garbage. Image→tags would require a separate training run on image+tag pairs.
  • Rating tags are not emitted. Add rating:safe, rating:questionable, or rating:explicit yourself based on your use case.
  • Complex or ambiguous clothing can hallucinate. The model occasionally invents tag names for garment combinations it has not seen (e.g. sailor-collar_shirt instead of sailor_collar). A constrained-decoding fence (trie of 137k authorized tags) eliminates this class of error entirely at inference time; see the TagForge integration below.
  • Style zone misclassification. Frilly or sailor-collar garments reliably trigger "maid" in the JSON STYLE zone even when the described style is clearly something else (e.g. gothic punk). This is a training bias; the flat output is unaffected.
  • Image input does not work as a pipeline shortcut. The intended workflow for image→tags is: run a vision LLM (e.g. an abliterated Gemma or JoyCaption) to generate a natural-language description, then pipe that description into BooruNL with the standard instruction prefix. Feeding an image directly via the mmproj produces hallucinated output — the vision encoder was never trained to map visual features to danbooru tags.

Training details

Detail Value
Base model Qwen/Qwen3.5-0.8B-Base
Fine-tune method Full fine-tune (Unsloth + SFTTrainer)
Dataset 124,252 description → tag pairs (Gemma-4-26B-A4B labelled)
Epochs 2
Best checkpoint Step 5800
Final F1 85.2% (P 94.2%, R 77.9%)
Invalid tag rate 2.0% (without fence)
Hardware NVIDIA RTX 5060 Ti 16GB

The training set covers the full danbooru tag vocabulary including explicit content. Gender-mismatch rows (Gemini labeller hallucinating male tags on female subjects) were detected and removed before training, which was the main quality improvement over the previous dataset version.

TagForge integration

BooruNL-0.8B ships as a node in TagForgeSuite. The node auto-downloads BooruNL-0.8B-Q6_K.gguf to models/BooruNL/ on first use and applies a constrained-decoding fence at inference time, guaranteeing that every emitted tag is a real, authorized danbooru tag — the 2% invalid rate above drops to 0%.

The JSON zone output from BooruNL is natively compatible with TagForge's structured_tags input — no conversion needed. Both systems were built by the same author and share the same zone vocabulary. The TagForge sampler accepts BooruNL JSON directly alongside its own native string format and handles the zone mapping automatically.

TagForge is not yet publicly released, but a release is planned soon.

Downloads last month
177
GGUF
Model size
0.8B params
Architecture
qwen35
Hardware compatibility
Log In to add your hardware

4-bit

6-bit

8-bit

Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for cooperdk/BooruNL-0.8B-GGUF

Quantized
(31)
this model