chikamov1 commited on
Commit
32143c7
Β·
1 Parent(s): c169cca

Fix: Corrected model loading on startup in FastAPI app

Browse files
Files changed (5) hide show
  1. .dockerignore +6 -0
  2. Dockerfile +17 -0
  3. app.py +9 -0
  4. requirements.txt +16 -0
  5. translation_api.py +161 -0
.dockerignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ .git/
4
+ models/
5
+ *.safetensors
6
+ *.bin
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ # Set working directory
4
+ WORKDIR /app
5
+
6
+ # Copy files
7
+ COPY . .
8
+
9
+ # Install dependencies
10
+ RUN pip install --no-cache-dir --upgrade pip && \
11
+ pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Expose the default port used by Hugging Face Docker Spaces
14
+ EXPOSE 7860
15
+
16
+ # Start the FastAPI app using Uvicorn
17
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # This imports the FastAPI app
2
+ from translation_api import app
3
+
4
+ # If needed, you can add any root route
5
+ @app.get("/")
6
+ def read_root():
7
+ return {"message": "Welcome to ChikaMo Translator API"}
8
+
9
+
requirements.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ transformers==4.41.2
2
+ datasets==2.19.1
3
+ torch>=2.2.0
4
+ fastapi==0.111.0
5
+ uvicorn==0.30.0
6
+ huggingface_hub
7
+
8
+ # googletrans==4.0.0-rc1
9
+ # ❌ Conflict
10
+ deep-translator==1.11.4
11
+ #βœ… Modern replacement
12
+
13
+ python-dotenv==1.0.1
14
+ tqdm==4.66.4
15
+ scikit-learn==1.4.2
16
+ rich==13.7.
translation_api.py ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # translate_api.py
2
+ # Defines the FastAPI application for the Chiakamo Translator.
3
+
4
+ from fastapi import FastAPI, HTTPException
5
+ from pydantic import BaseModel
6
+ from transformers import MarianMTModel, MarianTokenizer, pipeline
7
+ import torch
8
+ import os # Import os for environment variables (e.g., HF_HOME)
9
+
10
+ # Initialize the FastAPI application
11
+ app = FastAPI(title="ChikaMo Translator API",
12
+ description="API for translating between Tagalog and English, with fallback to Helsinki-NLP models.")
13
+
14
+ # --- Model Configuration ---
15
+ # Define the Hugging Face model repository IDs for your custom models.
16
+ # These should be your repositories on Hugging Face Hub (e.g., chikamov1/opus-mt-tl-en-chikamo)
17
+ LOCAL_MODELS = {
18
+ "tl-en": "chikamov1/opus-mt-tl-en-chikamo", # Your custom Tagalog-to-English model
19
+ "en-tl": "chikamov1/opus-mt-en-tl-chikamo", # Your custom English-to-Tagalog model
20
+ }
21
+
22
+ # Define HuggingFace fallback models (Helsinki-NLP is a good choice)
23
+ FALLBACK_MODELS = {
24
+ "tl-en": "Helsinki-NLP/opus-mt-tl-en",
25
+ "en-tl": "Helsinki-NLP/opus-mt-en-tl",
26
+ }
27
+
28
+ # Dictionaries to store loaded models and tokenizers to avoid reloading on every request
29
+ loaded_models = {}
30
+ loaded_tokenizers = {}
31
+ fallback_pipelines = {}
32
+
33
+ # --- Model Loading Functions ---
34
+
35
+ # Function to get custom model and tokenizer (loads and caches)
36
+ def get_model_and_tokenizer(pair: str):
37
+ """
38
+ Loads and caches the custom MarianMT model and tokenizer for a given language pair.
39
+ """
40
+ if pair in LOCAL_MODELS:
41
+ if pair not in loaded_models:
42
+ print(f"Attempting to load local model: {LOCAL_MODELS[pair]}")
43
+ try:
44
+ # Load model and tokenizer from your Hugging Face Hub repository
45
+ model = MarianMTModel.from_pretrained(LOCAL_MODELS[pair])
46
+ tokenizer = MarianTokenizer.from_pretrained(LOCAL_MODELS[pair])
47
+ loaded_models[pair] = model
48
+ loaded_tokenizers[pair] = tokenizer
49
+ print(f"Successfully loaded local model: {LOCAL_MODELS[pair]}")
50
+ except Exception as e:
51
+ print(f"Failed to load local model {LOCAL_MODELS[pair]}: {e}")
52
+ return None, None # Return None if loading fails
53
+ return loaded_models.get(pair), loaded_tokenizers.get(pair)
54
+ return None, None
55
+
56
+ # Function to get fallback pipeline (loads and caches)
57
+ def get_fallback_pipeline(pair: str):
58
+ """
59
+ Loads and caches a fallback translation pipeline for a given language pair.
60
+ """
61
+ if pair not in fallback_pipelines:
62
+ print(f"Attempting to load fallback model: {FALLBACK_MODELS[pair]}")
63
+ try:
64
+ # Use the pipeline abstraction for fallback models
65
+ pipe = pipeline("translation", model=FALLBACK_MODELS[pair])
66
+ fallback_pipelines[pair] = pipe
67
+ print(f"Successfully loaded fallback model: {FALLBACK_MODELS[pair]}")
68
+ except Exception as e:
69
+ print(f"Failed to load fallback model {FALLBACK_MODELS[pair]}: {e}")
70
+ return None # Return None if loading fails
71
+ return fallback_pipelines.get(pair)
72
+
73
+ # --- Pydantic Model for Request Body ---
74
+ class TranslationRequest(BaseModel):
75
+ """
76
+ Defines the structure of the incoming JSON request for translation.
77
+ """
78
+ source_lang: str # e.g., "tl" for Tagalog, "en" for English
79
+ target_lang: str # e.g., "en" for English, "tl" for Tagalog
80
+ text: str # The text to be translated
81
+
82
+ # --- API Endpoints ---
83
+
84
+ # Root endpoint for basic API information
85
+ @app.get("/")
86
+ def read_root():
87
+ """
88
+ Returns a welcome message and API status.
89
+ """
90
+ return {"message": "Welcome to ChikaMo Translator API", "status": "running"}
91
+
92
+ # Translation endpoint
93
+ @app.post("/translate")
94
+ def translate_text_endpoint(req: TranslationRequest):
95
+ """
96
+ Translates text between specified source and target languages.
97
+ Prioritizes custom models, falls back to Helsinki-NLP if custom fails or is not found.
98
+ """
99
+ pair = f"{req.source_lang.lower()}-{req.target_lang.lower()}"
100
+ text = req.text.strip()
101
+
102
+ if not text:
103
+ raise HTTPException(status_code=400, detail="Input text is empty.")
104
+
105
+ translated_output = ""
106
+ fallback_used = False
107
+
108
+ # Try to use local (custom) model first
109
+ model, tokenizer = get_model_and_tokenizer(pair)
110
+ if model and tokenizer:
111
+ try:
112
+ inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True)
113
+ # Ensure model is on CPU if no GPU is available, or move to GPU if present
114
+ device = "cuda" if torch.cuda.is_available() else "cpu"
115
+ model.to(device)
116
+ inputs = {k: v.to(device) for k, v in inputs.items()}
117
+
118
+ with torch.no_grad():
119
+ translated = model.generate(**inputs)
120
+ translated_output = tokenizer.decode(translated[0], skip_special_tokens=True)
121
+ fallback_used = False
122
+ except Exception as e:
123
+ print(f"Error during custom model translation for pair {pair}: {e}. Attempting fallback.")
124
+ # Clear to force fallback path
125
+ model, tokenizer = None, None
126
+
127
+ # If custom model failed or wasn't available, try fallback
128
+ if not model and pair in FALLBACK_MODELS:
129
+ pipe = get_fallback_pipeline(pair)
130
+ if pipe:
131
+ try:
132
+ translated_output = pipe(text)[0]["translation_text"]
133
+ fallback_used = True
134
+ except Exception as e:
135
+ print(f"Error during fallback model translation for pair {pair}: {e}.")
136
+ raise HTTPException(status_code=500, detail=f"Translation failed for pair {pair} with both custom and fallback models.")
137
+ else:
138
+ raise HTTPException(status_code=500, detail=f"Fallback model for pair {pair} could not be loaded.")
139
+ elif not model: # No custom model and no fallback defined
140
+ raise HTTPException(status_code=400, detail=f"Unsupported language pair: {pair}, and no fallback model configured.")
141
+
142
+
143
+ return {
144
+ "translation": translated_output,
145
+ "source_lang": req.source_lang,
146
+ "target_lang": req.target_lang,
147
+ "fallback_used": fallback_used
148
+ }
149
+
150
+ # --- Application Startup Event ---
151
+ # This ensures models are loaded when the FastAPI app starts up.
152
+ # This is crucial for Hugging Face Spaces where the app is started by Uvicorn.
153
+ @app.on_event("startup")
154
+ async def startup_event():
155
+ # Pre-load all local and fallback models during startup
156
+ print("Pre-loading models during application startup...")
157
+ for pair in LOCAL_MODELS.keys():
158
+ get_model_and_tokenizer(pair) # Attempt to load custom models
159
+ for pair in FALLBACK_MODELS.keys():
160
+ get_fallback_pipeline(pair) # Attempt to load fallback pipelines
161
+ print("Model pre-loading complete.")