Lucia 0.8B: Qwen3.5-0.8B, think symbolic image (full fine-tune)
Lucia 0.8B is the smallest member of the Lucia family, the thinking lookers: a full fine-tune of Qwen/Qwen3.5-0.8B that looks at a picture and writes the row of thought-symbolic-image-captions: a short chain of plain-language thoughts, one named step per line, then the wordings (a caption, a prompt, tags, a critique, by depth), then the questions a careful looker would still ask, each with its answer and what settles it. Every size is taught the same rows under the same recipe; the sizes differ in what they can hold, and the reads below say what this one holds. She keeps the row's form whole and backs her thoughts at the 9B's level; the medium term and the long specification rows are where the size shows. The lightest member for small cards and bulk runs.
see: A single figure, seated, holding a book, in three-quarter view.
place: The figure sits at the centre, the book held low against the lap.
form: The sitter fills the frame from the knees up, the background a plain dark wash.
colour: Brown ink over graphite, the paper a warm cream.
mood: The lowered eyes and the still hands suggest absorption, a reader lost in the page.
caption: A seated figure reads a book held in the lap, drawn in brown ink over graphite.
prompt: drawing, seated figure reading, three-quarter view, brown ink over graphite, cream paper, plain dark wash
question: thing name -> book [record]
question: medium -> ink and graphite [record]
question: mood tone -> absorbed [chain]
question: sitter identity -> unknown [unknown]
The steps a chain may use, in the order a careful looker takes them: see, name, place, count, relate, form, light, colour, mood,
mean, intend, story, compare, change, check, say. A quick look holds 1 to 2 thoughts, a closer look 3 to 5, a long look 6 to 10.
Questions end in [record] (the answer is a fact of the picture), [chain] (the chain worked it out) or [unknown]. The prompt
line's first term names the kind of picture (painting, print, drawing, photograph).
Loading
This repository holds the full fine-tune; the LoRA adapter of the same recipe is AbstractPhil/qwen3.5-0.8b-think-symbolic-image, lighter to keep and to switch off. Every Lucia loads the same way and is run the same way: thinking off, greedy decoding, no system prompt (the row's form is learned, and a system turn she was not trained with makes her over-write). Needs transformers 5; the adapter also needs peft 0.18 or newer.
The LoRA adapter (AbstractPhil/qwen3.5-0.8b-think-symbolic-image): the base model, the adapter merged into it, the processor from the adapter repository (the base processor's files are kept there).
import torch
from peft import PeftModel
from transformers import AutoModelForMultimodalLM, AutoProcessor
processor = AutoProcessor.from_pretrained("AbstractPhil/qwen3.5-0.8b-think-symbolic-image")
model = AutoModelForMultimodalLM.from_pretrained("Qwen/Qwen3.5-0.8B", dtype=torch.bfloat16, device_map="cuda")
model = PeftModel.from_pretrained(model, "AbstractPhil/qwen3.5-0.8b-think-symbolic-image").merge_and_unload().eval()
The full fine-tune (AbstractPhil/qwen3.5-0.8b-think-symbolic-image-full): the model and the processor from the full repository, nothing else to merge.
import torch
from transformers import AutoModelForMultimodalLM, AutoProcessor
processor = AutoProcessor.from_pretrained("AbstractPhil/qwen3.5-0.8b-think-symbolic-image-full")
model = AutoModelForMultimodalLM.from_pretrained("AbstractPhil/qwen3.5-0.8b-think-symbolic-image-full", dtype=torch.bfloat16, device_map="cuda").eval()
Then, for either:
from PIL import Image
image = Image.open("picture.jpg").convert("RGB")
prompt = ("Look at this picture and think it through in writing, a closer look, ending with a caption; then write a caption "
"and a prompt or tags, and the questions a careful looker would still ask.")
messages = [{"role": "user", "content": [{"type": "image", "image": image}, {"type": "text", "text": prompt}]}]
text = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False, enable_thinking=False)
inputs = processor(text=[text], images=[image], return_tensors="pt").to(model.device)
out = model.generate(**inputs, max_new_tokens=800, do_sample=False)
print(processor.batch_decode(out[:, inputs["input_ids"].shape[1]:], skip_special_tokens=True)[0])
The depth words: "a quick look" (1 to 2 thoughts), "a closer look" (3 to 5), "a long look" (6 to 10). The parser, the scorer and
the trainer are in qwen-symbolic-thinking
(qwen_symbolic_thinking.chain.parse, chain.score); the same package extracted every number below.
The reads
Read the same way as the adapter and as Lucia v1, the 9B: 60 pictures and 40 specifications no training saw, against the dataset's own rows (row shape, said / backed, definitions); then the medium term on two sets no training row came from: 500 National Gallery of Art pictures (100 drawings, prints, paintings, photographs and objects each) and 200 pictures of AIC, NGA, Cleveland and the Met. The adapter's numbers and the 9B's stand beside.
| read | Lucia 0.8B, full fine-tune | Lucia 0.8B, the adapter | Lucia v1 (9B) |
|---|---|---|---|
| row shape, pictures / specifications | 1.00 / 1.00 | 1.00 / 1.00 | 1.00 / 1.00 |
| said / backed, whole rows | 0.20 / 0.30 | 0.19 / 0.30 | 0.19 / 0.30 |
| definitions, pictures / specifications | 0.00 / 0.15 | 0.00 / 0.12 | 0.02 / 0.08 |
| 500 held-out pictures: painting / print / drawing / photograph / object named rightly of 100 | 83 / 75 / 78 / 80 / 90 | 83 / 74 / 80 / 79 / 90 | 83 / 76 / 86 / 80 / 90 |
| 200 museum pictures: painting / print / drawing / photograph / object | 13 of 27 / 37 of 39 / 12 of 13 / 12 of 13 / 66 of 66 | 13 of 27 / 37 of 39 / 12 of 13 / 12 of 13 / 66 of 66 | 13 of 27 / 38 of 39 / 12 of 13 / 11 of 13 / 66 of 66 |
| the 200 museum pictures on one RTX PRO 6000, batch 64, 800 new tokens a row | 0.15 s a row | 0.13 s a row | 0.27 s a row |
What this says: the full fine-tune and the adapter are the same looker. Every read lands within the spread of the adapter's own
two seeds (the 500 held-out pictures 406 named rightly against the adapter's 406; the museum pictures identical; said / backed
0.20 / 0.30 against 0.19 / 0.30), so the choice between the two is a matter of deployment, not of quality: the full model loads
on its own with no adapter library; the adapter is 20 M parameters to keep and can be switched off. What the size does not
hold is the same in both: the drawing term on the held-out pictures (78 to 80 against the 9B's 86) and more defining phrases on
the long specification rows. The museum painting row understates every size: watercolours and gouaches on paper are named
drawings, as the National Gallery's own catalogue does. The reads are made with qwen_symbolic_thinking.tools (probe_read,
museum_read, museum_mismatch, screen_conflicts), so any model can be read the same way.
One note on stopping: this model's generation_config.json names both <|endoftext|> and <|im_end|> as end tokens. The
base's configuration names only the first, and a fully fine-tuned model no longer writes it after a turn, so with the base's
configuration the row would run on. Load the model from this repository as shown above and it stops where the row ends.
How she was trained
| base | Qwen/Qwen3.5-0.8B; the language model's weights trained in fp32 under bf16 autocast, the vision tower frozen; saved in bf16 with the processor (752 M parameters trained, 1.7 GB) |
| data | Lucia v1's rows exactly, as for the adapter: 951 picture rows taught twice, 500 long-look drawn rows, 2,000 synthetic closer looks and 17,385 museum rows whose catalogue gives the medium, the prompt line's first term set to it (6,606 drawings, 3,523 paintings, 3,000 prints, 2,016 photographs, 2,240 objects); 20,836 items, 21,787 examples; the 500 probe pictures held out |
| optimiser | plain Adam 2e-5 (a full fine-tune's rate, a tenth of the adapter's), cosine schedule, 3% warmup, batch 8 x 2, two epochs (2,724 steps, 1.17 s a step, 53 minutes on one RTX PRO 6000), the loss on the answer only, seed 20261030 |
| thinking | off in training and at inference: every Lucia is taught and run with enable_thinking=False |
| stopping | generation_config.json ends a row on `< |
| record | qwen-symbolic-thinking v0.1.18, run_queue("lucia_0.8b") from notebooks/lucia_0.8b.ipynb; the run's full log is train_summary.json beside the weights; read under v0.1.22 |
Versions
| version | where | tag |
|---|---|---|
| Lucia 0.8B v1 | the root of this repository | lucia-v1 |
The family
| size | base | the LoRA adapter | the full fine-tune |
|---|---|---|---|
| 9B | Qwen/Qwen3.5-9B | qwen3.5-9b-think-symbolic-image | not trained (the states of a 9B full fine-tune do not fit one card) |
| 4B | Qwen/Qwen3.5-4B | qwen3.5-4b-think-symbolic-image | qwen3.5-4b-think-symbolic-image-full |
| 2B | Qwen/Qwen3.5-2B | qwen3.5-2b-think-symbolic-image | qwen3.5-2b-think-symbolic-image-full |
| 0.8B | Qwen/Qwen3.5-0.8B | qwen3.5-0.8b-think-symbolic-image | qwen3.5-0.8b-think-symbolic-image-full |
The 9B is Lucia v1; every other size is trained under her recipe and read on the same held-out sets.
License
Apache-2.0, the base model's licence. The rows she was taught come from the datasets above under their own terms.