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