Derr11 commited on
Commit
9c85028
·
verified ·
1 Parent(s): 7c953da

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +159 -122
app.py CHANGED
@@ -7,25 +7,46 @@ import spaces
7
  from transformers import Qwen3OmniMoeForConditionalGeneration, Qwen3OmniMoeProcessor
8
  from qwen_omni_utils import process_mm_info
9
 
10
- # ==========================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  # إعدادات عامة
12
- # ==========================
13
 
14
  MODEL_PATH = os.getenv("MODEL_PATH", "Qwen/Qwen3-Omni-30B-A3B-Instruct")
15
- USE_AUDIO_IN_VIDEO = True # استخدام الصوت داخل الفيديو لو وجد
16
 
17
  VOICE_CHOICES = ["Ethan", "Chelsie", "Aiden"]
18
  DEFAULT_VOICE = "Ethan"
19
 
20
- # تحميل كسول
21
  model = None
22
  processor = None
23
 
24
 
25
  def load_model():
26
  """
27
- تحميل Qwen3-Omni والمعالج عند أول استدعاء فقط (على ZeroGPU).
28
- تم إلغاء flash_attention_2 و device_map='auto' لتجنب مشاكل الاستدعاء.
 
29
  """
30
  global model, processor
31
 
@@ -34,7 +55,7 @@ def load_model():
34
 
35
  print(f"[ZeroGPU] Loading model from: {MODEL_PATH}")
36
 
37
- # نحدد نوع البيانات والجهاز
38
  if torch.cuda.is_available():
39
  torch_dtype = torch.bfloat16
40
  device = "cuda"
@@ -42,13 +63,12 @@ def load_model():
42
  torch_dtype = torch.float32
43
  device = "cpu"
44
 
45
- # تحميل النموذج بدون flash_attention_2 ولا device_map="auto"
46
  local_model = Qwen3OmniMoeForConditionalGeneration.from_pretrained(
47
  MODEL_PATH,
48
  torch_dtype=torch_dtype,
49
- attn_implementation="eager", # الأكثر أماناً في هذه البيئة
50
  )
51
-
52
  local_model.to(device)
53
 
54
  local_processor = Qwen3OmniMoeProcessor.from_pretrained(MODEL_PATH)
@@ -58,33 +78,47 @@ def load_model():
58
  print(f"[ZeroGPU] Model loaded on {device} with dtype {torch_dtype}.")
59
 
60
 
61
- def build_messages_from_history(history, system_prompt, user_text, image, audio_path, video_path):
 
 
 
 
 
 
 
62
  """
63
- تحويل تاريخ الدردشة + المدخل الحالي إلى تنسيق الرسائل المطلوب من Qwen3-Omni.
 
64
  history: list of [user_text, assistant_text]
65
  """
66
  messages = []
67
 
68
  if system_prompt:
69
- messages.append({
70
- "role": "system",
71
- "content": [{"type": "text", "text": system_prompt}],
72
- })
 
 
73
 
74
  # تاريخ المحادثة
75
  for user_msg, assistant_msg in history:
76
  if user_msg:
77
- messages.append({
78
- "role": "user",
79
- "content": [{"type": "text", "text": user_msg}],
80
- })
 
 
81
  if assistant_msg:
82
- messages.append({
83
- "role": "assistant",
84
- "content": [{"type": "text", "text": assistant_msg}],
85
- })
 
 
86
 
87
- # الرسالة الحالية (وسائط + نص)
88
  user_content = []
89
 
90
  if image is not None:
