Meteord commited on
Commit
170e8a9
·
verified ·
1 Parent(s): 2ccc07a

Sync from GitHub via hub-sync

Browse files
app/backend/adapter_registry.py CHANGED
@@ -23,10 +23,10 @@ ADAPTERS = {
23
  path=GEMMA_DIR / "checkpoints" / "gemma4_retrieval_adapter",
24
  description="Retrieval/tool-call adapter checkpoint used by the Gemma backend.",
25
  ),
26
- "tool_json": AdapterSpec(
27
- name="tool_json",
28
- path=REPO_ROOT / "models" / "gemma4-tool-lora-adapter",
29
- description="Adapter trained by training/trainer.py on llm_user_queries.jsonl.",
30
  ),
31
  }
32
 
 
23
  path=GEMMA_DIR / "checkpoints" / "gemma4_retrieval_adapter",
24
  description="Retrieval/tool-call adapter checkpoint used by the Gemma backend.",
25
  ),
26
+ "openui_translater": AdapterSpec(
27
+ name="openui_translater",
28
+ path=REPO_ROOT / "train" / "openui_lang" / "ouputs" / "openui-translate-mini-lora",
29
+ description="Adapter trained on openui_sft_train.jsonl for tool result - openui component translation.",
30
  ),
31
  }
32
 
