andreagemelli commited on
Commit
f7e9c38
·
verified ·
1 Parent(s): f814bef

Fix generation from the published artifact; rewrite model card

Browse files
README.md CHANGED
@@ -16,11 +16,9 @@ tags:
16
  base_model:
17
  - OpenGVLab/InternViT-300M-448px-V2_5
18
  - PleIAs/Baguettotron
19
- datasets:
20
- - liuhaotian/LLaVA-CC3M-Pretrain-595K
21
  ---
22
 
23
- # Baguettotron-VLM — Stage 1 (Projector Warmup)
24
 
25
  **Baguettotron-VLM** is an open, fully-reproducible, multilingual Vision-Language
26
  Model in the **sub-1B parameter** class. It extends
@@ -29,26 +27,24 @@ text-only reasoning SLM — with visual capabilities via the
29
  [InternViT-300M-448px-V2.5](https://huggingface.co/OpenGVLab/InternViT-300M-448px-V2_5)
30
  vision encoder and a lightweight MLP projector, for a total of ~628M parameters.
31
 
32
- It supports **six European languages** (EN, FR, DE, ES, IT, PL, inherited from
33
- the Baguettotron backbone), natively emits `<think>` reasoning traces, and was
34
- trained end-to-end on a single H100 SXM code, data pipeline, weights, and
35
- training logs are all public. Everything is released under **Apache 2.0** with
36
- a proof-of-concept goal: exploring what an open sub-1B multilingual VLM can do,
37
- not chasing SOTA.
38
 
39
- The project ships three checkpoints from a staged training curriculum:
40
 
41
- - **Stage 1 — projector warmup**: [andreagemelli/Baguettotron-VLM-Stage1](https://huggingface.co/andreagemelli/Baguettotron-VLM-Stage1)
42
- (MLP projector only, LLaVA-CC3M-Pretrain-595K published for reproducibility).
43
- - **Stage 2 — instruction tuning**: [andreagemelli/Baguettotron-VLM-Stage2](https://huggingface.co/andreagemelli/Baguettotron-VLM-Stage2)
44
- (full fine-tune on The Cauldron + SYNTH, direct-answer mode).
45
- - **Stage 3 — reasoning SFT (flagship)**: [andreagemelli/Baguettotron-VLM](https://huggingface.co/andreagemelli/Baguettotron-VLM)
46
- (reasoning traces via R1-Vision-Reasoning-Instructions, dynamic `<think>` toggle).
47
 
48
- Source & training pipeline: [github.com/andreagemelli/baguettotron-vlm](https://github.com/andreagemelli/baguettotron-vlm).
 
49
 
50
 
51
- > **Stage 1 checkpoint.** Only the MLP projector has been trained (on LLaVA-CC3M-Pretrain-595K); the ViT and the Baguettotron LLM are the unmodified base weights. Published for reproducibility for actual use prefer the Stage 2 or Stage 3 checkpoints.
52
 
53
  ## Architecture
54
 
@@ -59,32 +55,35 @@ Image (448×448)
59
  → MLP projector (2-layer, ~2.7M) → 256 tokens × 576d
60
  → Interleave with text tokens
61
  → Baguettotron (321M, Llama arch, 80L, h=576)
62
- → Text output with <think> reasoning traces
63
 
64
  Total: ~628M parameters
65
  ```
66
 
67
  ## Usage
68
 
 
 
 
 
69
  ```python
70
  import torch
71
  from transformers import AutoModelForImageTextToText, AutoProcessor
72
  from PIL import Image
73
 
74
  model = AutoModelForImageTextToText.from_pretrained(
75
- "andreagemelli/Baguettotron-VLM-Stage1",
76
  trust_remote_code=True,
77
- torch_dtype=torch.bfloat16,
78
  device_map="auto",
79
  )
80
  processor = AutoProcessor.from_pretrained(
81
- "andreagemelli/Baguettotron-VLM-Stage1",
82
  trust_remote_code=True,
83
  )
84
 
85
  image = Image.open("photo.jpg").convert("RGB")
86
  inputs = processor(
87
- messages=[{"role": "user", "content": "<image>\nDescribe the image"}],
88
  image=image,
89
  )
90
  inputs = {k: v.to(model.device) for k, v in inputs.items() if v is not None}
@@ -92,13 +91,61 @@ inputs = {k: v.to(model.device) for k, v in inputs.items() if v is not None}
92
  print(model.chat(**inputs))
93
  ```
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  ### Chat template
96
 
97
- Stage 1 was trained on short image captions with no `<think>` traces. The processor emits a bare assistant prefix (`<|im_start|>assistant\n`) and the model completes the caption directly. Keep prompts simple ("Describe the image").
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
- ## Training details
100
 
101
- | | Stage 1 |
102
  |---|---|
103
  | Data | LLaVA-CC3M-Pretrain-595K (595K image-caption pairs) |
104
  | Trainable params | ~2.7M (projector only) |
@@ -107,29 +154,26 @@ Stage 1 was trained on short image captions with no `<think>` traces. The proces
107
  | Learning rate | 1e-3, cosine, 250-step warmup |
108
  | Precision | bf16 |
109
  | Hardware | 1× H100 SXM (RunPod) |
110
- | Duration | ~5h |
111
-
112
- ## Project scope & limitations
113
-
114
- Baguettotron-VLM is a **self-educational, open-source proof of concept** trained
115
- on a limited compute budget. The goal has always been to explore what an open,
116
- fully-reproducible, multilingual sub-1B VLM can do when every design choice has
117
- to justify itself against a hard budget cap.
118
-
119
- Concretely, this means the model may have limitations:
120
-
121
- - **Likely undertrained for VQA alignment.** The Stage 2 instruction-tuning
122
- budget (~60h on a single H100) is small relative to the data mixture; the
123
- model has seen far fewer tokens than typical VLMs of comparable size.
124
- - **Hallucinations.** The Baguettotron backbone is pretrained on synthetic
125
- corpora and reasoning traces, which gives it strong `<think>` behaviour but
126
- also a tendency to confabulate details that aren't grounded in the image,
127
- especially on fine-grained or text-heavy questions.
128
- - **Best on simple, scene-level prompts.** In informal testing the model is
129
- most reliable at open-ended description and scene understanding
130
- ("what is happening in this image?") and less reliable at OCR, counting,
131
- chart reading, or multi-hop visual reasoning. Quantitative benchmark
132
- numbers will be published once the evaluation harness lands.
133
 
134
  **Contributions and suggestions are very welcome** — issues, PRs, and ideas for
135
  better data mixes, training recipes, or evaluation setups are all appreciated.
@@ -146,7 +190,7 @@ If you use or extend Baguettotron-VLM in your research, please cite it:
146
  title = {Baguettotron-VLM: An Open, Reproducible, Multilingual Sub-1B Vision-Language Model},
147
  author = {Gemelli, Andrea},
148
  year = {2026},
149
- howpublished = {\url{https://huggingface.co/andreagemelli/Baguettotron-VLM}},
150
  note = {Code: \url{https://github.com/andreagemelli/baguettotron-vlm}}
151
  }
152
  ```
@@ -154,4 +198,4 @@ If you use or extend Baguettotron-VLM in your research, please cite it:
154
 
155
  ## License
156
 
157
- Apache 2.0 — see the [GitHub repo](https://github.com/andreagemelli/baguettotron-vlm).
 
16
  base_model:
17
  - OpenGVLab/InternViT-300M-448px-V2_5
18
  - PleIAs/Baguettotron
 
 
19
  ---
20
 
21
+ # baguettotron-internvit-alignment
22
 
23
  **Baguettotron-VLM** is an open, fully-reproducible, multilingual Vision-Language
24
  Model in the **sub-1B parameter** class. It extends
 
27
  [InternViT-300M-448px-V2.5](https://huggingface.co/OpenGVLab/InternViT-300M-448px-V2_5)
28
  vision encoder and a lightweight MLP projector, for a total of ~628M parameters.
29
 
30
+ Trained end-to-end in **~72h on a single H100 (~€219)**. It inherits six European
31
+ languages (EN, FR, DE, ES, IT, PL) from the Baguettotron backbone, and — unusually —
32
+ its entire LM pretraining corpus is auditable: Baguettotron was trained on
33
+ [SYNTH](https://huggingface.co/datasets/PleIAs/SYNTH), ~58K Wikipedia articles expanded
34
+ synthetically, with no scraped web.
 
35
 
36
+ Two checkpoints are published:
37
 
38
+ - [**baguettotron-vision-vqa**](https://huggingface.co/andreagemelli/baguettotron-vision-vqa)
39
+ instruction-tuned on The Cauldron. **This is the model you want.**
40
+ - [baguettotron-internvit-alignment](https://huggingface.co/andreagemelli/baguettotron-internvit-alignment)
41
+ projector-only warmup, published so the alignment stage can be reproduced.
 
 
42
 
43
+ Apache 2.0. This is a proof of concept and a costed recipe, not a SOTA attempt.
44
+ Source: [github.com/andreagemelli/baguettotron-vlm](https://github.com/andreagemelli/baguettotron-vlm).
45
 
46
 
47
+ > **Alignment checkpoint.** Only the MLP projector was trained (on LLaVA-CC3M-Pretrain-595K) the ViT and the Baguettotron LLM are unmodified base weights. It writes short captions and nothing more. Published so the alignment stage can be reproduced; for actual use take [baguettotron-vision-vqa](https://huggingface.co/andreagemelli/baguettotron-vision-vqa).
48
 
49
  ## Architecture
50
 
 
55
  → MLP projector (2-layer, ~2.7M) → 256 tokens × 576d
56
  → Interleave with text tokens
57
  → Baguettotron (321M, Llama arch, 80L, h=576)
 
58
 
59
  Total: ~628M parameters
60
  ```
61
 
62
  ## Usage
63
 
64
+ ```bash
65
+ pip install "transformers>=4.56,<5" torch pillow timm einops accelerate
66
+ ```
67
+
68
  ```python
69
  import torch
70
  from transformers import AutoModelForImageTextToText, AutoProcessor
71
  from PIL import Image
72
 
73
  model = AutoModelForImageTextToText.from_pretrained(
74
+ "andreagemelli/baguettotron-internvit-alignment",
75
  trust_remote_code=True,
76
+ dtype=torch.bfloat16,
77
  device_map="auto",
78
  )
79
  processor = AutoProcessor.from_pretrained(
80
+ "andreagemelli/baguettotron-internvit-alignment",
81
  trust_remote_code=True,
82
  )
83
 
84
  image = Image.open("photo.jpg").convert("RGB")
85
  inputs = processor(
86
+ messages=[{"role": "user", "content": "<image>\nDescribe the image concisely."}],
87
  image=image,
88
  )
89
  inputs = {k: v.to(model.device) for k, v in inputs.items() if v is not None}
 
91
  print(model.chat(**inputs))
92
  ```
93
 
94
+ `chat()` returns the answer as a plain string, already trimmed at the end of the
95
+ assistant turn. `max_new_tokens=...` bounds the length.
96
+
97
+ ### Apple Silicon and CPU
98
+
99
+ The snippet above runs unchanged on `mps` and `cpu` — keep `device_map="auto"`, or drop
100
+ it and call `.to("mps")` yourself. `dtype=torch.float32` works too. A short answer takes
101
+ ~1-3s on `mps` against ~20-25s on CPU, so prefer `mps` on a Mac.
102
+
103
+ One caveat handled for you: transformers' repetition-penalty processor corrupts the
104
+ first decoding step on MPS, which produced a garbage first token. `chat()` disables the
105
+ penalty on that backend automatically.
106
+
107
+ ### Example output
108
+
109
+ Four COCO images, greedy, `Describe the image concisely.` — verbatim:
110
+
111
+ | image | output |
112
+ |---|---|
113
+ | two cats on a couch | `a cat is sleeping on the couch` |
114
+ | close-up of a brown bear | `the bear is a good friend.` |
115
+ | an upside-down STOP sign | `a sign for a stop sign` |
116
+ | a red double-decker bus | `the bus is a red double - decoration` |
117
+
118
+ That is the honest range of this checkpoint: it locates the subject and then drifts, which is what 2.7M trained parameters buys. It does not read the text on the sign or the bus.
119
+
120
  ### Chat template
121
 
122
+ Trained on short image captions with no `<think>` traces. The processor emits a bare assistant prefix (`<|im_start|>assistant\n`) and the model completes the caption directly. Keep prompts simple ("Describe the image").
123
+
124
+ ## What the evaluation found
125
+
126
+ Measured on 6 held-out [RealWorldQA](https://huggingface.co/datasets/xai-org/RealworldQA)
127
+ images, judged 1–5 by a vision judge, 244 of 288 cells completed. Thin, and reported as
128
+ such — full tables and caveats in
129
+ [RESULTS.md](https://github.com/andreagemelli/baguettotron-vlm/blob/main/.planning/RESULTS.md).
130
+
131
+ - **Short factual questions are its strength** (2.5–3.5), open description its weakest
132
+ (~1.5). That inverted our own assumption that this would be a describer.
133
+ - **A third checkpoint made things worse.** A reasoning-SFT stage on top of the
134
+ instruction-tuned model scored 1.25 against its 2.17, roughly 3 SE. The reasoning
135
+ traces are undertrained for imagery and mostly add fluent hallucination, so that
136
+ checkpoint is **not published**. It is kept as a recorded negative result.
137
+ - **Decoding presets are inside noise** — greedy 2.17 vs 1.88 for the other three.
138
+
139
+ ### Decoding
140
+
141
+ Use **greedy**, which is the default. It is recommended for determinism, not because
142
+ it beat anything. Pass `do_sample=True, temperature=...` to `chat()` if you want
143
+ sampling anyway.
144
+
145
 
146
+ ## Training
147
 
148
+ | | |
149
  |---|---|
150
  | Data | LLaVA-CC3M-Pretrain-595K (595K image-caption pairs) |
151
  | Trainable params | ~2.7M (projector only) |
 
154
  | Learning rate | 1e-3, cosine, 250-step warmup |
155
  | Precision | bf16 |
156
  | Hardware | 1× H100 SXM (RunPod) |
157
+ | Duration | ~5h (~€15) |
158
+
159
+ ## Limitations
160
+
161
+ - **No stop token.** `<|im_end|>` was masked out of the training loss, so the model
162
+ never learned to emit one it spells the turn marker out as ordinary text instead.
163
+ `chat()` works around this by stopping on that text (a 512-token call drops from
164
+ ~21s to ~1.5s), but the defect is in the weights and needs a retrain to fix.
165
+ - **Resolution ceiling.** One 448×448 crop → 256 visual tokens puts document text at
166
+ roughly 2–4 px/char. OCR, charts and documents are out of reach by architecture, not
167
+ by budget. Neither published checkpoint reads text in an image.
168
+ - **Hallucinations**, especially on fine-grained or text-heavy questions.
169
+ - **Undertrained for VQA alignment** ~60h of instruction tuning on one H100 is small
170
+ relative to the data mixture.
171
+ - **Multilingual capability is inherited, not verified.** The backbone covers six
172
+ languages; the VLM was never evaluated on non-English benchmarks.
173
+ - **Evaluation is thin**: 6 held-out images, one judge, one rubric, 244 judged cells.
174
+ Every number here is directional.
175
+ - **`transformers` 5.x cannot load this model.** The blocker is upstream in InternViT's
176
+ remote code, which predates v5's tied-weight API. Pin `transformers<5`.
 
 
 
177
 
178
  **Contributions and suggestions are very welcome** — issues, PRs, and ideas for
179
  better data mixes, training recipes, or evaluation setups are all appreciated.
 
190
  title = {Baguettotron-VLM: An Open, Reproducible, Multilingual Sub-1B Vision-Language Model},
191
  author = {Gemelli, Andrea},
192
  year = {2026},
193
+ howpublished = {\url{https://huggingface.co/andreagemelli/baguettotron-vision-vqa}},
194
  note = {Code: \url{https://github.com/andreagemelli/baguettotron-vlm}}
195
  }
196
  ```
 
198
 
199
  ## License
200
 
201
+ Apache 2.0 — see the [GitHub repo](https://github.com/andreagemelli/baguettotron-vlm).
chat_template.jinja ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {% for m in messages %}<|im_start|>{{ m['role'] }}
2
+ {{ m['content'] }}<|im_end|>
3
+ {% endfor %}{% if add_generation_prompt %}<|im_start|>assistant
4
+ <think>
5
+ {% endif %}
config.json CHANGED
@@ -11,6 +11,13 @@
11
  "chat_style": "base",
12
  "stage": 1,
13
  "torch_dtype": "bfloat16",
 
 
 
 
 
 
 
14
  "auto_map": {
15
  "AutoConfig": "configuration_baguettotron_vlm.BaguettotronVLMConfig",
16
  "AutoModelForImageTextToText": "modeling_baguettotron_vlm.BaguettotronVLMForConditionalGeneration",
 
11
  "chat_style": "base",
12
  "stage": 1,
13
  "torch_dtype": "bfloat16",
14
+ "bos_token_id": 1,
15
+ "eos_token_id": [
16
+ 2,
17
+ 65492
18
+ ],
19
+ "pad_token_id": 3,
20
+ "tie_word_embeddings": false,
21
  "auto_map": {
22
  "AutoConfig": "configuration_baguettotron_vlm.BaguettotronVLMConfig",
23
  "AutoModelForImageTextToText": "modeling_baguettotron_vlm.BaguettotronVLMForConditionalGeneration",
generation_config.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": false,
3
+ "bos_token_id": 1,
4
+ "eos_token_id": [
5
+ 2,
6
+ 65492
7
+ ],
8
+ "pad_token_id": 3,
9
+ "do_sample": false,
10
+ "max_new_tokens": 256,
11
+ "transformers_version": "4.57.0"
12
+ }
modeling_baguettotron_vlm.py CHANGED
@@ -8,12 +8,39 @@ from transformers import (
8
  AutoModelForCausalLM,
9
  AutoTokenizer,
10
  PreTrainedModel,
 
11
  )
12
  from transformers.modeling_outputs import CausalLMOutputWithPast
13
 
14
  from .configuration_baguettotron_vlm import BaguettotronVLMConfig
15
 
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  class PixelUnshuffleProjector(nn.Module):
18
  """Reduces ViT tokens 4× via PixelUnshuffle then projects to LLM dim."""
19
 
@@ -55,37 +82,69 @@ class BaguettotronVLMForConditionalGeneration(PreTrainedModel):
55
 
56
  config_class = BaguettotronVLMConfig
57
  _no_split_modules = ["InternVisionEncoderLayer", "LlamaDecoderLayer"]
 
 
 
58
  # Tell HF Trainer not to pass num_items_in_batch (loss handled internally)
59
  model_accepts_loss_kwargs: bool = False
60
 
61
  def __init__(self, config: BaguettotronVLMConfig):
62
  super().__init__(config)
63
 
64
- self.vit = AutoModel.from_pretrained(
65
- config.vit_model_id,
66
- dtype=torch.bfloat16,
67
- low_cpu_mem_usage=True,
68
- trust_remote_code=True,
69
- )
70
- self.projector = PixelUnshuffleProjector(
71
- in_dim=config.vit_hidden,
72
- out_dim=config.llm_hidden,
73
- factor=config.unshuffle_factor,
74
- )
75
 
76
- tokenizer = AutoTokenizer.from_pretrained(config.llm_model_id)
77
- tokenizer.add_special_tokens(
78
- {"additional_special_tokens": [config.image_token, "</image>"]}
79
- )
80
- raw_id = tokenizer.convert_tokens_to_ids(config.image_token)
81
- self.image_token_id: int = raw_id if isinstance(raw_id, int) else int(raw_id[0])
 
 
 
 
 
 
 
 
 
 
82
 
83
- self.llm = AutoModelForCausalLM.from_pretrained(
84
- config.llm_model_id, dtype=torch.bfloat16
85
- )
 
 
 
 
 
 
 
 
 
86
  self.llm.resize_token_embeddings(len(tokenizer), mean_resizing=False)
87
- # Break weight tying — safetensors rejects shared-storage tensors
 
88
  self.llm.lm_head.weight = nn.Parameter(self.llm.lm_head.weight.data.clone())
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
  self._tokenizer = tokenizer
91
 
@@ -158,17 +217,51 @@ class BaguettotronVLMForConditionalGeneration(PreTrainedModel):
158
  -1, self.config.llm_hidden
159
  ).to(inputs_embeds.dtype)
160
 
161
- im_end_id = int(self._tokenizer.convert_tokens_to_ids("<|im_end|>"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  output_ids = self.llm.generate(
163
  inputs_embeds=inputs_embeds,
164
  attention_mask=attention_mask,
165
  max_new_tokens=max_new_tokens,
166
- do_sample=False,
167
  repetition_penalty=repetition_penalty,
168
- eos_token_id=[self._tokenizer.eos_token_id, im_end_id],
169
  **generate_kwargs,
170
  )
171
  decoded = self._tokenizer.decode(output_ids[0], skip_special_tokens=False)
172
- if "<|im_end|>" in decoded:
173
- decoded = decoded[: decoded.index("<|im_end|>")]
 
174
  return decoded.strip()
 
8
  AutoModelForCausalLM,
9
  AutoTokenizer,
10
  PreTrainedModel,
11
+ StoppingCriteria,
12
  )
13
  from transformers.modeling_outputs import CausalLMOutputWithPast
14
 
15
  from .configuration_baguettotron_vlm import BaguettotronVLMConfig
16
 
17
 
18
+ class StopOnTurnEnd(StoppingCriteria):
19
+ """Stop when the decoded tail contains a turn marker.
20
+
21
+ These weights never emit a stop *token*: <|im_end|> was masked out of the
22
+ training loss, so the model learned to spell the marker out as ordinary
23
+ text ("<|", "im", "_", "end", "|>") and then start a fresh turn. Passing
24
+ eos_token_id therefore does nothing — generation runs to max_new_tokens
25
+ every time, which is ~20x more tokens than the answer needs.
26
+
27
+ Matching on decoded text rather than token ids is deliberate: how the
28
+ marker splits depends on what precedes it (".<|" merges into a single
29
+ token after a period), so an id-sequence match misses many cases.
30
+ """
31
+
32
+ def __init__(self, tokenizer, markers=("<|im_end|>", "<|im_start|>"), window=8):
33
+ self.tokenizer = tokenizer
34
+ self.markers = markers
35
+ self.window = window
36
+
37
+ def __call__(self, input_ids: torch.Tensor, scores, **kwargs) -> bool:
38
+ tail = self.tokenizer.decode(
39
+ input_ids[0, -self.window:], skip_special_tokens=False
40
+ )
41
+ return any(marker in tail for marker in self.markers)
42
+
43
+
44
  class PixelUnshuffleProjector(nn.Module):
45
  """Reduces ViT tokens 4× via PixelUnshuffle then projects to LLM dim."""
46
 
 
82
 
83
  config_class = BaguettotronVLMConfig
84
  _no_split_modules = ["InternVisionEncoderLayer", "LlamaDecoderLayer"]
85
+ # Nothing is tied in this model — see __init__. Without this, the inherited
86
+ # Llama tied-weight bookkeeping drops llm.lm_head.weight during loading.
87
+ _tied_weights_keys: list[str] = []
88
  # Tell HF Trainer not to pass num_items_in_batch (loss handled internally)
89
  model_accepts_loss_kwargs: bool = False
90
 
91
  def __init__(self, config: BaguettotronVLMConfig):
92
  super().__init__(config)
93
 
94
+ # Honour the dtype the caller asked for. from_pretrained(dtype=X) makes X
95
+ # the default dtype for the duration of __init__, and the projector picks
96
+ # it up automatically. Hard-coding bfloat16 for the submodules while the
97
+ # projector follows the default is what made dtype=torch.float32 fail with
98
+ # "mat1 and mat2 must have the same dtype".
99
+ dtype = torch.get_default_dtype()
100
+ if dtype not in (torch.float32, torch.float16, torch.bfloat16):
101
+ dtype = torch.bfloat16
 
 
 
102
 
103
+ # The explicit CPU device context shields these nested from_pretrained
104
+ # calls from an outer meta-device init context (accelerate's device_map,
105
+ # and transformers >= 5), which otherwise aborts with "You are using
106
+ # from_pretrained with a meta device context manager".
107
+ with torch.device("cpu"):
108
+ self.vit = AutoModel.from_pretrained(
109
+ config.vit_model_id,
110
+ dtype=dtype,
111
+ low_cpu_mem_usage=True,
112
+ trust_remote_code=True,
113
+ )
114
+ self.projector = PixelUnshuffleProjector(
115
+ in_dim=config.vit_hidden,
116
+ out_dim=config.llm_hidden,
117
+ factor=config.unshuffle_factor,
118
+ )
119
 
120
+ tokenizer = AutoTokenizer.from_pretrained(config.llm_model_id)
121
+ tokenizer.add_special_tokens(
122
+ {"additional_special_tokens": [config.image_token, "</image>"]}
123
+ )
124
+ raw_id = tokenizer.convert_tokens_to_ids(config.image_token)
125
+ self.image_token_id: int = (
126
+ raw_id if isinstance(raw_id, int) else int(raw_id[0])
127
+ )
128
+
129
+ self.llm = AutoModelForCausalLM.from_pretrained(
130
+ config.llm_model_id, dtype=dtype
131
+ )
132
  self.llm.resize_token_embeddings(len(tokenizer), mean_resizing=False)
133
+ # Break weight tying — safetensors rejects shared-storage tensors, and
134
+ # training updates lm_head independently of the input embeddings.
135
  self.llm.lm_head.weight = nn.Parameter(self.llm.lm_head.weight.data.clone())
136
+ # Keep it broken. Llama declares lm_head.weight as a tied key, so
137
+ # from_pretrained would skip it while loading and then re-tie it to
138
+ # embed_tokens — silently discarding the trained output head.
139
+ self.llm.config.tie_word_embeddings = False
140
+ self.llm._tied_weights_keys = []
141
+
142
+ # NOTE: transformers >= 5 cannot load this model, and the blocker is
143
+ # upstream: OpenGVLab's InternViT remote code predates v5's tied-weight
144
+ # API, so v5 aborts inside the nested AutoModel.from_pretrained above
145
+ # with "'InternVisionModel' object has no attribute
146
+ # 'all_tied_weights_keys'". Nothing can be patched from here — the model
147
+ # cards pin transformers<5 until InternViT is updated.
148
 
149
  self._tokenizer = tokenizer
150
 
 
217
  -1, self.config.llm_hidden
218
  ).to(inputs_embeds.dtype)
219
 
220
+ # These weights end a turn by spelling out the literal text "<|im_end|>"
221
+ # as ordinary tokens and then emitting <|end_of_text|> — the special
222
+ # <|im_end|> token is never produced. <|end_of_text|> is therefore the id
223
+ # that actually stops generation; the others are listed for safety.
224
+ # PleIAs/Baguettotron's tokenizer declares no eos_token, so eos_token_id
225
+ # is None there and must be filtered out — an unfiltered [None, ...] makes
226
+ # generate() raise "'NoneType' object cannot be interpreted as an integer".
227
+ unk_id = self._tokenizer.unk_token_id
228
+ candidates = (
229
+ self._tokenizer.eos_token_id,
230
+ self._tokenizer.convert_tokens_to_ids("<|end_of_text|>"),
231
+ self._tokenizer.convert_tokens_to_ids("<|im_end|>"),
232
+ )
233
+ eos_ids = list(
234
+ dict.fromkeys(
235
+ tok_id
236
+ for tok_id in candidates
237
+ if tok_id is not None and tok_id != unk_id
238
+ )
239
+ )
240
+
241
+ # transformers' repetition-penalty processor corrupts the very first step
242
+ # on MPS: generate(inputs_embeds=...) starts from an empty input_ids, and
243
+ # the empty-index gather/scatter zeroes the entire logits row on Metal
244
+ # (it is a no-op on CPU and CUDA, as it should be). The result is a
245
+ # garbage first token that derails the whole answer.
246
+ if inputs_embeds.device.type == "mps" and repetition_penalty != 1.0:
247
+ repetition_penalty = 1.0
248
+
249
+ # do_sample defaults to greedy but can be overridden by callers
250
+ # (e.g. the inference sweep) without colliding on the keyword.
251
+ generate_kwargs.setdefault("do_sample", False)
252
+ generate_kwargs.setdefault(
253
+ "stopping_criteria", [StopOnTurnEnd(self._tokenizer)]
254
+ )
255
  output_ids = self.llm.generate(
256
  inputs_embeds=inputs_embeds,
257
  attention_mask=attention_mask,
258
  max_new_tokens=max_new_tokens,
 
259
  repetition_penalty=repetition_penalty,
260
+ eos_token_id=eos_ids,
261
  **generate_kwargs,
262
  )
263
  decoded = self._tokenizer.decode(output_ids[0], skip_special_tokens=False)
264
+ for marker in ("<|im_end|>", "<|im_start|>", "<|end_of_text|>"):
265
+ if marker in decoded:
266
+ decoded = decoded[: decoded.index(marker)]
267
  return decoded.strip()
special_tokens_map.json CHANGED
@@ -15,32 +15,5 @@
15
  "single_word": false
16
  }
17
  ],
18
- "bos_token": {
19
- "content": "<|begin_of_text|>",
20
- "lstrip": false,
21
- "normalized": false,
22
- "rstrip": false,
23
- "single_word": false
24
- },
25
- "eos_token": {
26
- "content": "<|end_of_text|>",
27
- "lstrip": false,
28
- "normalized": false,
29
- "rstrip": false,
30
- "single_word": false
31
- },
32
- "pad_token": {
33
- "content": "[PAD]",
34
- "lstrip": false,
35
- "normalized": false,
36
- "rstrip": false,
37
- "single_word": false
38
- },
39
- "unk_token": {
40
- "content": "[UNK]",
41
- "lstrip": false,
42
- "normalized": false,
43
- "rstrip": false,
44
- "single_word": false
45
- }
46
  }
 
15
  "single_word": false
16
  }
17
  ],
18
+ "eos_token": "<|end_of_text|>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  }
tokenizer.json CHANGED
@@ -50,7 +50,7 @@
50
  },
51
  {
52
  "id": 65492,
53
- "content": "<|im_end>",
54
  "single_word": false,
55
  "lstrip": false,
56
  "rstrip": false,
@@ -69,11 +69,11 @@
69
  {
70
  "id": 65494,
71
  "content": "</think>",
72
- "single_word": false,
73
  "lstrip": false,
74
  "rstrip": false,
75
- "normalized": false,
76
- "special": true
77
  },
78
  {
79
  "id": 65495,
 
50
  },
51
  {
52
  "id": 65492,
53
+ "content": "<|im_end|>",
54
  "single_word": false,
55
  "lstrip": false,
56
  "rstrip": false,
 
69
  {
70
  "id": 65494,
71
  "content": "</think>",
72
+ "single_word": true,
73
  "lstrip": false,
74
  "rstrip": false,
75
+ "normalized": true,
76
+ "special": false
77
  },
78
  {
79
  "id": 65495,
tokenizer_config.json CHANGED
@@ -41,7 +41,7 @@
41
  "special": true
42
  },
43
  "65492": {
44
- "content": "<|im_end>",
45
  "lstrip": false,
46
  "normalized": false,
47
  "rstrip": false,
@@ -59,10 +59,10 @@
59
  "65494": {
60
  "content": "</think>",
61
  "lstrip": false,
62
- "normalized": false,
63
  "rstrip": false,
64
- "single_word": false,
65
- "special": true
66
  },
67
  "65495": {
68
  "content": "source_1",
@@ -413,12 +413,9 @@
413
  "<image>",
414
  "</image>"
415
  ],
416
- "bos_token": "<|begin_of_text|>",
417
  "clean_up_tokenization_spaces": true,
418
  "eos_token": "<|end_of_text|>",
419
  "extra_special_tokens": {},
420
  "model_max_length": 1000000000000000019884624838656,
421
- "pad_token": "[PAD]",
422
- "tokenizer_class": "PreTrainedTokenizerFast",
423
- "unk_token": "[UNK]"
424
  }
 
41
  "special": true
42
  },
43
  "65492": {
44
+ "content": "<|im_end|>",
45
  "lstrip": false,
46
  "normalized": false,
47
  "rstrip": false,
 
59
  "65494": {
60
  "content": "</think>",
61
  "lstrip": false,
62
+ "normalized": true,
63
  "rstrip": false,
64
+ "single_word": true,
65
+ "special": false
66
  },
67
  "65495": {
68
  "content": "source_1",
 
413
  "<image>",
414
  "</image>"
415
  ],
 
416
  "clean_up_tokenization_spaces": true,
417
  "eos_token": "<|end_of_text|>",
418
  "extra_special_tokens": {},
419
  "model_max_length": 1000000000000000019884624838656,
420
+ "tokenizer_class": "PreTrainedTokenizerFast"
 
 
421
  }