AhmedBenElsayedBenAhmed commited on
Commit
3f5bca8
·
1 Parent(s): 898af55
Files changed (1) hide show
  1. app.py +131 -82
app.py CHANGED
@@ -1,20 +1,21 @@
1
- # ===============================
2
- # 1️⃣ استيراد المكتبات
3
- # ===============================
 
4
  import os
5
- os.environ["HF_HOME"] = "/tmp"
6
- os.environ["TRANSFORMERS_CACHE"] = "/tmp"
7
- os.environ["PEFT_HOME"] = "/tmp"
 
 
8
 
 
9
  from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments
10
  from peft import LoraConfig, get_peft_model, TaskType, PeftModel
11
- from flask import Flask, request, jsonify
12
- from flask_cors import CORS
13
- import threading, torch, json
14
  from datasets import Dataset
15
 
16
  # ===============================
17
- # 2️⃣ إعداد المتغيرات
18
  # ===============================
19
  MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct"
20
  FLASK_PORT = 7860
@@ -27,33 +28,46 @@ os.makedirs(SAVE_DIR, exist_ok=True)
27
  DATASET_FILE = os.path.join(DATA_DIR, "dataset.json")
28
 
29
  # ===============================
30
- # 3️⃣ تحديد الجهاز
31
  # ===============================
32
  device = "cuda" if torch.cuda.is_available() else "cpu"
33
  print("🔹 Using device:", device)
34
 
 
 
 
 
 
 
 
 
 
35
  # ===============================
36
- # 4️⃣ تحميل الموديل الأساسي بأمان
37
  # ===============================
38
- print("🔄 Loading base model...")
39
  tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
40
 
 
 
 
 
41
  try:
42
- model = AutoModelForCausalLM.from_pretrained(
43
  MODEL_NAME,
44
- device_map="auto" if device=="cuda" else {"": "cpu"},
45
- torch_dtype=torch.float16 if device=="cuda" else torch.float32,
46
- load_in_8bit=(device=="cuda"),
47
- trust_remote_code=True
48
  )
49
- model.to(device)
50
  print("✅ Base model loaded!")
51
  except Exception as e:
52
  print("⚠️ Failed to load base model:", e)
53
  raise e
54
 
55
  # ===============================
56
- # 5️⃣ إعداد LoRA
57
  # ===============================
