Spaces:
Running on Zero
Running on Zero
| """Modal service: MiniCPM4.1-8B (the reasoning model) in its OWN environment. | |
| The 8B's trust_remote_code is written for transformers ~4.56 and RuntimeErrors | |
| on the transformers 5.12 that MiniCPM-V-4.6 forces on the Space. Modal isolates | |
| it: this container pins transformers <5.0, loads the 8B on a warm GPU, and | |
| exposes `Engine.generate` which the Space calls via the Modal SDK. | |
| Deploy: modal deploy modal_app.py | |
| Test: modal run modal_app.py::smoke | |
| """ | |
| import re | |
| import modal | |
| APP_NAME = "budgetbuddy-8b" | |
| MODEL_ID = "openbmb/MiniCPM4.1-8B" | |
| image = ( | |
| modal.Image.debian_slim(python_version="3.11") | |
| .pip_install( | |
| "torch", | |
| "transformers>=4.56,<5.0", | |
| "accelerate>=0.30", | |
| "sentencepiece", | |
| "numpy<2", | |
| ) | |
| ) | |
| app = modal.App(APP_NAME) | |
| hf_cache = modal.Volume.from_name("bb-hf-cache", create_if_missing=True) | |
| with image.imports(): | |
| import torch | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| _THINK_RE = re.compile(r"<think>.*?</think>", re.DOTALL | re.IGNORECASE) | |
| class Engine: | |
| def load_cpu(self): | |
| # Runs during snapshot creation (CPU only). The 16GB weight load is | |
| # captured in the snapshot, so later cold starts restore instead of reload. | |
| self.tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True) | |
| self.model = AutoModelForCausalLM.from_pretrained( | |
| MODEL_ID, torch_dtype="bfloat16", trust_remote_code=True | |
| ).eval() | |
| def to_gpu(self): | |
| self.model = self.model.to("cuda") | |
| def generate(self, messages, max_new_tokens: int = 512, | |
| enable_thinking: bool = False) -> str: | |
| try: | |
| inputs = self.tokenizer.apply_chat_template( | |
| messages, tokenize=True, add_generation_prompt=True, | |
| enable_thinking=enable_thinking, return_dict=True, return_tensors="pt", | |
| ).to("cuda") | |
| except TypeError: | |
| inputs = self.tokenizer.apply_chat_template( | |
| messages, tokenize=True, add_generation_prompt=True, | |
| return_dict=True, return_tensors="pt", | |
| ).to("cuda") | |
| with torch.no_grad(): | |
| out = self.model.generate( | |
| **inputs, max_new_tokens=max_new_tokens, do_sample=False | |
| ) | |
| text = self.tokenizer.decode( | |
| out[0][inputs["input_ids"].shape[-1]:], skip_special_tokens=True | |
| ) | |
| return _THINK_RE.sub("", str(text)).strip() | |
| def smoke(): | |
| r = Engine().generate.remote( | |
| messages=[{"role": "user", "content": "Reply with exactly: ok"}], | |
| max_new_tokens=12, | |
| ) | |
| print("SMOKE RESULT:", repr(r)) | |