app/backend/smolnalysis_model_wrapper.py ADDED
@@ -0,0 +1,351 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import importlib.util
4
+ import os
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import torch
10
+ from transformers import AutoModelForCausalLM, AutoTokenizer
11
+
12
+
13
+ REPO_ROOT = Path(__file__).resolve().parents[2]
14
+ ROUTER_SOURCE = REPO_ROOT / "train" / "router" / "router_mlp.py"
15
+ DEFAULT_ROUTER_OUTPUT_DIR = Path(
16
+ os.getenv("SMOLNALYSIS_ROUTER_OUTPUT_DIR", str(REPO_ROOT / "train" / "router" / "outputs" / "router-mlp"))
17
+ )
18
+ BASE_MODEL_ID = os.getenv("SMOLNALYSIS_MINICPM_TRANSFORMERS_MODEL_ID", os.getenv("MODEL_ID", "openbmb/MiniCPM5-1B"))
19
+ DEFAULT_OPENUI_TRANSLATOR_ADAPTER_PATH = Path(
20
+ os.getenv(
21
+ "SMOLNALYSIS_DEFAULT_OPENUI_TRANSLATOR_ADAPTER_PATH",
22
+ str(REPO_ROOT / "train" / "openui_lang" / "outputs" / "openui-sft-stats-components-lora"),
23
+ )
24
+ )
25
+ BASE_ADAPTER_NAMES = {"", "base", "none", "no_adapter", "no-adapter", "general", "general_agent"}
26
+ ROLE_ENV_KEYS = {
27
+ "general_agent": "GENERAL_AGENT",
28
+ "ckan_retrieval": "CKAN_RETRIEVAL",
29
+ "data_analysis": "DATA_ANALYSIS",
30
+ "openui_translator": "OPENUI_TRANSLATOR",
31
+ }
32
+ DEFAULT_LABEL_TO_ADAPTER = {
33
+ "general_agent": None,
34
+ "base": None,
35
+ "none": None,
36
+ "ckan": "ckan_retrieval",
37
+ "retrieval": "ckan_retrieval",
38
+ "ckan_retrieval": "ckan_retrieval",
39
+ "openui": "openui_translator",
40
+ "openui_translator": "openui_translator",
41
+ "analysis": "data_analysis",
42
+ "data_analysis": "data_analysis",
43
+ }
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class AdapterSource:
48
+ name: str
49
+ source: str
50
+ is_path: bool
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class RouterDecision:
55
+ role: str
56
+ adapter: str | None
57
+ confidence: float
58
+ logits: list[float]
59
+
60
+
61
+ def _clean_env_value(name: str, default: str = "") -> str:
62
+ raw = os.getenv(name, default)
63
+ lines = []
64
+ for line in str(raw).splitlines():
65
+ value = line.strip().strip('"').strip("'")
66
+ if value and not value.startswith("#"):
67
+ lines.append(value)
68
+ return lines[-1] if lines else default
69
+
70
+
71
+ def _role_env(role: str, suffix: str) -> str:
72
+ return f"SMOLNALYSIS_MINICPM_{ROLE_ENV_KEYS[role]}_{suffix}"
73
+
74
+
75
+ def _repo_path(path: str | Path) -> Path:
76
+ value = Path(path).expanduser()
77
+ return value if value.is_absolute() else REPO_ROOT / value
78
+
79
+
80
+ def _adapter_source_for_role(role: str) -> AdapterSource | None:
81
+ if role not in ROLE_ENV_KEYS:
82
+ return None
83
+
84
+ default_path = str(DEFAULT_OPENUI_TRANSLATOR_ADAPTER_PATH) if role == "openui_translator" else ""
85
+ adapter_path = _clean_env_value(_role_env(role, "ADAPTER_PATH"), _clean_env_value(_role_env(role, "LORA_PATH"), default_path))
86
+ adapter_repo_id = _clean_env_value(
87
+ _role_env(role, "ADAPTER_REPO_ID"),
88
+ _clean_env_value(_role_env(role, "LORA_REPO_ID"), ""),
89
+ )
90
+ if adapter_path:
91
+ return AdapterSource(role, adapter_path, True)
92
+ if adapter_repo_id:
93
+ return AdapterSource(role, adapter_repo_id, False)
94
+ return None
95
+
96
+
97
+ class SmolnalysisMoE(torch.nn.Module):
98
+ """Small inference wrapper for router-selected LoRA adapters."""
99
+
100
+ def __init__(
101
+ self,
102
+ model_base_name: str = BASE_MODEL_ID,
103
+ *,
104
+ task_router: torch.nn.Module | None = None,
105
+ router_labels: list[str] | None = None,
106
+ label_to_adapter: dict[str, str | None] | None = None,
107
+ load_in_4bit: bool = True,
108
+ load_task_router: bool = True,
109
+ router_output_dir: str | Path = DEFAULT_ROUTER_OUTPUT_DIR,
110
+ router_max_length: int = 512,
111
+ ) -> None:
112
+ super().__init__()
113
+ self.model_base_name = model_base_name
114
+ self.load_in_4bit = load_in_4bit
115
+ self.router_output_dir = _repo_path(router_output_dir)
116
+ self.router_max_length = router_max_length
117
+ self.task_router = task_router
118
+ self.router_labels = router_labels
119
+ self.label_to_adapter = {**DEFAULT_LABEL_TO_ADAPTER, **(label_to_adapter or {})}
120
+ self.loaded_adapters: set[str] = set()
121
+ self.active_adapter: str | None = None
122
+ self.last_router_decision: RouterDecision | None = None
123
+
124
+ if self.task_router is None and load_task_router:
125
+ self.task_router, loaded_labels = self.load_task_router(self.router_output_dir)
126
+ if self.router_labels is None:
127
+ self.router_labels = loaded_labels
128
+ if self.router_labels is None:
129
+ self.router_labels = ["general_agent", "ckan_retrieval", "openui_translator"]
130
+
131
+ self.tokenizer = AutoTokenizer.from_pretrained(model_base_name, trust_remote_code=True)
132
+ if self.tokenizer.pad_token is None:
133
+ self.tokenizer.pad_token = self.tokenizer.eos_token
134
+ self.model_base = self.load_model_base(model_base_name)
135
+ self.vocab_size = len(self.tokenizer)
136
+
137
+ @staticmethod
138
+ def load_task_router(output_dir: str | Path) -> tuple[torch.nn.Module, list[str]]:
139
+ output_dir = _repo_path(output_dir)
140
+ if not (output_dir / "router_mlp.pt").exists() or not (output_dir / "config.json").exists():
141
+ raise FileNotFoundError(f"Router artifacts are missing in {output_dir}")
142
+ spec = importlib.util.spec_from_file_location("smolnalysis_router_mlp", ROUTER_SOURCE)
143
+ if spec is None or spec.loader is None:
144
+ raise ImportError(f"Could not load router module from {ROUTER_SOURCE}")
145
+ module = importlib.util.module_from_spec(spec)
146
+ spec.loader.exec_module(module)
147
+ router, config = module.load_router_mlp(output_dir)
148
+ return router, list(config.labels)
149
+
150
+ def _build_quantization_config(self):
151
+ if not self.load_in_4bit:
152
+ return None
153
+
154
+ from transformers import BitsAndBytesConfig
155
+
156
+ return BitsAndBytesConfig(
157
+ load_in_4bit=True,
158
+ bnb_4bit_quant_type="nf4",
159
+ bnb_4bit_compute_dtype=torch.bfloat16,
160
+ bnb_4bit_use_double_quant=True,
161
+ )
162
+
163
+ def load_model_base(self, model_base_name: str):
164
+ quantization_config = self._build_quantization_config()
165
+ model = AutoModelForCausalLM.from_pretrained(
166
+ model_base_name,
167
+ trust_remote_code=True,
168
+ torch_dtype=torch.bfloat16,
169
+ device_map="auto" if quantization_config is not None else None,
170
+ quantization_config=quantization_config,
171
+ )
172
+ model.eval()
173
+ return model
174
+
175
+ def _normalize_messages(self, inputs: Any) -> list[dict[str, str]]:
176
+ if isinstance(inputs, str):
177
+ return [{"role": "user", "content": inputs}]
178
+ if isinstance(inputs, list):
179
+ return [{"role": str(message["role"]), "content": str(message["content"])} for message in inputs]
180
+ if isinstance(inputs, dict):
181
+ if "messages" in inputs:
182
+ return self._normalize_messages(inputs["messages"])
183
+ if "prompt" in inputs:
184
+ return [{"role": "user", "content": str(inputs["prompt"])}]
185
+ if "content" in inputs:
186
+ return [{"role": str(inputs.get("role", "user")), "content": str(inputs["content"])}]
187
+ raise TypeError("inputs must be tokenized features, a message list, a prompt string, or a dict with messages/prompt")
188
+
189
+ @staticmethod
190
+ def _latest_user_message(messages: list[dict[str, str]]) -> list[dict[str, str]]:
191
+ for message in reversed(messages):
192
+ if message.get("role") == "user" and message.get("content"):
193
+ return [{"role": "user", "content": message["content"]}]
194
+ return []
195
+
196
+ def _tokenize_messages(self, messages: list[dict[str, str]], *, max_length: int | None = None) -> dict[str, Any]:
197
+ tokenized = self.tokenizer.apply_chat_template(
198
+ messages,
199
+ tokenize=True,
200
+ add_generation_prompt=True,
201
+ return_dict=True,
202
+ return_tensors="pt",
203
+ )
204
+ if max_length is not None:
205
+ tokenized["input_ids"] = tokenized["input_ids"][:, -max_length:]
206
+ tokenized["attention_mask"] = tokenized["attention_mask"][:, -max_length:]
207
+ return tokenized
208
+
209
+ def _preprocess(self, inputs: Any) -> dict[str, Any]:
210
+ messages = self._normalize_messages(inputs)
211
+ latest_user_messages = self._latest_user_message(messages)
212
+ router_messages = latest_user_messages or messages
213
+ return {
214
+ "messages": messages,
215
+ "latest_user_messages": latest_user_messages,
216
+ "router_inputs": self._tokenize_messages(router_messages, max_length=self.router_max_length),
217
+ }
218
+
219
+ @staticmethod
220
+ def _is_preprocessed(inputs: Any) -> bool:
221
+ return isinstance(inputs, dict) and ("input_ids" in inputs or "router_inputs" in inputs)
222
+
223
+ def _adapter_from_label(self, label: str | None) -> str | None:
224
+ adapter_name = self.label_to_adapter.get((label or "").strip().casefold(), label)
225
+ if adapter_name is None:
226
+ return None
227
+ adapter_name = adapter_name.strip().casefold()
228
+ return None if adapter_name in BASE_ADAPTER_NAMES else adapter_name
229
+
230
+ def adapter_source_for_role(self, role: str | None) -> AdapterSource | None:
231
+ adapter_name = self._adapter_from_label(role)
232
+ return _adapter_source_for_role(adapter_name) if adapter_name else None
233
+
234
+ def route(self, inputs: Any) -> tuple[dict[str, Any], RouterDecision | None]:
235
+ preprocessed = inputs if self._is_preprocessed(inputs) else self._preprocess(inputs)
236
+ self.route_adapter(preprocessed)
237
+ return preprocessed, self.last_router_decision
238
+
239
+ def route_adapter(self, preprocessed: dict[str, Any]) -> str | None:
240
+ if self.task_router is None:
241
+ self.last_router_decision = None
242
+ return None
243
+
244
+ router_features = preprocessed.get("router_inputs", preprocessed)
245
+ try:
246
+ router_device = next(self.task_router.parameters()).device
247
+ except StopIteration:
248
+ router_device = torch.device("cpu")
249
+ router_inputs = {
250
+ key: value.to(router_device) if torch.is_tensor(value) else value
251
+ for key, value in router_features.items()
252
+ if key in {"input_ids", "attention_mask"}
253
+ }
254
+ with torch.inference_mode():
255
+ output = self.task_router(**router_inputs)
256
+ logits = output["logits"] if isinstance(output, dict) else output.logits
257
+ probabilities = torch.softmax(logits, dim=-1)[0]
258
+ label_index = int(logits.argmax(dim=-1).item())
259
+ label = self.router_labels[label_index]
260
+ adapter = self._adapter_from_label(label)
261
+ self.last_router_decision = RouterDecision(
262
+ role=label,
263
+ adapter=adapter,
264
+ confidence=float(probabilities[label_index].item()),
265
+ logits=[float(value) for value in logits[0].detach().cpu().tolist()],
266
+ )
267
+ return adapter
268
+
269
+ def set_adapter(self, adapter_name: str | None) -> None:
270
+ adapter_name = self._adapter_from_label(adapter_name)
271
+ if adapter_name is None:
272
+ self.active_adapter = None
273
+ self.model_base.eval()
274
+ return
275
+
276
+ adapter = _adapter_source_for_role(adapter_name)
277
+ if adapter is None:
278
+ raise KeyError(f"No MiniCPM adapter source configured for role '{adapter_name}'")
279
+ if adapter.is_path:
280
+ adapter_path = Path(adapter.source).expanduser()
281
+ if not adapter_path.exists():
282
+ raise FileNotFoundError(f"MiniCPM adapter path does not exist for role '{adapter.name}': {adapter_path}")
283
+ adapter_source = str(adapter_path)
284
+ else:
285
+ adapter_source = adapter.source
286
+
287
+ from peft import PeftModel
288
+
289
+ if not self.loaded_adapters:
290
+ self.model_base = PeftModel.from_pretrained(self.model_base, adapter_source, adapter_name=adapter.name)
291
+ self.loaded_adapters.add(adapter.name)
292
+ elif adapter.name not in self.loaded_adapters:
293
+ self.model_base.load_adapter(adapter_source, adapter_name=adapter.name)
294
+ self.loaded_adapters.add(adapter.name)
295
+
296
+ self.model_base.set_adapter(adapter.name)
297
+ self.model_base.eval()
298
+ self.active_adapter = adapter.name
299
+
300
+ def _generation_inputs(self, preprocessed: dict[str, Any]) -> dict[str, Any]:
301
+ if self.active_adapter is None:
302
+ messages = preprocessed.get("messages") or preprocessed.get("latest_user_messages") or []
303
+ else:
304
+ messages = preprocessed.get("latest_user_messages") or preprocessed.get("messages") or []
305
+ return self._tokenize_messages(messages)
306
+
307
+ def forward(
308
+ self,
309
+ inputs: Any,
310
+ *,
311
+ max_new_tokens: int = 512,
312
+ temperature: float = 0.7,
313
+ top_p: float = 0.95,
314
+ top_k: int = 64,
315
+ adapter: str | None = "auto",
316
+ ):
317
+ already_tokenized = isinstance(inputs, dict) and "input_ids" in inputs
318
+ preprocessed = inputs if self._is_preprocessed(inputs) else self._preprocess(inputs)
319
+ requested_adapter = (adapter or "auto").strip().casefold() if isinstance(adapter, str) else adapter
320
+ selected_adapter = self._adapter_from_label(adapter)
321
+ if requested_adapter in {None, "auto", "router"}:
322
+ selected_adapter = self.route_adapter(preprocessed)
323
+ self.set_adapter(selected_adapter)
324
+
325
+ model_inputs = preprocessed if already_tokenized else self._generation_inputs(preprocessed)
326
+ device = next(self.model_base.parameters()).device
327
+ model_inputs = {key: value.to(device) if torch.is_tensor(value) else value for key, value in model_inputs.items()}
328
+ input_tokens = int(model_inputs["input_ids"].shape[-1])
329
+ generation_kwargs: dict[str, Any] = {
330
+ **model_inputs,
331
+ "max_new_tokens": max_new_tokens,
332
+ "do_sample": temperature > 0,
333
+ "top_p": top_p,
334
+ "pad_token_id": self.tokenizer.eos_token_id,
335
+ }
336
+ if temperature > 0:
337
+ generation_kwargs["temperature"] = temperature
338
+ generation_kwargs["top_k"] = top_k
339
+
340
+ with torch.inference_mode():
341
+ use_base = self.active_adapter is None and hasattr(self.model_base, "disable_adapter")
342
+ if use_base:
343
+ with self.model_base.disable_adapter():
344
+ outputs = self.model_base.generate(**generation_kwargs)
345
+ else:
346
+ outputs = self.model_base.generate(**generation_kwargs)
347
+ return outputs[:, input_tokens:]
348
+
349
+ def generate_text(self, inputs: Any, **generation_kwargs: Any) -> str:
350
+ output_ids = self.forward(inputs, **generation_kwargs)
351
+ return self.tokenizer.decode(output_ids[0], skip_special_tokens=True).strip()
app/router_test_interface.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from typing import Any
5
+
6
+ try:
7
+ from .backend.smolnalysis_model_wrapper import SmolnalysisMoE
8
+ except ImportError:
9
+ from backend.smolnalysis_model_wrapper import SmolnalysisMoE
10
+
11
+
12
+ def inspect_prompt(
13
+ wrapper_model: SmolnalysisMoE,
14
+ prompt: str,
15
+ *,
16
+ generate: bool = False,
17
+ max_new_tokens: int = 128,
18
+ ) -> str:
19
+ text = prompt.strip()
20
+ if not text:
21
+ return "Enter a user message."
22
+
23
+ messages = [{"role": "user", "content": text}]
24
+ preprocessed, decision = wrapper_model.route(messages)
25
+ selected_adapter = decision.adapter if decision else None
26
+ role = decision.role if decision else "general_agent"
27
+ adapter_source = wrapper_model.adapter_source_for_role(selected_adapter or role)
28
+
29
+ lines = [
30
+ "model_wrapper: loaded",
31
+ f"base_model: {wrapper_model.model_base_name}",
32
+ f"router_output_dir: {wrapper_model.router_output_dir}",
33
+ ]
34
+ if decision is None:
35
+ lines.append("router_prediction: none")
36
+ else:
37
+ lines.extend(
38
+ [
39
+ f"router_prediction: {decision.role}",
40
+ f"router_confidence: {decision.confidence:.3f}",
41
+ f"router_logits: {[round(value, 3) for value in decision.logits]}",
42
+ ]
43
+ )
44
+
45
+ lines.append(f"selected_adapter: {selected_adapter or 'base'}")
46
+ if adapter_source is None:
47
+ lines.append("adapter_source: base/no adapter")
48
+ else:
49
+ source_type = "path" if adapter_source.is_path else "repo"
50
+ lines.append(f"adapter_source: {source_type}:{adapter_source.source}")
51
+
52
+ if generate:
53
+ try:
54
+ output = wrapper_model.generate_text(
55
+ preprocessed,
56
+ adapter="auto",
57
+ max_new_tokens=max_new_tokens,
58
+ temperature=0.0,
59
+ )
60
+ lines.extend(["", "model_output:", output])
61
+ except Exception as exc:
62
+ lines.append(f"model_error: {type(exc).__name__}: {exc}")
63
+ else:
64
+ lines.append("generation: disabled")
65
+ return "\n".join(lines)
66
+
67
+
68
+ def run_cli(wrapper_model: SmolnalysisMoE, *, generate: bool, max_new_tokens: int) -> None:
69
+ mode = "route + generate" if generate else "route only"
70
+ print(f"Model-wrapper router test ({mode}). Type /exit to quit.\n")
71
+ while True:
72
+ prompt = input("user> ").strip()
73
+ if prompt.lower() in {"/exit", "exit", "quit", "/quit"}:
74
+ return
75
+ print(inspect_prompt(wrapper_model, prompt, generate=generate, max_new_tokens=max_new_tokens))
76
+ print()
77
+
78
+
79
+ def run_web(
80
+ wrapper_model: SmolnalysisMoE,
81
+ host: str,
82
+ port: int | None,
83
+ *,
84
+ generate: bool,
85
+ max_new_tokens: int,
86
+ ) -> Any:
87
+ import gradio as gr
88
+
89
+ def inspect(prompt: str) -> str:
90
+ return inspect_prompt(wrapper_model, prompt, generate=generate, max_new_tokens=max_new_tokens)
91
+
92
+ demo = gr.Interface(
93
+ fn=inspect,
94
+ inputs=gr.Textbox(label="User message", lines=4),
95
+ outputs=gr.Textbox(label="Model-wrapper router decision", lines=16),
96
+ title="smolnalysis model-wrapper router test",
97
+ allow_flagging="never",
98
+ )
99
+ return demo.launch(server_name=host, server_port=port)
100
+
101
+
102
+ def parse_args() -> argparse.Namespace:
103
+ parser = argparse.ArgumentParser(description="Interactively inspect routing through SmolnalysisMoE only.")
104
+ parser.add_argument("--web", action="store_true", help="Launch a tiny Gradio UI instead of the terminal prompt.")
105
+ parser.add_argument("--generate", action="store_true", help="Also generate through SmolnalysisMoE after routing.")
106
+ parser.add_argument("--max-new-tokens", type=int, default=128, help="Maximum generated tokens for --generate.")
107
+ parser.add_argument("--host", default="127.0.0.1", help="Host for --web.")
108
+ parser.add_argument("--port", type=int, default=None, help="Port for --web.")
109
+ parser.add_argument("--load-in-4bit", action=argparse.BooleanOptionalAction, default=True)
110
+ return parser.parse_args()
111
+
112
+
113
+ def main() -> None:
114
+ args = parse_args()
115
+ wrapper_model = SmolnalysisMoE(load_in_4bit=args.load_in_4bit)
116
+ if args.web:
117
+ run_web(wrapper_model, args.host, args.port, generate=args.generate, max_new_tokens=args.max_new_tokens)
118
+ else:
119
+ run_cli(wrapper_model, generate=args.generate, max_new_tokens=args.max_new_tokens)
120
+
121
+
122
+ if __name__ == "__main__":
123
+ main()