Spaces:
Sleeping
Sleeping
| import sys | |
| import os | |
| import re | |
| import gradio as gr | |
| import torch | |
| from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer | |
| from threading import Thread | |
| from huggingface_hub import login | |
| from dotenv import load_dotenv | |
| # --- FIX: Add project root to Python's path --- | |
| project_root = os.path.dirname(os.path.abspath(__file__)) | |
| sys.path.insert(0, project_root) | |
| # --- Updated Spaces import for Zero-GPU compatibility --- | |
| try: | |
| import spaces | |
| print("'spaces' module imported successfully.") | |
| except ImportError: | |
| print("Warning: 'spaces' module not found. Using dummy decorator for local execution.") | |
| class DummySpaces: | |
| def GPU(self, *args, **kwargs): | |
| def decorator(func): | |
| print(f"Note: Dummy @GPU decorator used for function '{func.__name__}'.") | |
| return func | |
| return decorator | |
| spaces = DummySpaces() | |
| # --- Step 1: Hugging Face Authentication --- | |
| load_dotenv() | |
| HF_TOKEN = os.getenv("HF_TOKEN") | |
| if not HF_TOKEN: | |
| raise ValueError("FATAL: Hugging Face token not found. Please set the HF_TOKEN environment variable.") | |
| print("--- Logging in to Hugging Face Hub ---") | |
| login(token=HF_TOKEN) | |
| # --- Step 2: Initialize Model and Tokenizer --- | |
| MODEL_NAME = "Gregniuki/Qwen3-0.6B-translate-EN-PL-EN" | |
| #MODEL_NAME = "Gregniuki/Qwen3-1.7B-translatate-En-Pl-En" | |
| print(f"--- Loading model from Hugging Face Hub: {MODEL_NAME} ---") | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| dtype = torch.bfloat16 if device.type == "cuda" else torch.float32 | |
| print(f"--- Using device: {device}, dtype: {dtype} ---") | |
| #try: | |
| # tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True) | |
| # model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, torch_dtype=dtype, trust_remote_code=True).to(device) | |
| # model.eval() | |
| # print("--- Model and Tokenizer Loaded Successfully ---") | |
| #except Exception as e: | |
| # raise RuntimeError(f"FATAL: Could not load components. Error: {e}") | |
| try: | |
| tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True) | |
| # --- FIX: Manually set the chat template for the Qwen3 model --- | |
| qwen3_chat_template = """{% for message in messages %}{% if message['role'] == 'system' %}{{ '<|im_start|>system\n' + message['content'] + '<|im_end|>\n' }}{% elif message['role'] == 'user' %}{{ '<|im_start|>user\n' + message['content'] + '<|im_end|>\n' }}{% elif message['role'] == 'assistant' %}{{ '<|im_start|>assistant\n' + message['content'] + '<|im_end|>\n' }}{% endif %}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% endif %}""" | |
| tokenizer.chat_template = qwen3_chat_template | |
| # --- END FIX --- | |
| model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, torch_dtype=dtype, trust_remote_code=True).to(device) | |
| model.eval() | |
| print("--- Model and Tokenizer Loaded Successfully ---") | |
| except Exception as e: | |
| raise RuntimeError(f"FATAL: Could not load components. Error: {e}") | |
| # --- Helper Functions --- | |
| def chunk_text(text: str, max_size: int) -> list[str]: | |
| if not text: return [] | |
| chunks, start_index = [], 0 | |
| while start_index < len(text): | |
| end_index = start_index + max_size | |
| if end_index >= len(text): | |
| chunks.append(text[start_index:]); break | |
| split_pos = text.rfind('.', start_index, end_index) | |
| if split_pos != -1: | |
| chunk, start_index = text[start_index : split_pos + 1], split_pos + 1 | |
| else: | |
| chunk, start_index = text[start_index:end_index], end_index | |
| chunks.append(chunk.strip()) | |
| return [c for c in chunks if c] | |
| # --- Step 3: Core Translation Function (Now with Token-by-Token Streaming) --- | |
| def translate_with_chunks(input_text: str, chunk_size: int, temperature: float, top_p: float, top_k: int, progress=gr.Progress()) -> str: | |
| """ | |
| Processes text by translating each chunk independently and streams the | |
| results back token-by-token for a smooth, real-time user experience. | |
| """ | |
| progress(0, desc="Starting...") | |
| if not input_text: | |
| yield "Input text is empty. Please enter some text to translate." | |
| return | |
| text_chunks = chunk_text(input_text, chunk_size) if len(input_text) > chunk_size else [input_text] | |
| num_chunks = len(text_chunks) | |
| print(f"Processing {num_chunks} independent chunk(s).") | |
| full_output = "" | |
| for i, chunk in enumerate(text_chunks): | |
| progress(0.1 + (i / num_chunks) * 0.9, desc=f"Translating chunk {i+1}/{num_chunks}") | |
| messages = [{"role": "user", "content": chunk}] | |
| prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) | |
| model_inputs = tokenizer([prompt], add_special_tokens=True, return_tensors="pt").to(device) | |
| # Use TextIteratorStreamer for real-time token generation | |
| streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True) | |
| # Set up generation arguments | |
| generation_kwargs = dict( | |
| **model_inputs, | |
| streamer=streamer, | |
| max_new_tokens=2048, | |
| do_sample=True, | |
| temperature=temperature, | |
| top_p=top_p, | |
| top_k=top_k | |
| ) | |
| # Run the generation in a separate thread to avoid blocking the UI | |
| thread = Thread(target=model.generate, kwargs=generation_kwargs) | |
| thread.start() | |
| # Yield new tokens as they are generated | |
| for new_token in streamer: | |
| full_output += new_token | |
| yield full_output | |
| # Add a space after each chunk for better readability | |
| full_output += " " | |
| yield full_output.strip() | |
| progress(1.0, desc="Done!") | |
| # --- Step 4: Create and Launch the Gradio App --- | |
| print("\n--- Initializing Gradio Interface ---") | |
| app = gr.Interface( | |
| fn=translate_with_chunks, | |
| inputs=[ | |
| gr.Textbox(lines=15, label="Input Text", placeholder="Enter long text to process here..."), | |
| gr.Slider( | |
| minimum=256, | |
| maximum=2048, | |
| value=2048, | |
| step=64, | |
| label="Character Chunk Size", | |
| info="Text will be split into chunks of this size for translation." | |
| ), | |
| gr.Slider( | |
| minimum=0.01, # Temperature cannot be 0 for sampling | |
| maximum=2.0, | |
| value=0.6, | |
| step=0.01, | |
| label="Temperature", | |
| info="Controls randomness. Higher values mean more random outputs." | |
| ), | |
| gr.Slider( | |
| minimum=0.0, | |
| maximum=1.0, | |
| value=0.8, | |
| step=0.05, | |
| label="Top-p (Nucleus Sampling)", | |
| info="Selects from tokens with a cumulative probability mass up to this value." | |
| ), | |
| gr.Slider( | |
| minimum=0, | |
| maximum=100, | |
| value=20, | |
| step=1, | |
| label="Top-k", | |
| info="Selects from the top 'k' most likely tokens at each step." | |
| ) | |
| ], | |
| outputs=gr.Textbox(lines=15, label="Model Output", interactive=False), | |
| title="Qwen3 0.6B EN-PL-EN Text Translator (Real-Time Streaming)", | |
| description="Processes long text by splitting it into independent chunks and streams the translation in real-time.", | |
| allow_flagging="never" | |
| ) | |
| if __name__ == "__main__": | |
| app.queue().launch() |