| import os |
| import sys |
| import spaces |
| import torch |
| from unittest.mock import patch |
| from transformers import AutoModelForCausalLM, AutoTokenizer |
| from transformers.dynamic_module_utils import get_imports as _original_get_imports |
|
|
| |
| |
| def _strip_flash_attn(filename, *args, **kwargs): |
| """Strips flash_attn from model import list so FA2 is never enabled.""" |
| return [i for i in _original_get_imports(filename, *args, **kwargs) if i != "flash_attn"] |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| sys.modules["flash_attn"] = None |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| try: |
| import transformers.utils.import_utils as _tu |
| if not hasattr(_tu, "is_torch_fx_available"): |
| _tu.is_torch_fx_available = lambda: False |
| except Exception: |
| pass |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| model_id = "openbmb/MiniCPM4.1-8B" |
| hf_token = os.environ.get("HF_TOKEN") |
|
|
| _tokenizer = None |
| _model = None |
|
|
|
|
| def _patch_flash_attn_imports(filename, *args, **kwargs): |
| """ |
| Documented community fix: patch transformers' get_imports to strip |
| flash_attn from the model's required imports before from_pretrained runs. |
| This prevents MiniCPM4.1-8B's modeling_minicpm.py from forcing FA2 |
| regardless of attn_implementation setting. |
| Source: https://huggingface.co/openbmb/MiniCPM4.1-8B/discussions |
| """ |
| from transformers.dynamic_module_utils import get_imports as _orig_get_imports |
| imports = _orig_get_imports(filename, *args, **kwargs) |
| return [imp for imp in imports if imp != "flash_attn"] |
|
|
|
|
| def _ensure_loaded(): |
| """Thread-safe lazy loader. Does nothing after first successful load.""" |
| global _tokenizer, _model |
|
|
| if _model is not None: |
| return True |
|
|
| print("[Core Router] First request received — loading MiniCPM4.1-8B now...") |
| try: |
| _tokenizer = AutoTokenizer.from_pretrained( |
| model_id, |
| trust_remote_code=True, |
| token=hf_token |
| ) |
|
|
| |
| |
| |
| with patch( |
| "transformers.dynamic_module_utils.get_imports", |
| side_effect=_strip_flash_attn |
| ): |
| _model = AutoModelForCausalLM.from_pretrained( |
| model_id, |
| trust_remote_code=True, |
| torch_dtype="auto", |
| low_cpu_mem_usage=True, |
| attn_implementation="eager", |
| token=hf_token |
| ) |
|
|
| |
|
|
| print("[Core Router] MiniCPM4.1-8B loaded successfully!") |
| return True |
| except Exception as e: |
| print(f"[Core Router] Load failed: {e}") |
| _model = None |
| _tokenizer = None |
| return False |
|
|
|
|
| @spaces.GPU |
| def _core_router_inference(prompt: str) -> str: |
| """ |
| Runs MiniCPM4.1-8B inference on the ZeroGPU. |
| Lazy-loads the model on the first call. |
| """ |
| if not _ensure_loaded(): |
| return "Error: Core Router model could not be loaded. Please try again." |
|
|
| _model.to("cuda") |
| _model.eval() |
|
|
| try: |
| |
| prompt = "Hello, what is 2+2? Please answer clearly." |
| print("\n\n" + "="*50) |
| print("=== CHRISTIAN'S SMOKE TEST ===") |
| print(f"1. tokenizer.chat_template:\n{_tokenizer.chat_template}\n") |
| |
| |
| messages = [{"role": "user", "content": prompt}] |
| prompt_str = _tokenizer.apply_chat_template( |
| messages, |
| tokenize=False, |
| add_generation_prompt=True |
| ) |
| print(f"2. Decoded prompt before generation:\n{prompt_str}\n") |
| |
| |
| inputs = _tokenizer(prompt_str, return_tensors="pt").to("cuda") |
| print(f"3. Input length: {inputs.input_ids.shape}\n") |
| |
| |
| print("4. Starting Deterministic Generation...") |
| outputs = _model.generate( |
| **inputs, |
| max_new_tokens=128, |
| do_sample=False, |
| eos_token_id=_tokenizer.eos_token_id, |
| pad_token_id=_tokenizer.pad_token_id if _tokenizer.pad_token_id is not None else _tokenizer.eos_token_id, |
| repetition_penalty=1.05, |
| use_cache=False |
| ) |
| |
| |
| generated_ids = outputs[0][inputs.input_ids.shape[1]:] |
| print(f"5. Raw generated IDs:\n{generated_ids.tolist()}\n") |
| |
| |
| response = _tokenizer.decode(generated_ids, skip_special_tokens=True) |
| print(f"6. Decoded continuation only:\n{response}") |
| print("="*50 + "\n\n") |
| |
| return response.strip() |
| except Exception as e: |
| return f"Error during Core Router generation: {e}" |
|
|
|
|
| def process_workflow(user_text: str, raw_vision_text: str = None) -> str: |
| """ |
| The Main Flow Controller. |
| Orchestrates: Vision -> Parse -> Mellum -> Cohere -> VoxCPM2. |
| """ |
| system_context = ( |
| "You are the Core Router for the Family Bill Assistant. " |
| "You are a helpful, friendly AI that helps families understand their bills. " |
| "You can analyze receipts, split bills, and answer financial questions." |
| ) |
|
|
| if raw_vision_text: |
| prompt = ( |
| f"{system_context}\n\n" |
| f"The user uploaded a receipt. Here is the extracted text:\n" |
| f"{raw_vision_text}\n\n" |
| f"The user says: {user_text}\n\n" |
| f"Please analyze the receipt and respond helpfully." |
| ) |
| else: |
| prompt = ( |
| f"{system_context}\n\n" |
| f"The user says: {user_text}\n\n" |
| f"Please respond to their question." |
| ) |
|
|
| return _core_router_inference(prompt) |
|
|