58
  lora_config = LoraConfig(
59
  task_type=TaskType.CAUSAL_LM,
@@ -62,23 +76,31 @@ lora_config = LoraConfig(
62
  lora_dropout=0.05,
63
  target_modules=["q_proj", "v_proj"]
64
  )
65
- model = get_peft_model(model, lora_config)
66
- model.to(device)
67
- print("✅ LoRA model ready on device:", device)
 
 
 
 
 
 
68
 
69
  # ===============================
70
- # 6️⃣ Auto load LoRA weights
71
  # ===============================
72
- if os.path.exists(SAVE_DIR):
73
  try:
74
- base_model = AutoModelForCausalLM.from_pretrained(
 
 
75
  MODEL_NAME,
76
- device_map="auto" if device=="cuda" else {"": "cpu"},
77
- torch_dtype=torch.float16 if device=="cuda" else torch.float32,
78
- load_in_8bit=(device=="cuda"),
79
- trust_remote_code=True
80
  )
81
- model = PeftModel.from_pretrained(base_model, SAVE_DIR)
82
  model.to(device)
83
  print("✅ LoRA weights auto-loaded from disk!")
84
  except Exception as e:
@@ -95,7 +117,7 @@ CORS(app)
95
  trainer = None
96
  current_dataset = None
97
 
98
- # Fake DB
99
  users_db = {
100
  "1": {"name": "Ahmed Elsayed", "role": "Student"},
101
  "2": {"name": "Mona Ali", "role": "Lawyer"},
@@ -103,53 +125,61 @@ users_db = {
103
  }
104
 
105
  # ===============================
106
- # 7a️⃣ توليد نصوص
107
  # ===============================
108
- @app.route("/generate", methods=["POST"])
109
  def generate():
110
  try:
111
- data = request.json
112
- prompt = data.get("prompt", "").strip()
113
  user_id = str(data.get("userId", "")).strip()
114
 
 
 
 
115
  user_info = users_db.get(user_id)
116
  if not user_info:
117
  return jsonify({"error": "User not found"}), 404
118
 
119
  user_name = user_info["name"]
120
 
121
- # if current_dataset is None:
122
- # return jsonify({
123
- # "messages": [
124
- # {"role": "assistant", "content": f"⚖️ أهلاً {user_name}! أنا زين، مساعدك الذكي. برجاء طرح سؤال قانوني."}
125
- # ]
126
- # })
127
-
128
- # Search in dataset
129
- for item in current_dataset:
130
- lines = item["text"].split("\n")
131
- for line in lines:
132
- if line.lower().startswith("user:") and prompt in line:
133
- response_messages = []
134
- for l in lines:
135
- if ":" in l:
136
- role, content = l.split(":", 1)
137
- response_messages.append({
138
- "role": role.strip().lower(),
139
- "content": content.strip()
140
- })
141
- response_messages.insert(0, {"role": "assistant", "content": f"أهلاً {user_name} 👋"})
142
- return jsonify({"messages": response_messages})
143
-
144
- # Fallback: generate
145
- inputs = tokenizer(prompt, return_tensors="pt").to(device)
146
  outputs = model.generate(
147
- **inputs,
 
148
  max_new_tokens=150,
149
  temperature=0.7,
150
- do_sample=True
 
151
  )
152
- reply = tokenizer.decode(outputs[0], skip_special_tokens=True)
 
 
 
153
 
154
  return jsonify({
155
  "messages": [
@@ -162,15 +192,16 @@ def generate():
162
  return jsonify({"messages": [{"role": "assistant", "content": f"خطأ: {str(e)}"}]})
163
 
164
  # ===============================
165
- # 7b️⃣ تحميل الداتا
166
  # ===============================
 
167
  def load_dataset():
168
  global current_dataset
169
  if os.path.exists(DATASET_FILE):
170
  with open(DATASET_FILE, "r", encoding="utf-8") as f:
171
  try:
172
  data = json.load(f)
173
- except:
174
  data = []
175
 
176
  if data:
@@ -183,8 +214,12 @@ def load_dataset():
183
  formatted.append({"text": conversation.strip()})
184
  elif "text" in item:
185
  formatted.append({"text": item["text"]})
186
- current_dataset = Dataset.from_list(formatted)
187
- print(f"✅ Loaded {len(formatted)} records from dataset.json")
 
 
 
 
188
  else:
189
  current_dataset = None
190
  else:
@@ -193,7 +228,7 @@ def load_dataset():
193
  load_dataset()
194
 
195
  # ===============================
196
- # 7c️⃣ تدريب LoRA (Async)
197
  # ===============================
198
  @app.route("/train", methods=["POST"])
199
  def train():
@@ -203,7 +238,7 @@ def train():
203
  epochs = 1
204
  batch_size = 1
205
 
206
- # 1️⃣ JSON مباشر
207
  if request.is_json:
208
  body = request.get_json()
209
  if isinstance(body, list):
@@ -216,7 +251,6 @@ def train():
216
  epochs = int(body.get("epochs", 1)) if isinstance(body, dict) else 1
217
  batch_size = int(body.get("batch_size", 1)) if isinstance(body, dict) else 1
218
 
219
- # 2️⃣ form-data ملف
220
  else:
221
  file = request.files.get("file")
222
  if file:
@@ -232,7 +266,7 @@ def train():
232
  with open(DATASET_FILE, "r", encoding="utf-8") as f:
233
  try:
234
  old_data = json.load(f)
235
- except:
236
  old_data = []
237
 
238
  combined = old_data + data
@@ -265,8 +299,9 @@ def train():
265
 
266
  current_dataset = Dataset.from_list(formatted)
267
 
 
268
  def tokenize_function(examples):
269
- return tokenizer(examples["text"], truncation=True, padding="max_length", max_length=256)
270
 
271
  tokenized_dataset = current_dataset.map(tokenize_function, batched=True, remove_columns=["text"])
272
 
@@ -276,18 +311,29 @@ def train():
276
  num_train_epochs=epochs,
277
  logging_steps=2,
278
  save_strategy="epoch",
279
- fp16=torch.cuda.is_available(),
280
- report_to=None
281
  )
282
 
283
  trainer = Trainer(model=model, args=training_args, train_dataset=tokenized_dataset)
284
 
285
  # تشغيل التدريب في Thread منفصل
286
  def run_training():
287
- print("🔄 Training started...")
288
- trainer.train()
289
- trainer.save_model(SAVE_DIR)
290
- print("✅ Training completed & model saved!")
 
 
 
 
 
 
 
 
 
 
 
291
 
292
  threading.Thread(target=run_training, daemon=True).start()
293
 
@@ -301,11 +347,10 @@ def train():
301
  except Exception as e:
302
  return jsonify({"error": str(e)})
303
 
304
-
305
  # ===============================
306
- # 8️⃣ تشغيل Flask
307
  # ===============================
308
- @app.route("/", methods=["GET"])
309
  def home():
310
  return jsonify({
311
  "status": "ok",
@@ -313,5 +358,9 @@ def home():
313
  "endpoints": ["/generate", "/train"]
314
  })
315
 
 
 
 
316
  if __name__ == "__main__":
 
317
  app.run(host="0.0.0.0", port=FLASK_PORT, threaded=True)
 
1
+ # flask_lora_server.py
2
+ # نسخة مُحسّنة من سكربت Flask لتشغيل موديل أساسي + LoRA + تدريب + توليد
3
+ # تتضمن تحسينات: فحص bitsandbytes، تحميل وحفظ LoRA آمن، توليد آمن، وحماية أساسية (قابلة للتفعيل لاحقًا)
4
+
5
  import os
6
+ import json
7
+ import threading
8
+ import torch
9
+ from flask import Flask, request, jsonify
10
+ from flask_cors import CORS
11
 
12
+ # Transformers / PEFT
13
  from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments
14
  from peft import LoraConfig, get_peft_model, TaskType, PeftModel
 
 
 
15
  from datasets import Dataset
16
 
17
  # ===============================
18
+ # 1️⃣ إعداد المسارات والمتغيرات
19
  # ===============================
20
  MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct"
21
  FLASK_PORT = 7860
 
28
  DATASET_FILE = os.path.join(DATA_DIR, "dataset.json")
29
 
30
  # ===============================
31
+ # 2️⃣ تحديد الجهاز ووجود bitsandbytes
32
  # ===============================
33
  device = "cuda" if torch.cuda.is_available() else "cpu"
34
  print("🔹 Using device:", device)
35
 
36
+ use_8bit = False
37
+ if device == "cuda":
38
+ try:
39
+ import bitsandbytes # noqa
40
+ use_8bit = True
41
+ print("🔸 bitsandbytes available: enabling 8-bit loading")
42
+ except Exception as e:
43
+ print("⚠️ bitsandbytes not available — will not use 8-bit:", e)
44
+
45
  # ===============================
46
+ # 3️⃣ تحميل الـ Tokenizer
47
  # ===============================
48
+ print("🔄 Loading tokenizer...")
49
  tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
50
 
51
+ # ===============================
52
+ # 4️⃣ تحميل الموديل الأساسي (مع تعامل آمن للـ 8-bit)
53
+ # ===============================
54
+ print("🔄 Loading base model...")
55
  try:
56
+ base_model = AutoModelForCausalLM.from_pretrained(
57
  MODEL_NAME,
58
+ device_map="auto" if device == "cuda" else {"": "cpu"},
59
+ torch_dtype=torch.float16 if device == "cuda" else torch.float32,
60
+ load_in_8bit=use_8bit,
61
+ trust_remote_code=True,
62
  )
63
+ base_model.to(device)
64
  print("✅ Base model loaded!")
65
  except Exception as e:
66
  print("⚠️ Failed to load base model:", e)
67
  raise e
68
 
69
  # ===============================
70
+ # 5️⃣ إعداد LoRA على الموديل
71
  # ===============================
72
  lora_config = LoraConfig(
73
  task_type=TaskType.CAUSAL_LM,
 
76
  lora_dropout=0.05,
77
  target_modules=["q_proj", "v_proj"]
78
  )
79
+
80
+ # Wrap base model with PEFT LoRA
81
+ try:
82
+ model = get_peft_model(base_model, lora_config)
83
+ model.to(device)
84
+ print("✅ LoRA model ready on device:", device)
85
+ except Exception as e:
86
+ print("⚠️ Failed to apply LoRA wrapper:", e)
87
+ raise
88
 
89
  # ===============================
90
+ # 6️⃣ محاولة تحميل أوزان LoRA محفوظة (آمنة)
91
  # ===============================
92
+ if os.path.isdir(SAVE_DIR) and any(os.scandir(SAVE_DIR)):
93
  try:
94
+ print("🔄 Attempting to load LoRA from", SAVE_DIR)
95
+ # reload base model (fresh) and then load peft weights
96
+ base_model_for_peft = AutoModelForCausalLM.from_pretrained(
97
  MODEL_NAME,
98
+ device_map="auto" if device == "cuda" else {"": "cpu"},
99
+ torch_dtype=torch.float16 if device == "cuda" else torch.float32,
100
+ load_in_8bit=use_8bit,
101
+ trust_remote_code=True,
102
  )
103
+ model = PeftModel.from_pretrained(base_model_for_peft, SAVE_DIR, device_map="auto" if device == "cuda" else {"": "cpu"})
104
  model.to(device)
105
  print("✅ LoRA weights auto-loaded from disk!")
106
  except Exception as e:
 
117
  trainer = None
118
  current_dataset = None
119
 
120
+ # Fake DB — غيّر أو اربطه بقاعدة حقيقية لاحقًا
121
  users_db = {
122
  "1": {"name": "Ahmed Elsayed", "role": "Student"},
123
  "2": {"name": "Mona Ali", "role": "Lawyer"},
 
125
  }
126
 
127
  # ===============================
128
+ # 7a️⃣ دالة توليد النصوص — محسّنة
129
  # ===============================
130
+ @app.route("/generate", methods=["POST"])
131
  def generate():
132
  try:
133
+ data = request.json or {}
134
+ prompt = (data.get("prompt") or "").strip()
135
  user_id = str(data.get("userId", "")).strip()
136
 
137
+ if not prompt:
138
+ return jsonify({"error": "Empty prompt"}), 400
139
+
140
  user_info = users_db.get(user_id)
141
  if not user_info:
142
  return jsonify({"error": "User not found"}), 404
143
 
144
  user_name = user_info["name"]
145
 
146
+ # البحث في الداتا إذا كانت موجودة
147
+ if current_dataset is not None:
148
+ prompt_lower = prompt.lower()
149
+ for item in current_dataset:
150
+ text = item.get("text", "")
151
+ lines = text.split("\n")
152
+ for line in lines:
153
+ line_lower = line.lower()
154
+ if line_lower.startswith("user:") and prompt_lower in line_lower:
155
+ response_messages = []
156
+ for l in lines:
157
+ if ":" in l:
158
+ role, content = l.split(":", 1)
159
+ response_messages.append({
160
+ "role": role.strip().lower(),
161
+ "content": content.strip()
162
+ })
163
+ response_messages.insert(0, {"role": "assistant", "content": f"أهلاً {user_name} 👋"})
164
+ return jsonify({"messages": response_messages})
165
+
166
+ # Fallback: generate using النموذج
167
+ inputs = tokenizer(prompt, return_tensors="pt", truncation=True, padding=True).to(device)
168
+ input_ids = inputs["input_ids"]
169
+ attention_mask = inputs.get("attention_mask", None)
170
+
171
  outputs = model.generate(
172
+ input_ids=input_ids,
173
+ attention_mask=attention_mask,
174
  max_new_tokens=150,
175
  temperature=0.7,
176
+ do_sample=True,
177
+ pad_token_id=(getattr(tokenizer, 'eos_token_id', None) or getattr(tokenizer, 'pad_token_id', None))
178
  )
179
+
180
+ # اقتطاع جزء الـ prompt من الناتج للحصول على الجزء المولَّد فقط
181
+ generated_ids = outputs[0][input_ids.shape[-1]:]
182
+ reply = tokenizer.decode(generated_ids, skip_special_tokens=True).strip()
183
 
184
  return jsonify({
185
  "messages": [
 
192
  return jsonify({"messages": [{"role": "assistant", "content": f"خطأ: {str(e)}"}]})
193
 
194
  # ===============================
195
+ # 7b️⃣ تحميل الداتا من ملف dataset.json
196
  # ===============================
197
+
198
  def load_dataset():
199
  global current_dataset
200
  if os.path.exists(DATASET_FILE):
201
  with open(DATASET_FILE, "r", encoding="utf-8") as f:
202
  try:
203
  data = json.load(f)
204
+ except Exception:
205
  data = []
206
 
207
  if data:
 
214
  formatted.append({"text": conversation.strip()})
215
  elif "text" in item:
216
  formatted.append({"text": item["text"]})
217
+ try:
218
+ current_dataset = Dataset.from_list(formatted)
219
+ print(f"✅ Loaded {len(formatted)} records from dataset.json")
220
+ except Exception as e:
221
+ print("⚠️ Failed to create Dataset:", e)
222
+ current_dataset = None
223
  else:
224
  current_dataset = None
225
  else:
 
228
  load_dataset()
229
 
230
  # ===============================
231
+ # 7c️⃣ تدريب LoRA (تشغيل في Thread منفصل)
232
  # ===============================
233
  @app.route("/train", methods=["POST"])
234
  def train():
 
238
  epochs = 1
239
  batch_size = 1
240
 
241
+ # JSON body مباشر أو dict يحتوي على data
242
  if request.is_json:
243
  body = request.get_json()
244
  if isinstance(body, list):
 
251
  epochs = int(body.get("epochs", 1)) if isinstance(body, dict) else 1
252
  batch_size = int(body.get("batch_size", 1)) if isinstance(body, dict) else 1
253
 
 
254
  else:
255
  file = request.files.get("file")
256
  if file:
 
266
  with open(DATASET_FILE, "r", encoding="utf-8") as f:
267
  try:
268
  old_data = json.load(f)
269
+ except Exception:
270
  old_data = []
271
 
272
  combined = old_data + data
 
299
 
300
  current_dataset = Dataset.from_list(formatted)
301
 
302
+ # Tokenize — استخدم padding=True لتقليل استهلاك الذاكرة
303
  def tokenize_function(examples):
304
+ return tokenizer(examples["text"], truncation=True, padding=True, max_length=256)
305
 
306
  tokenized_dataset = current_dataset.map(tokenize_function, batched=True, remove_columns=["text"])
307
 
 
311
  num_train_epochs=epochs,
312
  logging_steps=2,
313
  save_strategy="epoch",
314
+ fp16=(device == "cuda"),
315
+ report_to=None,
316
  )
317
 
318
  trainer = Trainer(model=model, args=training_args, train_dataset=tokenized_dataset)
319
 
320
  # تشغيل التدريب في Thread منفصل
321
  def run_training():
322
+ try:
323
+ print("🔄 Training started...")
324
+ trainer.train()
325
+ # Trainer.save_model attempts to save the model; مع PEFT من الأفضل save_pretrained
326
+ try:
327
+ trainer.save_model(SAVE_DIR)
328
+ except Exception as e:
329
+ print("⚠️ trainer.save_model failed:", e)
330
+ try:
331
+ model.save_pretrained(SAVE_DIR)
332
+ except Exception as e:
333
+ print("⚠️ model.save_pretrained failed:", e)
334
+ print("✅ Training completed & model saved!")
335
+ except Exception as e:
336
+ print("❌ Training failed:", e)
337
 
338
  threading.Thread(target=run_training, daemon=True).start()
339
 
 
347
  except Exception as e:
348
  return jsonify({"error": str(e)})
349
 
 
350
  # ===============================
351
+ # 8️⃣ Endpoint بسيط للفحص
352
  # ===============================
353
+ @app.route("/", methods=["GET"])
354
  def home():
355
  return jsonify({
356
  "status": "ok",
 
358
  "endpoints": ["/generate", "/train"]
359
  })
360
 
361
+ # ===============================
362
+ # 9️⃣ تشغيل Flask
363
+ # ===============================
364
  if __name__ == "__main__":
365
+ # ملاحظة أمنيّة: لا تشغّل هذا السكربت مكشوفًا للعالم دون طبقة مصادقة أو proxy
366
  app.run(host="0.0.0.0", port=FLASK_PORT, threaded=True)