File size: 7,544 Bytes
7994ca9
 
 
 
 
 
 
 
9024f27
7994ca9
 
2751fc7
7994ca9
 
 
 
2751fc7
9024f27
 
2751fc7
 
7994ca9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2751fc7
 
 
 
a4d0c85
2751fc7
 
 
a4d0c85
2751fc7
 
 
 
 
 
6aafe97
7994ca9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a4d0c85
7994ca9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import spaces  # MUST be the very first import for ZeroGPU Spaces

import os
import gradio as gr
import torch
import regex
import unicodedata
import re
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel

# ─── Model Configuration ──────────────────────────────────────────────────────
BASE_MODEL = "unsloth/gemma-3-270m-it"
LORA_PATH  = "Razon2006/gemma3-tamil-Grapheme-NewChange-r32-lr"
hf_token = os.environ.get("HF_TOKEN")

print("Loading tokenizer globally...")
tokenizer = AutoTokenizer.from_pretrained(LORA_PATH, token=hf_token)

# Global model variable for lazy loading
model = None

# ─── Grapheme Tokenizer & Constants ───────────────────────────────────────────
INSTRUCTION = (
    "கீழ்க்காணும் தமிழ் வெண்பாவை, அதன் சொற்களை சரியான எல்லைகளுடன் "
    "பிரித்து எழுதுக (பதப்பிரிவு). சொற்களின் வரிசை மாறாமல், "
    "ஒட்டிய சொற்களை பிரிக்கவும், தேவையெனில் பிரிந்த எழுத்துக்களை "
    "இணைத்து சரியான சொல்லை உருவாக்கவும். பதப்பிரிவை மட்டும் தரவும்."
)

PROBLEMATIC_AKSHARAS = {
    'யு', 'றா', 'ஞா', 'யே', 'யை', 'றே', 'கெ', 'றோ', 'யெ', 'யொ',
    'றெ', 'ழா', 'நூ', 'லெ', 'வொ', 'ரெ', 'றொ', 'கீ', 'டே', 'லொ',
    'னொ', 'டொ', 'ணே', 'ளெ', 'ணோ', 'ரொ', 'றூ', 'ணீ', 'றீ', 'ழீ',
    'னூ', 'ணெ', 'வூ', 'யீ', 'டீ', 'ளொ', 'லூ', 'நொ', 'ணொ', 'டூ',
    'நை', 'கௌ', 'ளூ', 'ழே', 'ழூ', 'ஞை', 'ளீ', 'ஜை', 'ணூ', 'ழொ',
    'ழெ', 'ஞு', 'ஞெ', 'ஞூ', 'ஸா', 'ஜூ', 'எா', 'எீ', 'ழோ', 'பௌ',
}

def clean_grapheme_split(text, tokenizer):
    words = text.split(' ')
    result_words = []
    for word in words:
        if not word.strip():
            continue
        clusters = regex.findall(r'\X', word)
        clusters = [
            unicodedata.normalize('NFC', c) for c in clusters
            if c.strip() and any('\u0B80' <= ch <= '\u0BFF' for ch in c)
        ]
        if not clusters:
            result_words.append(word)
            continue
        merged = []
        i = 0
        while i < len(clusters):
            c = clusters[i]
            tokens = tokenizer.tokenize(c)
            if (c in PROBLEMATIC_AKSHARAS or len(tokens) > 1) and i + 1 < len(clusters):
                combined = c + clusters[i+1]
                while len(tokenizer.tokenize(combined)) > 1 and i + 2 < len(clusters):
                    i += 1
                    combined = combined + clusters[i+1]
                merged.append(combined)
                i += 2
            else:
                merged.append(c)
                i += 1
        result_words.append('|'.join(merged))
    return ' '.join(result_words)

def post_process_grapheme(text):
    return text.replace('|', '')

# ─── Inference Pipeline ───────────────────────────────────────────────────────
@spaces.GPU(duration=60)
def generate_pathaseth(venba, max_new_tokens=256):
    global model
    
    # LAZY LOADING: Load the model here where CUDA is fully unlocked by ZeroGPU
    if model is None:
        print("Loading base model directly into GPU...")
        base_model = AutoModelForCausalLM.from_pretrained(
            BASE_MODEL,
            torch_dtype=torch.bfloat16,
            device_map="cuda", # <--- FIX: Forces native loading to GPU to avoid precision loss
            token=hf_token
        )
        print("Loading LoRA adapter...")
        model = PeftModel.from_pretrained(base_model, LORA_PATH, token=hf_token)
        model.eval()
        print("Model ready for inference!")

    venba = venba.strip()
    if not venba:
        return "Please enter a Venba verse."

    venba_clean = re.sub(r'\s+', ' ', venba).replace('்்', '்').strip()
    venba_no_space = venba_clean.replace(' ', '')
    venba_g = clean_grapheme_split(venba_no_space, tokenizer)

    messages = [{
        "role": "user",
        "content": f"{INSTRUCTION}\n\nவெண்பா: {venba_g}\nபதப்பிரிவு:"
    }]
    prompt = tokenizer.apply_chat_template(
        messages, tokenize=False, add_generation_prompt=True
    )
    bos = tokenizer.bos_token
    if bos and prompt.startswith(bos):
        prompt = prompt[len(bos):]

    inputs = tokenizer(prompt, return_tensors="pt").to("cuda")

    with torch.no_grad():
        outputs = model.generate(
            **inputs,
            max_new_tokens=max_new_tokens,
            use_cache=True,
            do_sample=False, # <--- FIX: Ensures deterministic output (stops hallucination)
        )

    new_tokens = outputs[0][inputs['input_ids'].shape[1]:]
    raw = tokenizer.decode(new_tokens, skip_special_tokens=True).strip()
    return post_process_grapheme(raw)

# ─── Example Venbas ───────────────────────────────────────────────────────────
EXAMPLES = [
    ["இறுதி பயப்பினு மெஞ்சா திறைவற் குறுதி பயப்பதாந் தூது"],
]

# ─── Gradio Interface ─────────────────────────────────────────────────────────
with gr.Blocks(
    title="Tamil Venba Pathasetham",
    theme=gr.themes.Soft()
) as demo:

    gr.HTML("""
        <div style="text-align: center; font-size: 2em; font-weight: bold; color: #4B0082;">Tamil Venba Pathasetham (பதசேதம்)</div>
        <div style="text-align: center; color: #666; margin-bottom: 20px;">
            Automated word segmentation of classical Tamil Venba poetry using Gemma 3 + Grapheme Tokenization
        </div>
    """)

    with gr.Row():
        with gr.Column():
            venba_input = gr.Textbox(
                label="Enter Tamil Venba (வெண்பா)",
                placeholder="வண்மைதரு மாகமநூன் வைத்த பொருள்வழுவா...",
                lines=4,
            )
            submit_btn = gr.Button("Generate Pathasetham →", variant="primary", size="lg")

        with gr.Column():
            pathaseth_output = gr.Textbox(
                label="Pathasetham (பதசேதம்) Output",
                lines=4,
                interactive=False,
            )

    gr.Examples(
        examples=EXAMPLES,
        inputs=venba_input,
        outputs=pathaseth_output,
        fn=generate_pathaseth,
        cache_examples=False,
        label="Click any example to test instantly"
    )

    submit_btn.click(
        fn=generate_pathaseth,
        inputs=venba_input,
        outputs=pathaseth_output,
    )

if __name__ == "__main__":
    demo.launch()