Loading
The model is a native vision-language model and loads through
AutoModelForImageTextToText.
import torch
from transformers import AutoModelForImageTextToText, AutoTokenizer
REPO = "CrowtherLabs/atom-proton-1.0"
model = AutoModelForImageTextToText.from_pretrained(
REPO,
dtype=torch.bfloat16,
device_map="auto",
)
model.eval()
tokenizer = AutoTokenizer.from_pretrained(REPO)
Use AutoModelForCausalLM instead only if you intend to drop the vision tower and
serve the text-only decoder.
The weights occupy approximately 55 GB in bfloat16, so plan for an 80 GB
accelerator, or pass a quantization_config to fit a smaller one.
The architecture interleaves two attention types across its 64 layers, and the
linear-attention layers have a fast path that transformers does not ship. Without
it you will see
The fast path is not available ... Falling back to torch implementation
and noticeably slower inference. Install
flash-linear-attention and
causal-conv1d to enable it.
Generating
Serve this model at xhigh reasoning effort, which is the setting it was
adapted under. The chat template resolves effort as follows:
Table with columns: value, effect on the system prefix| value | effect on the system prefix |
|---|
| omitted | defaults to xhigh |
xhigh | full deliberation instruction |
high | alias for xhigh, identical output |
medium | no instruction line at all |
low | brief-thinking instruction |
Any other value raises an exception. Omitting the argument therefore gives the
correct prefix already, but set it explicitly so that a client configured with a
different default cannot silently change the prompt the model sees.
messages = [
{"role": "system", "content": "You are Atom, one of Crowther's specialised AI models."},
{"role": "user", "content": "Summarise the attached procurement policy in five points."},
]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
reasoning_effort="xhigh",
)
inputs = tokenizer(text, return_tensors="pt").to(model.device)
outputs = model.generate(**inputs, max_new_tokens=1024, do_sample=False)
decoded = tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
Reading the output
add_generation_prompt=True ends the prompt with <|im_start|>assistant\n<think>\n,
so generation begins inside the thinking block. The model emits its reasoning,
closes it with </think>, then writes the answer. Separate them on the closing
tag:
reasoning, _, answer = decoded.partition("</think>")
Show the answer to users, not the reasoning. Setting enable_thinking=False in
apply_chat_template suppresses reasoning, but the model was adapted exclusively
on thinking-enabled examples, so behaviour at that setting was not exercised.
Serving
The weights are a standard qwen3_5 architecture checkpoint, so any runtime with
support for that architecture can serve them:
vllm serve CrowtherLabs/atom-proton-1.0 --dtype bfloat16
Pass the reasoning effort through the client's chat-template arguments so that the
system prefix matches adaptation. In an OpenAI-compatible request that is
chat_template_kwargs: {"reasoning_effort": "xhigh"}. Serving configuration was
not exercised during adaptation, so verify the rendered prompt before relying on
it in production.