Spaces:
Running
Running
File size: 9,480 Bytes
1f39aa6 0dc9d0c 67a163c 1f39aa6 3fdc7b6 143e083 0dc9d0c 143e083 0dc9d0c 1f39aa6 43b39f0 1f39aa6 0dc9d0c c571a45 0dc9d0c c571a45 0dc9d0c 43b39f0 1f39aa6 0dc9d0c de4ab0d 0dc9d0c 43b39f0 c571a45 0dc9d0c 3fdc7b6 1f39aa6 0dc9d0c de4ab0d 151fa6d 1f39aa6 e0dc6e1 0dc9d0c 1f39aa6 3fdc7b6 1f39aa6 8d0070f 0dc9d0c e0dc6e1 0dc9d0c 1f39aa6 43b39f0 8d0070f 7d583b2 8d0070f 0dc9d0c 143e083 63c966a 4b929eb 63c966a 4b929eb 63c966a 4b929eb 613b55a 0dc9d0c dde491e 02f6bf9 67a163c 02f6bf9 3872c0b 9780ff0 3872c0b c31dbc6 3872c0b c31dbc6 3872c0b 0dc9d0c 613b55a dde491e 0dc9d0c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 | """
Translation interface using the MADLAD-400 3B model.
Translates English text to 22 production-ready languages from the MADLAD-400 paper.
"""
import csv
import tempfile
import warnings
from functools import lru_cache
from pathlib import Path
import gradio as gr
import spaces
import torch
from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
from langmap.langid_mapping import langid_to_language
MODEL_NAME = "google/madlad400-3b-mt"
def _get_device() -> torch.device:
if torch.cuda.is_available():
return torch.device("cuda")
warnings.warn("No GPU available. Running on CPU — translation will be slow.", stacklevel=2)
return torch.device("cpu")
@lru_cache(maxsize=1)
def _load_tokenizer() -> AutoTokenizer:
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, use_fast=True)
if tokenizer is None:
raise RuntimeError(f"Failed to load tokenizer for {MODEL_NAME}")
return tokenizer
@lru_cache(maxsize=1)
def _load_model() -> AutoModelForSeq2SeqLM:
device = _get_device()
dtype = torch.float16 if device.type == "cuda" else torch.float32
return AutoModelForSeq2SeqLM.from_pretrained(MODEL_NAME, dtype=dtype).to(device)
@lru_cache(maxsize=1)
def _build_language_mappings() -> tuple[dict[str, str], list[str]]:
tokenizer = _load_tokenizer()
vocab = tokenizer.get_vocab()
name_to_code = {v: k for k, v in langid_to_language.items() if k in vocab}
return name_to_code, sorted(name_to_code.keys())
@spaces.GPU
def translate(
text: str,
target_language_name: str,
max_new_tokens: int = 512,
num_beams: int = 1,
temperature: float = 1.0,
) -> str:
tokenizer = _load_tokenizer()
model = _load_model()
device = model.device
name_to_code, _ = _build_language_mappings()
target_code = name_to_code.get(target_language_name)
if target_code is None:
raise ValueError(f"Unsupported language: {target_language_name}")
if num_beams > 1 and temperature != 1.0:
gr.Info("Temperature has no effect when beam search is enabled (num_beams > 1).")
input_ids = tokenizer(target_code + " " + text, return_tensors="pt").input_ids.to(device)
generate_kwargs: dict = {"input_ids": input_ids, "max_new_tokens": max_new_tokens, "num_beams": num_beams}
if num_beams == 1:
generate_kwargs["do_sample"] = True
generate_kwargs["temperature"] = temperature
outputs = model.generate(**generate_kwargs)
result = tokenizer.decode(outputs[0], skip_special_tokens=True)
if not isinstance(result, str):
raise TypeError(f"Expected str from decode, got {type(result)}")
return result
def _parse_input_file(file_path: str) -> tuple[list[str], list[dict] | None]:
"""Parse a .txt or .csv file into a list of texts to translate.
Returns (texts, original_data). original_data is None for .txt files,
or a list of row dicts for .csv files (used to preserve extra columns in output).
"""
path = Path(file_path)
ext = path.suffix.lower()
if ext == ".txt":
texts = [line for line in path.read_text().splitlines() if line.strip()]
return texts, None
if ext == ".csv":
with open(path, newline="") as f:
reader = csv.DictReader(f)
if reader.fieldnames is None or "text" not in reader.fieldnames:
raise ValueError("CSV file must have a 'text' column.")
rows = list(reader)
texts = [row["text"] for row in rows]
return texts, rows
raise ValueError(f"Unsupported file type: {ext}. Use .txt or .csv.")
def _write_output_file(translations: list[str], original_data: list[dict] | None, ext: str) -> str:
"""Write translation results to a temp file and return its path."""
if ext == ".txt":
f = tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False)
f.write("\n".join(translations))
f.close()
return f.name
# CSV: append translation column to original data
f = tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False, newline="")
assert original_data is not None
fieldnames = list(original_data[0].keys()) + ["translation"]
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for row, translation in zip(original_data, translations):
row["translation"] = translation
writer.writerow(row)
f.close()
return f.name
def _build_table_data(texts: list[str], translations: list[str], original_data: list[dict] | None) -> dict:
"""Build a dict with headers and data for gr.Dataframe display."""
if original_data is None:
return {"headers": ["source", "translation"], "data": [[t, tr] for t, tr in zip(texts, translations)]}
headers = list(original_data[0].keys()) + ["translation"]
data = [[row[k] for k in original_data[0].keys()] + [tr] for row, tr in zip(original_data, translations)]
return {"headers": headers, "data": data}
@spaces.GPU
def translate_batch(
file,
target_language_name: str,
max_new_tokens: int = 512,
num_beams: int = 1,
temperature: float = 1.0,
progress=gr.Progress(),
) -> tuple[dict, str]:
if file is None:
raise gr.Error("Please upload a file first.")
file_path = file.name if hasattr(file, "name") else str(file)
ext = Path(file_path).suffix.lower()
texts, original_data = _parse_input_file(file_path)
if not texts:
raise gr.Error("File is empty or contains no text to translate.")
if num_beams > 1 and temperature != 1.0:
gr.Info("Temperature has no effect when beam search is enabled (num_beams > 1).")
translations = []
for i, text in enumerate(progress.tqdm(texts, desc="Translating")):
result = translate(text, target_language_name, max_new_tokens, num_beams, temperature)
translations.append(result)
output_path = _write_output_file(translations, original_data, ext)
table_data = _build_table_data(texts, translations, original_data)
return table_data, output_path
def _build_demo() -> gr.Blocks:
_, language_names = _build_language_mappings()
with gr.Blocks(title="MADLAD-400 Translation") as demo:
gr.Markdown(
"# MADLAD-400 Translation\n"
"Translate English into 22 production-ready languages using Google's MADLAD-400 3B model. "
"[Paper](https://arxiv.org/pdf/2309.04662)"
)
with gr.Row():
# --- Sidebar ---
with gr.Column(scale=1, min_width=250):
gr.Markdown("### Settings")
target_language = gr.Dropdown(
choices=language_names,
value="French",
label="Target language",
)
max_new_tokens = gr.Slider(minimum=1, maximum=1024, value=512, step=1, label="Max new tokens")
num_beams = gr.Slider(minimum=1, maximum=10, value=1, step=1, label="Num beams")
temperature = gr.Slider(minimum=0.1, maximum=2.0, value=1.0, step=0.1, label="Temperature")
# --- Main area ---
with gr.Column(scale=3):
with gr.Tab("Single"):
with gr.Row():
input_text = gr.Textbox(
label="English",
placeholder="Enter English text here",
lines=4,
)
output_text = gr.Textbox(
label="Translation",
lines=4,
buttons=["copy"],
interactive=False,
)
single_btn = gr.Button("Translate", variant="primary")
single_btn.click(
fn=translate,
inputs=[input_text, target_language, max_new_tokens, num_beams, temperature],
outputs=output_text,
)
gr.Examples(
examples=[
["Hello, how are you today?", "French"],
["The weather is beautiful.", "Spanish"],
["Thank you very much.", "German"],
["Where is the train station?", "Portuguese"],
],
inputs=[input_text, target_language],
)
with gr.Tab("Batch"):
with gr.Row():
batch_input = gr.File(
label="Upload file (.txt, one sentence per line, or .csv with 'text' column)",
file_types=[".txt", ".csv"],
)
batch_output = gr.File(label="Download translations", interactive=False)
batch_btn = gr.Button("Translate", variant="primary")
batch_preview = gr.Dataframe(label="Preview", interactive=False)
batch_btn.click(
fn=translate_batch,
inputs=[batch_input, target_language, max_new_tokens, num_beams, temperature],
outputs=[batch_preview, batch_output],
)
return demo
demo = _build_demo()
def main() -> None:
demo.launch(theme=gr.themes.Soft())
if __name__ == "__main__":
main()
|