Commit
·
528f18a
1
Parent(s):
6505ee1
Add application file
Browse files- Dockerfile +34 -0
- README.md +2 -2
- app.py +85 -0
- requirements.txt +5 -0
Dockerfile
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Use the official Python base image
|
2 |
+
FROM python:3.9-slim
|
3 |
+
|
4 |
+
# Set environment variables for predictable container behavior
|
5 |
+
ENV PYTHONDONTWRITEBYTECODE 1
|
6 |
+
ENV PYTHONUNBUFFERED 1
|
7 |
+
|
8 |
+
# Set a working directory in the container
|
9 |
+
WORKDIR /app
|
10 |
+
|
11 |
+
# Install system dependencies required for SentencePiece
|
12 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
13 |
+
build-essential \
|
14 |
+
cmake \
|
15 |
+
libprotobuf-dev \
|
16 |
+
protobuf-compiler \
|
17 |
+
libsentencepiece-dev \
|
18 |
+
libsentencepiece0 \
|
19 |
+
&& rm -rf /var/lib/apt/lists/*
|
20 |
+
|
21 |
+
# Copy the requirements file
|
22 |
+
COPY requirements.txt /app/
|
23 |
+
|
24 |
+
# Install Python dependencies
|
25 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
26 |
+
|
27 |
+
# Copy the app source code into the container
|
28 |
+
COPY app.py /app/
|
29 |
+
|
30 |
+
# Expose the port the app runs on
|
31 |
+
EXPOSE 7860
|
32 |
+
|
33 |
+
# Run the application
|
34 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
---
|
2 |
title: QnIA Translator API
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
colorTo: indigo
|
6 |
sdk: docker
|
7 |
pinned: false
|
|
|
1 |
---
|
2 |
title: QnIA Translator API
|
3 |
+
emoji: 🦙🤖
|
4 |
+
colorFrom: purple
|
5 |
colorTo: indigo
|
6 |
sdk: docker
|
7 |
pinned: false
|
app.py
ADDED
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import uvicorn
|
3 |
+
from typing import Dict
|
4 |
+
from fastapi import FastAPI
|
5 |
+
from transformers import NllbTokenizer, AutoModelForSeq2SeqLM
|
6 |
+
|
7 |
+
app = FastAPI()
|
8 |
+
|
9 |
+
def fix_tokenizer(tokenizer, new_lang='quz_Latn'):
|
10 |
+
"""
|
11 |
+
Add a new language token to the tokenizer vocabulary and update language mappings.
|
12 |
+
"""
|
13 |
+
# First ensure we're working with an NLLB tokenizer
|
14 |
+
if not hasattr(tokenizer, 'sp_model'):
|
15 |
+
raise ValueError("This function expects an NLLB tokenizer")
|
16 |
+
|
17 |
+
# Add the new language token if it's not already present
|
18 |
+
if new_lang not in tokenizer.additional_special_tokens:
|
19 |
+
tokenizer.add_special_tokens({
|
20 |
+
'additional_special_tokens': [new_lang]
|
21 |
+
})
|
22 |
+
|
23 |
+
# Initialize lang_code_to_id if it doesn't exist
|
24 |
+
if not hasattr(tokenizer, 'lang_code_to_id'):
|
25 |
+
tokenizer.lang_code_to_id = {}
|
26 |
+
|
27 |
+
# Add the new language to lang_code_to_id mapping
|
28 |
+
if new_lang not in tokenizer.lang_code_to_id:
|
29 |
+
# Get the ID for the new language token
|
30 |
+
new_lang_id = tokenizer.convert_tokens_to_ids(new_lang)
|
31 |
+
tokenizer.lang_code_to_id[new_lang] = new_lang_id
|
32 |
+
|
33 |
+
# Initialize id_to_lang_code if it doesn't exist
|
34 |
+
if not hasattr(tokenizer, 'id_to_lang_code'):
|
35 |
+
tokenizer.id_to_lang_code = {}
|
36 |
+
|
37 |
+
# Update the reverse mapping
|
38 |
+
tokenizer.id_to_lang_code[tokenizer.lang_code_to_id[new_lang]] = new_lang
|
39 |
+
|
40 |
+
return tokenizer
|
41 |
+
|
42 |
+
|
43 |
+
MODEL_URL = "pollitoconpapass/QnIA-translation-model"
|
44 |
+
model = AutoModelForSeq2SeqLM.from_pretrained(MODEL_URL)
|
45 |
+
tokenizer = NllbTokenizer.from_pretrained(MODEL_URL)
|
46 |
+
fix_tokenizer(tokenizer)
|
47 |
+
|
48 |
+
|
49 |
+
# === HEALTH CHECK ===
|
50 |
+
@app.get("/_health")
|
51 |
+
async def health_check():
|
52 |
+
return {'status': 'ok'}
|
53 |
+
|
54 |
+
|
55 |
+
# === TRANSLATION ===
|
56 |
+
@app.post("/qnia-translate")
|
57 |
+
async def translate(data: Dict, a=32, b=3, max_input_length=1024, num_beams=4):
|
58 |
+
start = time.time()
|
59 |
+
|
60 |
+
text = data['text']
|
61 |
+
src_lang = data['src_lang']
|
62 |
+
tgt_lang = data['tgt_lang']
|
63 |
+
|
64 |
+
tokenizer.src_lang = src_lang
|
65 |
+
tokenizer.tgt_lang = tgt_lang
|
66 |
+
inputs = tokenizer(text, return_tensors='pt', padding=True, truncation=True, max_length=max_input_length)
|
67 |
+
|
68 |
+
result = model.generate(
|
69 |
+
**inputs.to(model.device),
|
70 |
+
forced_bos_token_id=tokenizer.convert_tokens_to_ids(tgt_lang),
|
71 |
+
max_new_tokens=int(a + b * inputs.input_ids.shape[1]),
|
72 |
+
num_beams=num_beams,
|
73 |
+
)
|
74 |
+
|
75 |
+
translation = tokenizer.batch_decode(result, skip_special_tokens=True)
|
76 |
+
translation = translation[0]
|
77 |
+
|
78 |
+
end = time.time()
|
79 |
+
print(f"\nTime: {end - start}")
|
80 |
+
|
81 |
+
return {'translation': translation}
|
82 |
+
|
83 |
+
|
84 |
+
if __name__ == "__main__":
|
85 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
requirements.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
fastapi==0.115.6
|
2 |
+
sentencepiece==0.2.0
|
3 |
+
torch==2.4.0
|
4 |
+
transformers==4.46.1
|
5 |
+
uvicorn==0.34.0
|