ImageStudio Maintainer Claude Opus 4.8 (1M context) commited on
Commit
c5fc943
·
1 Parent(s): c4415be

feat: switch assistant VLM Qwen3.5-4B -> gemma-4-E4B-it-Uncensored-MAX

Browse files

Comment out the Qwen model (kept for revert) and load prithivMLmods/
gemma-4-E4B-it-Uncensored-MAX (Gemma4ForConditionalGeneration, multimodal).
Make the eos-token resolver model-agnostic: union the model's own
generation_config eos, the tokenizer eos, and known turn terminators across
families (Gemma <turn|>, Qwen <|im_end|>, Llama <|eot_id|>), skipping <unk>.
Gemma ships a generation_config so it stops natively; the union keeps the
manual-pin safety for models that don't. Update assistant UI copy; Gemma has
no thinking mode so the Reasoning toggle is now a documented no-op.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +41 -18
app.py CHANGED
@@ -140,14 +140,17 @@ noobxl_pipe.to("cuda")
140
  print("Pipelines loaded!")
141
 
142
  # -----------------------------------------------------------------------------
143
- # Qwen3.5-4B multimodal assistant. Powers the "Prompt Assistant" tab: one-turn
144
- # question answering with an optional image, for turning rough ideas into rich
145
- # prompts or describing a reference image. Loaded once, bf16 (~9-10 GB) — fits
146
- # alongside the two diffusion pipelines on the shared GPU.
147
  # -----------------------------------------------------------------------------
148
- VLM_MODEL_ID = "rodrigomt/Qwen3.5-4B-Uncensored-Aggressive"
 
 
 
149
 
150
- print("Loading Qwen3.5-4B assistant...")
151
  vlm_processor = AutoProcessor.from_pretrained(
152
  VLM_MODEL_ID, token=HF_TOKEN, trust_remote_code=True
153
  )
@@ -159,17 +162,37 @@ vlm_model = AutoModelForImageTextToText.from_pretrained(
159
  )
160
  vlm_model.to("cuda").eval()
161
 
162
- # This finetune ships NO generation_config.json and its config carries no
163
- # eos_token_id, so generate() has no stop token and runs to max_new_tokens
164
- # (endless output). Pin the stop tokens explicitly from the tokenizer: the chat
165
- # template ends each assistant turn with <|im_end|>, so that's the real
166
- # terminator (plus <|endoftext|> as a fallback).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  _vlm_tokenizer = getattr(vlm_processor, "tokenizer", vlm_processor)
168
- _VLM_EOS_IDS = sorted({
169
- tid for tok in ("<|im_end|>", "<|endoftext|>")
170
- for tid in (_vlm_tokenizer.convert_tokens_to_ids(tok),)
171
- if isinstance(tid, int) and tid >= 0
172
- } | ({_vlm_tokenizer.eos_token_id} if _vlm_tokenizer.eos_token_id is not None else set()))
173
  _VLM_PAD_ID = (
174
  _vlm_tokenizer.pad_token_id
175
  if _vlm_tokenizer.pad_token_id is not None
@@ -1306,7 +1329,7 @@ with gr.Blocks(fill_height=True) as demo:
1306
 
1307
  with gr.Tab("💬 Prompt Assistant"):
1308
  gr.Markdown(
1309
- "Ask **Qwen3.5-4B** a single question — with or without an image. "
1310
  "Turn a rough idea into a rich prompt, or upload a reference image and "
1311
  "ask the model to describe it as a prompt."
1312
  )
@@ -1327,7 +1350,7 @@ with gr.Blocks(fill_height=True) as demo:
1327
  choices=["Off", "On"],
1328
  value="Off",
1329
  label="🧠 Reasoning",
1330
- info="Off: direct answer, best for prompts • On: think step-by-step first (slower, raise max tokens)",
1331
  )
1332
  with gr.Accordion("⚙️ Settings", open=False):
