ZennyKenny commited on
Commit
e6cdbda
·
verified ·
1 Parent(s): e3493fe

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +60 -115
app.py CHANGED
@@ -1,57 +1,41 @@
1
  import os
2
  import re
3
- import importlib.util
4
  from pathlib import Path
5
 
6
  import torch
7
  import gradio as gr
8
  from transformers import AutoTokenizer, AutoModelForCausalLM, TextStreamer
 
9
 
10
- MODEL_ID = "ZennyKenny/oss-20b-prereform-to-modern-ru-merged"
 
 
11
 
12
- # ----------------- Load SYSTEM_PROMPT from 'text-prompt.py' -----------------
13
  def _load_system_prompt():
14
  prompt_path = Path(__file__).with_name("text-prompt.py")
15
- default_prompt = (
16
- "Ты компетентный редактор русского языка. "
17
- "Преобразуй дореформенную русскую орфографию (до 1918 года) "
18
- "в современную орфографию. Сохраняй смысл, пунктуацию и регистр. "
19
- "Не добавляй комментариев. Верни только преобразованный текст."
20
- )
21
  try:
22
- if not prompt_path.exists():
23
- return default_prompt
24
- spec = importlib.util.spec_from_file_location("text_prompt_mod", str(prompt_path))
25
- mod = importlib.util.module_from_spec(spec)
26
- assert spec and spec.loader, "Cannot load spec for text-prompt.py"
27
- spec.loader.exec_module(mod) # type: ignore[attr-defined]
28
- return getattr(mod, "SYSTEM_PROMPT", default_prompt)
29
  except Exception:
30
- return default_prompt
31
 
32
  SYSTEM_PROMPT = _load_system_prompt()
33
 
34
- # ----------------- Fallback: rule-based converter (no ML needed) -----------------
35
- REPLACEMENTS = [
36
- ("Ѣ", "Е"), ("ѣ", "е"),
37
- ("І", "И"), ("і", "и"),
38
- ("Ѳ", "Ф"), ("ѳ", "ф"),
39
- ("Ѵ", "И"), ("ѵ", "и"),
40
- ]
41
- TERMINAL_HARD_SIGN = re.compile(r"(?i)ъ\b") # remove word-final hard sign
42
- MULTI_SPACES = re.compile(r"[ \t]{2,}")
43
-
44
- def rule_based_convert(text: str) -> str:
45
- if not text:
46
- return ""
47
- out = text
48
- for old, new in REPLACEMENTS:
49
- out = out.replace(old, new)
50
- out = TERMINAL_HARD_SIGN.sub("", out)
51
- out = MULTI_SPACES.sub(" ", out)
52
- return out
53
 
54
- # ----------------- Model state (CPU-only) -----------------
55
  _tokenizer = None
56
  _model = None
57
  _streamer = None
@@ -59,54 +43,42 @@ _MODEL_READY = False
59
  _MODEL_ERROR = None
60
 
61
  def build_prompt(text: str) -> str:
62
- return (
63
- f"{SYSTEM_PROMPT}\n\n"
64
- f"Текст (дореформ.):\n{text.strip()}\n\n"
65
- f"Текст (современная орфография):"
66
- )
67
 
68
  def load_model_cpu():
69
- """Force CPU load. Gracefully degrade if loading fails."""
70
  global _tokenizer, _model, _streamer, _MODEL_READY, _MODEL_ERROR
71
  if _MODEL_READY or _MODEL_ERROR:
72
  return
73
-
74
  if os.getenv("DISABLE_MODEL", "0") == "1":
75
  _MODEL_ERROR = "Model disabled via DISABLE_MODEL=1."
76
  return
77
-
78
  try:
79
  os.environ["CUDA_VISIBLE_DEVICES"] = ""
80
- os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
81
-
82
- _tokenizer = AutoTokenizer.from_pretrained(
83
- MODEL_ID, use_fast=True, trust_remote_code=True
84
- )
85
- _model = AutoModelForCausalLM.from_pretrained(
86
- MODEL_ID,
87
  trust_remote_code=True,
88
- torch_dtype=torch.float32, # CPU dtype
89
  low_cpu_mem_usage=True,
90
- device_map=None, # ensure CPU
91
  ).to("cpu")
 
 
 
 
 
 
 
92
  _streamer = TextStreamer(_tokenizer, skip_prompt=True, skip_special_tokens=True)
93
  _MODEL_READY = True
94
  except Exception as e:
95
  _MODEL_ERROR = f"{type(e).__name__}: {e}"
96
 