@@ -100,17 +134,19 @@ def build_messages_from_history(history, system_prompt, user_text, image, audio_
100
  user_content.append({"type": "text", "text": user_text.strip()})
101
 
102
  if user_content:
103
- messages.append({
104
- "role": "user",
105
- "content": user_content,
106
- })
 
 
107
 
108
  return messages
109
 
110
 
111
- # ==========================
112
- # دالة الاستدلال (ZeroGPU)
113
- # ==========================
114
 
115
  @spaces.GPU(duration=120)
116
  def qwen3_omni_inference(
@@ -127,13 +163,13 @@ def qwen3_omni_inference(
127
  max_tokens,
128
  ):
129
  """
130
- تنفيذ الاستدلال الفعلي على ZeroGPU:
131
- - نص + صورة + صوت + فيديو
132
- - مخرج نصي دائماً، وصوتي عند الحاجة
133
  """
134
 
 
135
  if not (user_text or image is not None or audio_path or video_path):
136
- # لا يوجد مدخل من المستخدم
137
  return history, None, "", None, None, None
138
 
139
  load_model()
@@ -148,14 +184,14 @@ def qwen3_omni_inference(
148
  video_path=video_path,
149
  )
150
 
151
- # بناء النص من المحادثة (chat template)
152
  text_prompt = processor.apply_chat_template(
153
  messages,
154
  add_generation_prompt=True,
155
  tokenize=False,
156
  )
157
 
158
- # تجهيز الوسائط المتعددة (صوت/صورة/فيديو)
159
  audios, images, videos = process_mm_info(
160
  messages,
161
  use_audio_in_video=USE_AUDIO_IN_VIDEO,
@@ -172,13 +208,13 @@ def qwen3_omni_inference(
172
  use_audio_in_video=USE_AUDIO_IN_VIDEO,
173
  )
174
 
175
- # نقل المدخلات إلى نفس الجهاز ونفس dtype للنموذج
176
  first_param = next(model.parameters())
177
  device = first_param.device
178
  dtype = first_param.dtype
179
  inputs = inputs.to(device=device, dtype=dtype)
180
 
181
- # بارامترات التوليد
182
  gen_kwargs = dict(
183
  temperature=float(temperature),
184
  top_p=float(top_p),
@@ -187,16 +223,16 @@ def qwen3_omni_inference(
187
  use_audio_in_video=USE_AUDIO_IN_VIDEO,
188
  )
189
 
190
- # توليد النص فقط أو نص + صوت
191
  if not return_audio:
192
  gen_kwargs["return_audio"] = False
193
- text_ids, _ = model.generate(**inputs, **gen_kwargs)
194
  audio_out = None
195
  else:
196
  gen_kwargs["speaker"] = speaker
197
  text_ids, audio_out = model.generate(**inputs, **gen_kwargs)
198
 
199
- # فك ترميز النص الناتج
200
  input_len = inputs["input_ids"].shape[1]
201
  generated_text = processor.batch_decode(
202
  text_ids.sequences[:, input_len:],
@@ -205,10 +241,12 @@ def qwen3_omni_inference(
205
  )[0]
206
 
207
  # تحديث تاريخ الدردشة
208
- user_display = user_text if (user_text and user_text.strip()) else "[Multimodal message]"
 
 
209
  history = history + [[user_display, generated_text]]
210
 
211
- # تجهيز الصوت لمخرج Gradio (إن وجد)
212
  gr_audio = None
213
  if audio_out is not None:
214
  audio_np = audio_out.reshape(-1).detach().cpu().numpy()
@@ -219,12 +257,12 @@ def qwen3_omni_inference(
219
  return history, gr_audio, "", None, None, None
220
 
221
 
222
- # ==========================
223
- # دوال واجهة
224
- # ==========================
225
 
226
  def clear_chat():
227
- """مسح المحادثة والصوت."""
228
  return [], None
229
 
230
 
@@ -236,15 +274,15 @@ def create_interface():
236
  """
237
  <h1 style="text-align:center;">Qwen3-Omni-30B-A3B – ZeroGPU Chat</h1>
238
  <p style="text-align:center;">
239
- دردشة متعددة الوسائط (نص + صورة + صوت + فيديو) تعمل على ZeroGPU.
240
- استخدم حقل الرسالة مع المرفقات بالأسفل ثم اضغط <b>إرسال</b> أو Enter.
241
  (لإضافة سطر جديد استخدم Shift+Enter)
242
  </p>
243
  """
244
  )
245
 
246
  with gr.Row():
247
- # عمود المحادثة
248
  with gr.Column(scale=3):
249
  chatbot = gr.Chatbot(
250
  label="المحادثة",
@@ -257,7 +295,6 @@ def create_interface():
257
  autoplay=True,
258
  )
259
 
260
- # منطقة الإدخال أسفل الشات
261
  with gr.Row():
262
  user_text = gr.Textbox(
263
  label="رسالتك",
@@ -286,7 +323,7 @@ def create_interface():
286
  send_btn = gr.Button("إرسال", variant="primary", scale=2)
287
  clear_btn = gr.Button("مسح المحادثة", variant="secondary")
288
 
289
- # عمود الإعدادات
290
  with gr.Column(scale=1):
291
  gr.Markdown("### إعدادات النموذج")
292
 
@@ -335,78 +372,78 @@ def create_interface():
335
 
336
  gr.Markdown(
337
  """
338
- **ملاحظات الاستخدام:**
339
- - يمكنك إرسال نص فقط، أو نص مع صورة/صوت/فيديو في نفس الرسالة.
340
- - اضغط Enter للإرسال، وShift+Enter لسطر جديد.
341
- - ZeroGPU قد يستغرق عدة ثوانٍ لكل رد حسب طول الرسالة.
342
  """
343
  )
344
 
345
- # حالة المحادثة
346
- history_state = gr.State([])
347
-
348
- # دالة مشتركة للإرسال (زر + Enter)
349
- send_inputs = [
350
- history_state,
351
- user_text,
352
- image_input,
353
- audio_input,
354
- video_input,
355
- system_prompt,
356
- return_audio,
357
- speaker,
358
- temperature,
359
- top_p,
360
- max_tokens,
361
- ]
362
-
363
- send_outputs = [
364
- history_state,
365
- audio_output,
366
- user_text,
367
- image_input,
368
- audio_input,
369
- video_input,
370
- ]
371
-
372
- # زر إرسال
373
- send_btn.click(
374
- fn=qwen3_omni_inference,
375
- inputs=send_inputs,
376
- outputs=send_outputs,
377
- queue=True,
378
- ).then(
379
- lambda h: h,
380
- inputs=history_state,
381
- outputs=chatbot,
382
- )
383
 
384
- # إرسال بالـ Enter من داخل الـ Textbox
385
- user_text.submit(
386
- fn=qwen3_omni_inference,
387
- inputs=send_inputs,
388
- outputs=send_outputs,
389
- queue=True,
390
- ).then(
391
- lambda h: h,
392
- inputs=history_state,
393
- outputs=chatbot,
394
- )
395
 
396
- # مسح المحادثة
397
- clear_btn.click(
398
- fn=clear_chat,
399
- inputs=None,
400
- outputs=[history_state, audio_output],
401
- ).then(
402
- lambda: [],
403
- inputs=None,
404
- outputs=chatbot,
405
- ).then(
406
- lambda: ("", None, None),
407
- inputs=None,
408
- outputs=[user_text, image_input, audio_input],
409
- )
410
 
411
  return demo
412
 
@@ -414,5 +451,5 @@ def create_interface():
414
  demo = create_interface()
415
 
416
  if __name__ == "__main__":
417
- # نغلق SSR لأنه تجريبي ويسبب أحياناً مشاكل عرض
418
  demo.launch(ssr_mode=False)
 
7
  from transformers import Qwen3OmniMoeForConditionalGeneration, Qwen3OmniMoeProcessor
8
  from qwen_omni_utils import process_mm_info
9
 
10
+ # =========================================================
11
+ # Patch لتجاوز مشكلة lm_head في Qwen3OmniMoeTalker*
12
+ # =========================================================
13
+
14
+ def _patched_mark_tied_weights_as_initialized(self):
15
+ """
16
+ بعض إصدارات transformers + Qwen3-Omni تسبب خطأ:
17
+ Qwen3OmniMoeTalkerForConditionalGeneration has no attribute `lm_head`
18
+ عند محاولة ربط الـ tied weights.
19
+ هذا الـ patch يجعل هذه الخطوة no-op (لا تقوم بشيء)،
20
+ وهو آمن للاستنتاج (inference) في معظم الحالات.
21
+ """
22
+ return
23
+
24
+ # تطبيق الـ patch قبل أي استدعاء لـ from_pretrained
25
+ if hasattr(Qwen3OmniMoeForConditionalGeneration, "mark_tied_weights_as_initialized"):
26
+ Qwen3OmniMoeForConditionalGeneration.mark_tied_weights_as_initialized = (
27
+ _patched_mark_tied_weights_as_initialized
28
+ )
29
+
30
+ # =========================================================
31
  # إعدادات عامة
32
+ # =========================================================
33
 
34
  MODEL_PATH = os.getenv("MODEL_PATH", "Qwen/Qwen3-Omni-30B-A3B-Instruct")
35
+ USE_AUDIO_IN_VIDEO = True # استخدام الصوت داخل الفيديو إذا وجد
36
 
37
  VOICE_CHOICES = ["Ethan", "Chelsie", "Aiden"]
38
  DEFAULT_VOICE = "Ethan"
39
 
40
+ # سنحمّل النموذج كسولياً (عند أول استدعاء فقط)
41
  model = None
42
  processor = None
43
 
44
 
45
  def load_model():
46
  """
47
+ تحميل Qwen3-Omni والمعالج عند أول استدعاء فقط.
48
+ - نستخدم attn_implementation="eager" لتفادي الحاجة لـ flash-attn.
49
+ - لا نستخدم device_map="auto" لتفادي مشاكل توزيع الذاكرة على ZeroGPU.
50
  """
51
  global model, processor
52
 
 
55
 
56
  print(f"[ZeroGPU] Loading model from: {MODEL_PATH}")
57
 
58
+ # اختيار نوع البيانات والجهاز
59
  if torch.cuda.is_available():
60
  torch_dtype = torch.bfloat16
61
  device = "cuda"
 
63
  torch_dtype = torch.float32
64
  device = "cpu"
65
 
66
+ # تحميل النموذج (بدون flash_attention_2)
67
  local_model = Qwen3OmniMoeForConditionalGeneration.from_pretrained(
68
  MODEL_PATH,
69
  torch_dtype=torch_dtype,
70
+ attn_implementation="eager", # آمن على ZeroGPU بدون flash-attn
71
  )
 
72
  local_model.to(device)
73
 
74
  local_processor = Qwen3OmniMoeProcessor.from_pretrained(MODEL_PATH)
 
78
  print(f"[ZeroGPU] Model loaded on {device} with dtype {torch_dtype}.")
79
 
80
 
81
+ def build_messages_from_history(
82
+ history,
83
+ system_prompt,
84
+ user_text,
85
+ image,
86
+ audio_path,
87
+ video_path,
88
+ ):
89
  """
90
+ تحويل تاريخ الدردشة + المدخل الحالي إلى conversation بالـ format
91
+ المطلوب من Qwen3-Omni.
92
  history: list of [user_text, assistant_text]
93
  """
94
  messages = []
95
 
96
  if system_prompt:
97
+ messages.append(
98
+ {
99
+ "role": "system",
100
+ "content": [{"type": "text", "text": system_prompt}],
101
+ }
102
+ )
103
 
104
  # تاريخ المحادثة
105
  for user_msg, assistant_msg in history:
106
  if user_msg:
107
+ messages.append(
108
+ {
109
+ "role": "user",
110
+ "content": [{"type": "text", "text": user_msg}],
111
+ }
112
+ )
113
  if assistant_msg:
114
+ messages.append(
115
+ {
116
+ "role": "assistant",
117
+ "content": [{"type": "text", "text": assistant_msg}],
118
+ }
119
+ )
120
 
121
+ # محتوى رسالة المستخدم الحالية
122
  user_content = []
123
 
124
  if image is not None:
 
134
  user_content.append({"type": "text", "text": user_text.strip()})
135
 
136
  if user_content:
137
+ messages.append(
138
+ {
139
+ "role": "user",
140
+ "content": user_content,
141
+ }
142
+ )
143
 
144
  return messages
145
 
146
 
147
+ # =========================================================
148
+ # دالة الاستدلال (تعمل على ZeroGPU)
149
+ # =========================================================
150
 
151
  @spaces.GPU(duration=120)
152
  def qwen3_omni_inference(
 
163
  max_tokens,
164
  ):
165
  """
166
+ - تنفيذ الاستدلال على ZeroGPU.
167
+ - يدعم نص + صورة + صوت + فيديو في نفس الرسالة.
168
+ - مخرج نصي دائماً، ومخرج صوتي اختياري.
169
  """
170
 
171
+ # في حالة عدم وجود مداخل من المستخدم
172
  if not (user_text or image is not None or audio_path or video_path):
 
173
  return history, None, "", None, None, None
174
 
175
  load_model()
 
184
  video_path=video_path,
185
  )
186
 
187
+ # بناء نص المحادثة باستخدام chat_template
188
  text_prompt = processor.apply_chat_template(
189
  messages,
190
  add_generation_prompt=True,
191
  tokenize=False,
192
  )
193
 
194
+ # تجهيز الوسائط المتعددة
195
  audios, images, videos = process_mm_info(
196
  messages,
197
  use_audio_in_video=USE_AUDIO_IN_VIDEO,
 
208
  use_audio_in_video=USE_AUDIO_IN_VIDEO,
209
  )
210
 
211
+ # نقل إلى جهاز النموذج ونفس dtype
212
  first_param = next(model.parameters())
213
  device = first_param.device
214
  dtype = first_param.dtype
215
  inputs = inputs.to(device=device, dtype=dtype)
216
 
217
+ # إعدادات التوليد
218
  gen_kwargs = dict(
219
  temperature=float(temperature),
220
  top_p=float(top_p),
 
223
  use_audio_in_video=USE_AUDIO_IN_VIDEO,
224
  )
225
 
226
+ # توليد نص فقط أو نص + صوت
227
  if not return_audio:
228
  gen_kwargs["return_audio"] = False
229
+ text_ids, audio_out = model.generate(**inputs, **gen_kwargs)
230
  audio_out = None
231
  else:
232
  gen_kwargs["speaker"] = speaker
233
  text_ids, audio_out = model.generate(**inputs, **gen_kwargs)
234
 
235
+ # استخراج النص الناتج (بدون مدخل prompt)
236
  input_len = inputs["input_ids"].shape[1]
237
  generated_text = processor.batch_decode(
238
  text_ids.sequences[:, input_len:],
 
241
  )[0]
242
 
243
  # تحديث تاريخ الدردشة
244
+ user_display = (
245
+ user_text if (user_text and user_text.strip()) else "[Multimodal message]"
246
+ )
247
  history = history + [[user_display, generated_text]]
248
 
249
+ # تجهيز الصوت الناتج إن وجد
250
  gr_audio = None
251
  if audio_out is not None:
252
  audio_np = audio_out.reshape(-1).detach().cpu().numpy()
 
257
  return history, gr_audio, "", None, None, None
258
 
259
 
260
+ # =========================================================
261
+ # دوال واجهة Gradio
262
+ # =========================================================
263
 
264
  def clear_chat():
265
+ """إعادة تعيين المحادثة ومخرج الصوت."""
266
  return [], None
267
 
268
 
 
274
  """
275
  <h1 style="text-align:center;">Qwen3-Omni-30B-A3B – ZeroGPU Chat</h1>
276
  <p style="text-align:center;">
277
+ دردشة متعددة الوسائط (نص + صورة + صوت + فيديو) تعمل على ZeroGPU.<br/>
278
+ اكتب رسالتك، ويمكنك إضافة صورة/صوت/فيديو، ثم اضغط <b>إرسال</b> أو Enter.<br/>
279
  (لإضافة سطر جديد استخدم Shift+Enter)
280
  </p>
281
  """
282
  )
283
 
284
  with gr.Row():
285
+ # العمود الأيسر: المحادثة
286
  with gr.Column(scale=3):
287
  chatbot = gr.Chatbot(
288
  label="المحادثة",
 
295
  autoplay=True,
296
  )
297
 
 
298
  with gr.Row():
299
  user_text = gr.Textbox(
300
  label="رسالتك",
 
323
  send_btn = gr.Button("إرسال", variant="primary", scale=2)
324
  clear_btn = gr.Button("مسح المحادثة", variant="secondary")
325
 
326
+ # العمود الأيمن: الإعدادات
327
  with gr.Column(scale=1):
328
  gr.Markdown("### إعدادات النموذج")
329
 
 
372
 
373
  gr.Markdown(
374
  """
375
+ **ملاحظات:**
376
+ - يمكنك إرسال نص فقط، أو نص مع صورة/صوت/فيديو في رسالة واحدة.
377
+ - Enter للإرسال، وShift+Enter لسطر جديد.
378
+ - تشغيل النموذج على ZeroGPU قد يستغرق عدة ثوانٍ حسب طول الرسالة.
379
  """
380
  )
381
 
382
+ # حالة المحادثة
383
+ history_state = gr.State([])
384
+
385
+ # مدخلات دالة الإرسال
386
+ send_inputs = [
387
+ history_state,
388
+ user_text,
389
+ image_input,
390
+ audio_input,
391
+ video_input,
392
+ system_prompt,
393
+ return_audio,
394
+ speaker,
395
+ temperature,
396
+ top_p,
397
+ max_tokens,
398
+ ]
399
+
400
+ send_outputs = [
401
+ history_state,
402
+ audio_output,
403
+ user_text,
404
+ image_input,
405
+ audio_input,
406
+ video_input,
407
+ ]
408
+
409
+ # إرسال بالزر
410
+ send_btn.click(
411
+ fn=qwen3_omni_inference,
412
+ inputs=send_inputs,
413
+ outputs=send_outputs,
414
+ queue=True,
415
+ ).then(
416
+ lambda h: h,
417
+ inputs=history_state,
418
+ outputs=chatbot,
419
+ )
420
 
421
+ # إرسال بالـ Enter من Textbox
422
+ user_text.submit(
423
+ fn=qwen3_omni_inference,
424
+ inputs=send_inputs,
425
+ outputs=send_outputs,
426
+ queue=True,
427
+ ).then(
428
+ lambda h: h,
429
+ inputs=history_state,
430
+ outputs=chatbot,
431
+ )
432
 
433
+ # مسح المحادثة
434
+ clear_btn.click(
435
+ fn=clear_chat,
436
+ inputs=None,
437
+ outputs=[history_state, audio_output],
438
+ ).then(
439
+ lambda: [],
440
+ inputs=None,
441
+ outputs=chatbot,
442
+ ).then(
443
+ lambda: ("", None, None),
444
+ inputs=None,
445
+ outputs=[user_text, image_input, audio_input],
446
+ )
447
 
448
  return demo
449
 
 
451
  demo = create_interface()
452
 
453
  if __name__ == "__main__":
454
+ # إيقاف SSR لأنه تجريبي وقد يسبب مشكلة "Starting..."
455
  demo.launch(ssr_mode=False)