1333
  vlm_max_tokens = gr.Slider(
 
140
  print("Pipelines loaded!")
141
 
142
  # -----------------------------------------------------------------------------
143
+ # Multimodal assistant. Powers the "Prompt Assistant" tab: one-turn question
144
+ # answering with an optional image, for turning rough ideas into rich prompts or
145
+ # describing a reference image. Loaded once, bf16 — fits alongside the two
146
+ # diffusion pipelines on the shared GPU.
147
  # -----------------------------------------------------------------------------
148
+ # Previous model (Qwen3.5-4B) — kept for easy revert. It shipped no
149
+ # generation_config.json (needed manual eos pinning, see _resolve_vlm_eos_ids).
150
+ # VLM_MODEL_ID = "rodrigomt/Qwen3.5-4B-Uncensored-Aggressive"
151
+ VLM_MODEL_ID = "prithivMLmods/gemma-4-E4B-it-Uncensored-MAX"
152
 
153
+ print(f"Loading assistant: {VLM_MODEL_ID} ...")
154
  vlm_processor = AutoProcessor.from_pretrained(
155
  VLM_MODEL_ID, token=HF_TOKEN, trust_remote_code=True
156
  )
 
162
  )
163
  vlm_model.to("cuda").eval()
164
 
165
+
166
+ def _resolve_vlm_eos_ids(tokenizer, model):
167
+ """Collect every stop token id for the active VLM, model-agnostically.
168
+
169
+ generate() stops on these. We union three sources so a model swap can't
170
+ silently lose its terminator: (1) the model's own generation_config (correct
171
+ for models that ship one, e.g. Gemma's <turn|>/<eos>), (2) the tokenizer's
172
+ eos, and (3) known chat-turn terminators across families (Gemma's <turn|>,
173
+ Qwen's <|im_end|>, Llama's <|eot_id|>). Tokens absent from the vocab resolve
174
+ to <unk> and are skipped. This is what stops a model like the old Qwen
175
+ finetune (no generation_config) from rambling to max_new_tokens.
176
+ """
177
+ ids = set()
178
+ gc = getattr(model, "generation_config", None)
179
+ gc_eos = getattr(gc, "eos_token_id", None) if gc is not None else None
180
+ if isinstance(gc_eos, int):
181
+ ids.add(gc_eos)
182
+ elif isinstance(gc_eos, (list, tuple)):
183
+ ids.update(int(x) for x in gc_eos)
184
+ if tokenizer.eos_token_id is not None:
185
+ ids.add(int(tokenizer.eos_token_id))
186
+ unk = tokenizer.unk_token_id
187
+ for t in ("<turn|>", "<end_of_turn>", "<|im_end|>", "<|eot_id|>", "<|endoftext|>"):
188
+ tid = tokenizer.convert_tokens_to_ids(t)
189
+ if isinstance(tid, int) and tid >= 0 and tid != unk:
190
+ ids.add(tid)
191
+ return sorted(ids)
192
+
193
+
194
  _vlm_tokenizer = getattr(vlm_processor, "tokenizer", vlm_processor)
195
+ _VLM_EOS_IDS = _resolve_vlm_eos_ids(_vlm_tokenizer, vlm_model)
 
 
 
 
196
  _VLM_PAD_ID = (
197
  _vlm_tokenizer.pad_token_id
198
  if _vlm_tokenizer.pad_token_id is not None
 
1329
 
1330
  with gr.Tab("💬 Prompt Assistant"):
1331
  gr.Markdown(
1332
+ "Ask **Gemma 4 E4B** a single question — with or without an image. "
1333
  "Turn a rough idea into a rich prompt, or upload a reference image and "
1334
  "ask the model to describe it as a prompt."
1335
  )
 
1350
  choices=["Off", "On"],
1351
  value="Off",
1352
  label="🧠 Reasoning",
1353
+ info="Direct answers are best for prompts. (Reasoning mode only applies to thinking-capable models; Gemma 4 answers directly.)",
1354
  )
1355
  with gr.Accordion("⚙️ Settings", open=False):
1356
  vlm_max_tokens = gr.Slider(