97
- def convert_with_model(
98
- text: str,
99
- max_new_tokens: int,
100
- temperature: float,
101
- top_p: float,
102
- top_k: int,
103
- repetition_penalty: float,
104
- do_stream: bool
105
- ) -> str:
106
  prompt = build_prompt(text)
107
  inputs = _tokenizer(prompt, return_tensors="pt")
108
  input_ids = inputs.input_ids.to("cpu")
109
-
110
  gen_kwargs = dict(
111
  max_new_tokens=int(max_new_tokens),
112
  temperature=float(temperature),
@@ -115,65 +87,47 @@ def convert_with_model(
115
  repetition_penalty=float(repetition_penalty),
116
  do_sample=True,
117
  )
118
-
119
  if do_stream:
120
  chunks = []
121
-
122
- class _BufStreamer(TextStreamer):
123
- def on_finalized_text(self, text, stream_end=False):
124
- chunks.append(text)
125
-
126
- buf_streamer = _BufStreamer(_tokenizer, skip_prompt=True, skip_special_tokens=True)
127
- _ = _model.generate(input_ids=input_ids, streamer=buf_streamer, **gen_kwargs)
128
  out = "".join(chunks)
129
  else:
130
  with torch.no_grad():
131
- output_ids = _model.generate(input_ids=input_ids, **gen_kwargs)
132
- out = _tokenizer.decode(output_ids[0], skip_special_tokens=True)
133
-
134
  marker = "Текст (современная орфография):"
135
  return out.split(marker, 1)[-1].strip() if marker in out else out.strip()
136
 
137
  def convert(text, max_new_tokens, temperature, top_p, top_k, repetition_penalty, do_stream):
138
  if not text or not text.strip():
139
  return ""
140
-
141
  load_model_cpu()
142
-
143
  if _MODEL_READY:
144
  try:
145
- return convert_with_model(
146
- text, max_new_tokens, temperature, top_p, top_k, repetition_penalty, do_stream
147
- )
148
  except Exception:
149
  return rule_based_convert(text) + "\n\n[Примечание: использовано правило-базовое преобразование из-за ошибки генерации на CPU.]"
150
- else:
151
- note = "\n\n[Примечание: используется правило-базовое преобразование"
152
- if _MODEL_ERROR:
153
- note += f" (модель недоступна: {_MODEL_ERROR})"
154
- note += ".]"
155
- return rule_based_convert(text) + note
156
 
157
- # ----------------- UI -----------------
158
  with gr.Blocks(title="Pre-reform → Modern Russian (CPU-only)") as demo:
159
  gr.Markdown(
160
  """
161
  # Преобразование дореформенной орфографии → современная (CPU-only)
162
- Вставьте дореформенный русский текст получите современную орфографию.
163
- Модель: `ZennyKenny/oss-20b-prereform-to-modern-ru-merged`
164
-
165
- *Подсказка:* На CPU загрузка большой модели может быть недоступна; в таком случае
166
- автоматически используется быстрый правило-базовый конвертер (ѣ→е, і→и, ѳ→ф, ѵ→и, удаление конечного ъ).
167
  """
168
  )
169
-
170
  with gr.Row():
171
- with gr.Column(scale=1):
172
- inp = gr.Textbox(
173
- label="Ввод: дореформенный текст",
174
- placeholder="Например: \"въ мирѣ сёмъ многа есть...\"",
175
- lines=10
176
- )
177
  with gr.Accordion("Параметры генерации (медленно на CPU)", open=False):
178
  max_new_tokens = gr.Slider(8, 256, value=128, step=8, label="max_new_tokens")
179
  temperature = gr.Slider(0.0, 1.2, value=0.2, step=0.05, label="temperature")
@@ -181,26 +135,17 @@ with gr.Blocks(title="Pre-reform → Modern Russian (CPU-only)") as demo:
181
  top_k = gr.Slider(0, 100, value=40, step=1, label="top_k")
182
  repetition_penalty = gr.Slider(1.0, 2.0, value=1.05, step=0.01, label="repetition_penalty")
183
  do_stream = gr.Checkbox(value=False, label="Стриминг вывода")
184
-
185
  btn = gr.Button("Преобразовать", variant="primary")
186
-
187
- with gr.Column(scale=1):
188
  out = gr.Textbox(label="Вывод: современная орфография", lines=12)
189
-
190
- examples = [
191
- ["въ семъ домѣ обитало три семейства, и каждое имѣло свои обыкновенія."],
192
- ["Онъ шёлъ по узкой улѣцѣ, разсматривая вывѣски лавокъ и фонари."],
193
- ["въ мирѣ сёмъ многа есть, чего мудрецу и не снилось."]
194
- ]
195
- gr.Examples(examples=examples, inputs=[inp])
196
-
197
- def _on_click(text, a, b, c, d, e, f):
198
- return convert(text, a, b, c, d, e, f)
199
-
200
  btn.click(
201
- _on_click,
202
  inputs=[inp, max_new_tokens, temperature, top_p, top_k, repetition_penalty, do_stream],
203
- outputs=[out]
204
  )
205
 
206
  if __name__ == "__main__":
 
1
  import os
2
  import re
 
3
  from pathlib import Path
4
 
5
  import torch
6
  import gradio as gr
7
  from transformers import AutoTokenizer, AutoModelForCausalLM, TextStreamer
8
+ from peft import PeftModel # NEW
9
 
10
+ MODEL_ID_BASE = "openai/gpt-oss-20b" # base model
11
+ ADAPTER_REPO = "ZennyKenny/oss-20b-prereform-to-modern-ru-merged"
12
+ ADAPTER_SUBFOLDER = "checkpoint-60" # where adapter lives in your repo
13
 
14
+ # ---- load SYSTEM_PROMPT from text-prompt.py (same as before) ----
15
  def _load_system_prompt():
16
  prompt_path = Path(__file__).with_name("text-prompt.py")
17
+ default = ("Ты компетентный редактор русского языка. "
18
+ "Преобразуй дореформенную русскую орфографию (до 1918 года) "
19
+ "в современную орфографию. Сохраняй смысл, пунктуацию и регистр. "
20
+ "Не добавляй комментариев. Верни только преобразованный текст.")
 
 
21
  try:
22
+ ns = {}
23
+ exec(prompt_path.read_text(encoding="utf-8"), ns) if prompt_path.exists() else None
24
+ return ns.get("SYSTEM_PROMPT", default)
 
 
 
 
25
  except Exception:
26
+ return default
27
 
28
  SYSTEM_PROMPT = _load_system_prompt()
29
 
30
+ # ---- simple rule-based fallback (unchanged) ----
31
+ REPLACEMENTS = [("Ѣ","Е"),("ѣ","е"),("І","И"),("і","и"),("Ѳ","Ф"),("ѳ","ф"),("Ѵ","И"),("ѵ","и")]
32
+ TERMINAL_HARD_SIGN = re.compile(r"(?i)ъ\b")
33
+ def rule_based_convert(t):
34
+ if not t: return ""
35
+ for a,b in REPLACEMENTS: t = t.replace(a,b)
36
+ return TERMINAL_HARD_SIGN.sub("", t)
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
+ # ---- model state (CPU only) ----
39
  _tokenizer = None
40
  _model = None
41
  _streamer = None
 
43
  _MODEL_ERROR = None
44
 
45
  def build_prompt(text: str) -> str:
46
+ return f"{SYSTEM_PROMPT}\n\nТекст (дореформ.):\n{text.strip()}\n\nТекст (современная орфография):"
 
 
 
 
47
 
48
  def load_model_cpu():
49
+ """Load base model, then apply LoRA adapter from your repo."""
50
  global _tokenizer, _model, _streamer, _MODEL_READY, _MODEL_ERROR
51
  if _MODEL_READY or _MODEL_ERROR:
52
  return
 
53
  if os.getenv("DISABLE_MODEL", "0") == "1":
54
  _MODEL_ERROR = "Model disabled via DISABLE_MODEL=1."
55
  return
 
56
  try:
57
  os.environ["CUDA_VISIBLE_DEVICES"] = ""
58
+ _tokenizer = AutoTokenizer.from_pretrained(ADAPTER_REPO, use_fast=True, trust_remote_code=True)
59
+ base = AutoModelForCausalLM.from_pretrained(
60
+ MODEL_ID_BASE,
 
 
 
 
61
  trust_remote_code=True,
62
+ torch_dtype=torch.float32,
63
  low_cpu_mem_usage=True,
64
+ device_map=None,
65
  ).to("cpu")
66
+ # Apply LoRA adapter from your repo/subfolder
67
+ _model = PeftModel.from_pretrained(base, ADAPTER_REPO, subfolder=ADAPTER_SUBFOLDER)
68
+ # (Optional) Merge for faster inference on CPU:
69
+ try:
70
+ _model = _model.merge_and_unload()
71
+ except Exception:
72
+ pass
73
  _streamer = TextStreamer(_tokenizer, skip_prompt=True, skip_special_tokens=True)
74
  _MODEL_READY = True
75
  except Exception as e:
76
  _MODEL_ERROR = f"{type(e).__name__}: {e}"
77
 
78
+ def convert_with_model(text, max_new_tokens, temperature, top_p, top_k, repetition_penalty, do_stream):
 
 
 
 
 
 
 
 
79
  prompt = build_prompt(text)
80
  inputs = _tokenizer(prompt, return_tensors="pt")
81
  input_ids = inputs.input_ids.to("cpu")
 
82
  gen_kwargs = dict(
83
  max_new_tokens=int(max_new_tokens),
84
  temperature=float(temperature),
 
87
  repetition_penalty=float(repetition_penalty),
88
  do_sample=True,
89
  )
 
90
  if do_stream:
91
  chunks = []
92
+ class _Buf(TextStreamer):
93
+ def on_finalized_text(self, txt, stream_end=False):
94
+ chunks.append(txt)
95
+ buf = _Buf(_tokenizer, skip_prompt=True, skip_special_tokens=True)
96
+ _ = _model.generate(input_ids=input_ids, streamer=buf, **gen_kwargs)
 
 
97
  out = "".join(chunks)
98
  else:
99
  with torch.no_grad():
100
+ out_ids = _model.generate(input_ids=input_ids, **gen_kwargs)
101
+ out = _tokenizer.decode(out_ids[0], skip_special_tokens=True)
 
102
  marker = "Текст (современная орфография):"
103
  return out.split(marker, 1)[-1].strip() if marker in out else out.strip()
104
 
105
  def convert(text, max_new_tokens, temperature, top_p, top_k, repetition_penalty, do_stream):
106
  if not text or not text.strip():
107
  return ""
 
108
  load_model_cpu()
 
109
  if _MODEL_READY:
110
  try:
111
+ return convert_with_model(text, max_new_tokens, temperature, top_p, top_k, repetition_penalty, do_stream)
 
 
112
  except Exception:
113
  return rule_based_convert(text) + "\n\n[Примечание: использовано правило-базовое преобразование из-за ошибки генерации на CPU.]"
114
+ note = "\n\n[Примечание: используется правило-базовое преобразование"
115
+ if _MODEL_ERROR: note += f" (модель недоступна: {_MODEL_ERROR})"
116
+ note += ".]"
117
+ return rule_based_convert(text) + note
 
 
118
 
119
+ # ---- Gradio UI (same structure as before) ----
120
  with gr.Blocks(title="Pre-reform → Modern Russian (CPU-only)") as demo:
121
  gr.Markdown(
122
  """
123
  # Преобразование дореформенной орфографии → современная (CPU-only)
124
+ Модель: LoRA-адаптер к `openai/gpt-oss-20b` из `ZennyKenny/oss-20b-prereform-to-modern-ru-merged`.
125
+ При недоступности модели используется правило-базовый конвертер (ѣ→е, і→и, ѳ→ф, ѵ→и, удаление конечного ъ).
 
 
 
126
  """
127
  )
 
128
  with gr.Row():
129
+ with gr.Column():
130
+ inp = gr.Textbox(label="Ввод: дореформенный текст", lines=10)
 
 
 
 
131
  with gr.Accordion("Параметры генерации (медленно на CPU)", open=False):
132
  max_new_tokens = gr.Slider(8, 256, value=128, step=8, label="max_new_tokens")
133
  temperature = gr.Slider(0.0, 1.2, value=0.2, step=0.05, label="temperature")
 
135
  top_k = gr.Slider(0, 100, value=40, step=1, label="top_k")
136
  repetition_penalty = gr.Slider(1.0, 2.0, value=1.05, step=0.01, label="repetition_penalty")
137
  do_stream = gr.Checkbox(value=False, label="Стриминг вывода")
 
138
  btn = gr.Button("Преобразовать", variant="primary")
139
+ with gr.Column():
 
140
  out = gr.Textbox(label="Вывод: современная орфография", lines=12)
141
+ gr.Examples(
142
+ examples=[["въ семъ домѣ обитало три семейства, и каждое имѣло свои обыкновенія."]],
143
+ inputs=[inp],
144
+ )
 
 
 
 
 
 
 
145
  btn.click(
146
+ lambda t,a,b,c,d,e,f: convert(t,a,b,c,d,e,f),
147
  inputs=[inp, max_new_tokens, temperature, top_p, top_k, repetition_penalty, do_stream],
148
+ outputs=[out],
149
  )
150
 
151
  if __name__ == "__main__":