Derr11 commited on
Commit
38e2f41
·
verified ·
1 Parent(s): 8117e0e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +618 -1
app.py CHANGED
@@ -1 +1,618 @@
1
- import os import numpy as np import torch import gradio as gr import spaces import warnings warnings.filterwarnings("ignore") from transformers import Qwen3OmniMoeForConditionalGeneration, Qwen3OmniMoeProcessor from qwen_omni_utils import process_mm_info # ========================================================= # Patches لتجاوز مشاكل التوافق في Qwen3-Omni # ========================================================= def _patched_mark_tied_weights_as_initialized(self): """ تجاوز مشكلة lm_head في tied weights """ return def _patched_init_weights(self, module): """ تجاوز مشكلة initializer_range في Qwen3OmniMoeTalkerConfig """ # نحاول الحصول على initializer_range، وإذا لم يكن موجود نستخدم قيمة افتراضية try: std = self.config.initializer_range except AttributeError: # قيمة افتراضية آمنة std = 0.02 # تطبيق التهيئة الأساسية if isinstance(module, torch.nn.Linear): module.weight.data.normal_(mean=0.0, std=std) if module.bias is not None: module.bias.data.zero_() elif isinstance(module, torch.nn.Embedding): module.weight.data.normal_(mean=0.0, std=std) if module.padding_idx is not None: module.weight.data[module.padding_idx].zero_() def _patched_initialize_weights(self): """ تجاوز كامل لدالة initialize_weights في حالة استمرار المشاكل """ # نحاول التهيئة العادية، وإذا فشلت نتجاهلها try: # محاولة استخدام الدالة الأصلية إذا كانت موجودة if hasattr(self, '_original_initialize_weights'): self._original_initialize_weights() else: # تهيئة بسيطة آمنة for module in self.modules(): if isinstance(module, (torch.nn.Linear, torch.nn.Embedding)): if hasattr(module, 'weight'): module.weight.data.normal_(mean=0.0, std=0.02) if hasattr(module, 'bias') and module.bias is not None: module.bias.data.zero_() except Exception as e: print(f"Warning: Could not initialize weights properly: {e}") # نستمر بدون تهيئة - النموذج المحمل مسبقاً يجب أن يعمل # تطبيق الـ patches قبل أي استدعاء لـ from_pretrained def apply_patches(): """تطبيق جميع الـ patches اللازمة""" # Patch 1: تجاوز مشكلة lm_head if hasattr(Qwen3OmniMoeForConditionalGeneration, "mark_tied_weights_as_initialized"): Qwen3OmniMoeForConditionalGeneration.mark_tied_weights_as_initialized = ( _patched_mark_tied_weights_as_initialized ) # Patch 2: تجاوز مشكلة initializer_range if hasattr(Qwen3OmniMoeForConditionalGeneration, "_init_weights"): Qwen3OmniMoeForConditionalGeneration._init_weights = _patched_init_weights # Patch 3: تجاوز initialize_weights بالكامل إذا لزم if hasattr(Qwen3OmniMoeForConditionalGeneration, "initialize_weights"): # حفظ الدالة الأصلية Qwen3OmniMoeForConditionalGeneration._original_initialize_weights = ( Qwen3OmniMoeForConditionalGeneration.initialize_weights ) Qwen3OmniMoeForConditionalGeneration.initialize_weights = _patched_initialize_weights # تطبيق الـ patches apply_patches() # ========================================================= # إعدادات عامة # ========================================================= MODEL_PATH = os.getenv("MODEL_PATH", "Qwen/Qwen3-Omni-30B-A3B-Instruct") USE_AUDIO_IN_VIDEO = True # استخدام الصوت داخل الفيديو إذا وجد VOICE_CHOICES = ["Ethan", "Chelsie", "Aiden"] DEFAULT_VOICE = "Ethan" # سنحمّل النموذج كسولياً (عند أول استدعاء فقط) model = None processor = None def load_model(): """ تحميل Qwen3-Omni والمعالج عند أول استدعاء فقط. - نستخدم attn_implementation="eager" لتفادي الحاجة لـ flash-attn. - نضيف low_cpu_mem_usage=True لتحسين الأداء - نضيف ignore_mismatched_sizes=True لتجاوز مشاكل الأحجام """ global model, processor if model is not None and processor is not None: return print(f"[ZeroGPU] Loading model from: {MODEL_PATH}") # اختيار نوع البيانات والجهاز if torch.cuda.is_available(): torch_dtype = torch.bfloat16 device = "cuda" else: torch_dtype = torch.float32 device = "cpu" try: # محاولة تحميل النموذج مع خيارات إضافية للأمان local_model = Qwen3OmniMoeForConditionalGeneration.from_pretrained( MODEL_PATH, torch_dtype=torch_dtype, attn_implementation="eager", # آمن على ZeroGPU بدون flash-attn low_cpu_mem_usage=True, # تحسين استخدام الذاكرة ignore_mismatched_sizes=True, # تجاوز مشاكل الأحجام trust_remote_code=True, # السماح بالكود المخصص ) # نقل النموذج إلى الجهاز المناسب local_model = local_model.to(device) # وضع النموذج في وضع التقييم (inference) local_model.eval() except Exception as e: print(f"Error loading model: {e}") print("Attempting alternative loading method...") # محاولة بديلة مع تعطيل _init_weights try: # تعطيل _init_weights مؤقتاً original_init_weights = None if hasattr(Qwen3OmniMoeForConditionalGeneration, "_init_weights"): original_init_weights = Qwen3OmniMoeForConditionalGeneration._init_weights Qwen3OmniMoeForConditionalGeneration._init_weights = lambda self, module: None local_model = Qwen3OmniMoeForConditionalGeneration.from_pretrained( MODEL_PATH, torch_dtype=torch_dtype, attn_implementation="eager", low_cpu_mem_usage=True, ) # استعادة _init_weights if original_init_weights: Qwen3OmniMoeForConditionalGeneration._init_weights = original_init_weights local_model = local_model.to(device) local_model.eval() except Exception as e2: raise RuntimeError(f"Failed to load model: {e2}") # تحميل المعالج try: local_processor = Qwen3OmniMoeProcessor.from_pretrained( MODEL_PATH, trust_remote_code=True ) except Exception as e: print(f"Error loading processor: {e}") raise model = local_model processor = local_processor print(f"[ZeroGPU] Model loaded successfully on {device} with dtype {torch_dtype}.") def build_messages_from_history( history, system_prompt, user_text, image, audio_path, video_path, ): """ تحويل تاريخ الدردشة + المدخل الحالي إلى conversation بالـ format المطلوب من Qwen3-Omni. history: list of [user_text, assistant_text] """ messages = [] if system_prompt: messages.append( { "role": "system", "content": [{"type": "text", "text": system_prompt}], } ) # تاريخ المحادثة for user_msg, assistant_msg in history: if user_msg: messages.append( { "role": "user", "content": [{"type": "text", "text": user_msg}], } ) if assistant_msg: messages.append( { "role": "assistant", "content": [{"type": "text", "text": assistant_msg}], } ) # محتوى رسالة المستخدم الحالية user_content = [] if image is not None: user_content.append({"type": "image", "image": image}) if audio_path is not None and audio_path != "": user_content.append({"type": "audio", "audio": audio_path}) if video_path is not None and video_path != "": user_content.append({"type": "video", "video": video_path}) if user_text and user_text.strip(): user_content.append({"type": "text", "text": user_text.strip()}) if user_content: messages.append( { "role": "user", "content": user_content, } ) return messages # ========================================================= # دالة الاستدلال (تعمل على ZeroGPU) # ========================================================= @spaces.GPU(duration=120) def qwen3_omni_inference( history, user_text, image, audio_path, video_path, system_prompt, return_audio, speaker, temperature, top_p, max_tokens, ): """ - تنفيذ الاستدلال على ZeroGPU. - يدعم نص + صورة + صوت + فيديو في نفس الرسالة. - مخرج نصي دائماً، ومخرج صوتي اختياري. """ # في حالة عدم وجود مداخل من المستخدم if not (user_text or image is not None or audio_path or video_path): return history, None, "", None, None, None try: load_model() global model, processor messages = build_messages_from_history( history=history, system_prompt=system_prompt, user_text=user_text, image=image, audio_path=audio_path, video_path=video_path, ) # بناء نص المحادثة باستخدام chat_template text_prompt = processor.apply_chat_template( messages, add_generation_prompt=True, tokenize=False, ) # تجهيز الوسائط المتعددة audios, images, videos = process_mm_info( messages, use_audio_in_video=USE_AUDIO_IN_VIDEO, ) # تحويل إلى تينسورات inputs = processor( text=text_prompt, audio=audios, images=images, videos=videos, return_tensors="pt", padding=True, use_audio_in_video=USE_AUDIO_IN_VIDEO, ) # نقل إلى جهاز النموذج ونفس dtype first_param = next(model.parameters()) device = first_param.device # تحويل المدخلات إلى الجهاز المناسب for key in inputs: if hasattr(inputs[key], 'to'): inputs[key] = inputs[key].to(device) # إعدادات التوليد gen_kwargs = dict( temperature=float(temperature) if temperature > 0 else 1e-7, top_p=float(top_p), max_new_tokens=int(max_tokens), do_sample=temperature > 0, use_audio_in_video=USE_AUDIO_IN_VIDEO, ) # إضافة thinker_return_dict_in_generate فقط إذا كان مدعوماً if hasattr(model, 'config') and hasattr(model.config, 'thinker_return_dict_in_generate'): gen_kwargs["thinker_return_dict_in_generate"] = True # توليد نص فقط أو نص + صوت with torch.no_grad(): if not return_audio: gen_kwargs["return_audio"] = False outputs = model.generate(**inputs, **gen_kwargs) text_ids = outputs audio_out = None else: gen_kwargs["speaker"] = speaker gen_kwargs["return_audio"] = True outputs = model.generate(**inputs, **gen_kwargs) if isinstance(outputs, tuple): text_ids, audio_out = outputs else: text_ids = outputs audio_out = None # استخراج النص الناتج (بدون مدخل prompt) input_len = inputs["input_ids"].shape[1] # التعامل مع الأنواع المختلفة من المخرجات if hasattr(text_ids, 'sequences'): generated_ids = text_ids.sequences[:, input_len:] else: generated_ids = text_ids[:, input_len:] generated_text = processor.batch_decode( generated_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False, )[0] # تحديث تاريخ الدردشة user_display = ( user_text if (user_text and user_text.strip()) else "[Multimodal message]" ) history = history + [[user_display, generated_text]] # تجهيز الصوت الناتج إن وجد gr_audio = None if audio_out is not None: try: audio_np = audio_out.reshape(-1).detach().cpu().numpy() sample_rate = 24000 gr_audio = (sample_rate, audio_np.astype(np.float32)) except Exception as e: print(f"Warning: Could not process audio output: {e}") gr_audio = None # نعيد: history الجديد + صوت الرد + تفريغ مدخلات المستخدم return history, gr_audio, "", None, None, None except Exception as e: print(f"Error during inference: {e}") import traceback traceback.print_exc() # في حالة الخطأ، نضيف رسالة خطأ للمحادثة user_display = ( user_text if (user_text and user_text.strip()) else "[Multimodal message]" ) error_message = f"عذراً، حدث خطأ أثناء معالجة الرسالة: {str(e)}" history = history + [[user_display, error_message]] return history, None, "", None, None, None # ========================================================= # دوال واجهة Gradio # ========================================================= def clear_chat(): """إعادة تعيين المحادثة ومخرج الصوت.""" return [], None def create_interface(): with gr.Blocks( title="Qwen3-Omni-30B-A3B – ZeroGPU Chat", theme=gr.themes.Soft(), ) as demo: gr.Markdown( """ <h1 style="text-align:center;">🤖 Qwen3-Omni-30B-A3B – ZeroGPU Chat</h1> <p style="text-align:center;"> دردشة متعددة الوسائط (نص + صورة + صوت + فيديو) تعمل على ZeroGPU.<br/> اكتب رسالتك، ويمكنك إضافة صورة/صوت/فيديو، ثم اضغط <b>إرسال</b> أو Enter.<br/> (لإضافة سطر جديد استخدم Shift+Enter) </p> """ ) with gr.Row(): # العمود الأيسر: المحادثة with gr.Column(scale=3): chatbot = gr.Chatbot( label="المحادثة", height=480, elem_id="chatbot", ) audio_output = gr.Audio( label="رد النموذج (صوت)", type="numpy", autoplay=True, visible=True, ) with gr.Row(): user_text = gr.Textbox( label="رسالتك", placeholder="اكتب رسالتك هنا (يمكنك أيضاً إرفاق صورة/صوت/فيديو من الأسفل)...", lines=3, show_label=False, elem_id="message", ) with gr.Row(): image_input = gr.Image( label="📷 صورة (اختياري)", type="pil", sources=["upload", "webcam"], height=150, ) audio_input = gr.Audio( label="🎙️ صوت (اختياري)", type="filepath", sources=["microphone", "upload"], ) video_input = gr.Video( label="🎬 فيديو (اختياري)", height=150, ) with gr.Row(): send_btn = gr.Button("إرسال", variant="primary", scale=2) clear_btn = gr.Button("مسح المحادثة", variant="secondary") # العمود الأيمن: الإعدادات with gr.Column(scale=1): gr.Markdown("### ⚙️ إعدادات النموذج") system_prompt = gr.Textbox( label="System Prompt", value="You are a helpful, multilingual assistant.", lines=4, placeholder="يمكنك التحكم في شخصية النموذج من هنا (اختياري).", ) return_audio = gr.Checkbox( label="تفعيل مخرج صوتي (النموذج يتكلم)؟", value=True, ) speaker = gr.Dropdown( label="صوت المتحدث (speaker)", choices=VOICE_CHOICES, value=DEFAULT_VOICE, ) with gr.Accordion("إعدادات متقدمة", open=False): temperature = gr.Slider( label="Temperature (العشوائية)", minimum=0.0, maximum=1.5, value=0.6, step=0.05, ) top_p = gr.Slider( label="Top-p (حجم العينة)", minimum=0.1, maximum=1.0, value=0.95, step=0.05, ) max_tokens = gr.Slider( label="Max new tokens (طول الرد الأقصى)", minimum=16, maximum=1024, value=384, step=16, ) gr.Markdown( """ **📝 ملاحظات:** - يمكنك إرسال نص فقط، أو نص مع صورة/صوت/فيديو في رسالة واحدة - Enter للإرسال، Shift+Enter لسطر جديد - تشغيل النموذج على ZeroGPU قد يستغرق عدة ثوانٍ حسب طول الرسالة - النموذج يدعم اللغات المتعددة بما فيها العربية والإنجليزية """ ) # حالة المحادثة history_state = gr.State([]) # مدخلات دالة الإرسال send_inputs = [ history_state, user_text, image_input, audio_input, video_input, system_prompt, return_audio, speaker, temperature, top_p, max_tokens, ] send_outputs = [ history_state, audio_output, user_text, image_input, audio_input, video_input, ] # إرسال بالزر send_btn.click( fn=qwen3_omni_inference, inputs=send_inputs, outputs=send_outputs, queue=True, ).then( lambda h: h, inputs=history_state, outputs=chatbot, ) # إرسال بالـ Enter من Textbox user_text.submit( fn=qwen3_omni_inference, inputs=send_inputs, outputs=send_outputs, queue=True, ).then( lambda h: h, inputs=history_state, outputs=chatbot, ) # مسح المحادثة clear_btn.click( fn=clear_chat, inputs=None, outputs=[history_state, audio_output], ).then( lambda: [], inputs=None, outputs=chatbot, ).then( lambda: ("", None, None, None), inputs=None, outputs=[user_text, image_input, audio_input, video_input], ) # رسالة تحميل النموذج عند بدء التطبيق demo.load( lambda: gr.Info("جاري تحميل النموذج... قد يستغرق هذا بضع دقائق في المرة الأولى."), inputs=None, outputs=None, ) return demo # إنشاء الواجهة demo = create_interface() if __name__ == "__main__": # إطلاق التطبيق demo.launch( ssr_mode=False, # تعطيل SSR لتجنب مشاكل "Starting..." show_error=True, # عرض الأخطاء بشكل واضح )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import numpy as np
3
+ import torch
4
+ import gradio as gr
5
+ import spaces
6
+ import warnings
7
+ warnings.filterwarnings("ignore")
8
+
9
+ from transformers import Qwen3OmniMoeForConditionalGeneration, Qwen3OmniMoeProcessor
10
+ from qwen_omni_utils import process_mm_info
11
+
12
+ # =========================================================
13
+ # Patches لتجاوز مشاكل التوافق في Qwen3-Omni
14
+ # =========================================================
15
+
16
+ def _patched_mark_tied_weights_as_initialized(self):
17
+ """
18
+ تجاوز مشكلة lm_head في tied weights
19
+ """
20
+ return
21
+
22
+ def _patched_init_weights(self, module):
23
+ """
24
+ تجاوز مشكلة initializer_range في Qwen3OmniMoeTalkerConfig
25
+ """
26
+ # نحاول الحصول على initializer_range، وإذا لم يكن موجود نستخدم قيمة افتراضية
27
+ try:
28
+ std = self.config.initializer_range
29
+ except AttributeError:
30
+ # قيمة افتراضية آمنة
31
+ std = 0.02
32
+
33
+ # تطبيق التهيئة الأساسية
34
+ if isinstance(module, torch.nn.Linear):
35
+ module.weight.data.normal_(mean=0.0, std=std)
36
+ if module.bias is not None:
37
+ module.bias.data.zero_()
38
+ elif isinstance(module, torch.nn.Embedding):
39
+ module.weight.data.normal_(mean=0.0, std=std)
40
+ if module.padding_idx is not None:
41
+ module.weight.data[module.padding_idx].zero_()
42
+
43
+ def _patched_initialize_weights(self):
44
+ """
45
+ تجاوز كامل لدالة initialize_weights في حالة استمرار المشاكل
46
+ """
47
+ # نحاول التهيئة العادية، وإذا فشلت نتجاهلها
48
+ try:
49
+ # محاولة استخدام الدالة الأصلية إذا كانت موجودة
50
+ if hasattr(self, '_original_initialize_weights'):
51
+ self._original_initialize_weights()
52
+ else:
53
+ # تهيئة بسيطة آمنة
54
+ for module in self.modules():
55
+ if isinstance(module, (torch.nn.Linear, torch.nn.Embedding)):
56
+ if hasattr(module, 'weight'):
57
+ module.weight.data.normal_(mean=0.0, std=0.02)
58
+ if hasattr(module, 'bias') and module.bias is not None:
59
+ module.bias.data.zero_()
60
+ except Exception as e:
61
+ print(f"Warning: Could not initialize weights properly: {e}")
62
+ # نستمر بدون تهيئة - النموذج المحمل مسبقاً يجب أن يعمل
63
+
64
+ # تطبيق الـ patches قبل أي استدعاء لـ from_pretrained
65
+ def apply_patches():
66
+ """تطبيق جميع الـ patches اللازمة"""
67
+
68
+ # Patch 1: تجاوز مشكلة lm_head
69
+ if hasattr(Qwen3OmniMoeForConditionalGeneration, "mark_tied_weights_as_initialized"):
70
+ Qwen3OmniMoeForConditionalGeneration.mark_tied_weights_as_initialized = (
71
+ _patched_mark_tied_weights_as_initialized
72
+ )
73
+
74
+ # Patch 2: تجاوز مشكلة initializer_range
75
+ if hasattr(Qwen3OmniMoeForConditionalGeneration, "_init_weights"):
76
+ Qwen3OmniMoeForConditionalGeneration._init_weights = _patched_init_weights
77
+
78
+ # Patch 3: تجاوز initialize_weights بالكامل إذا لزم
79
+ if hasattr(Qwen3OmniMoeForConditionalGeneration, "initialize_weights"):
80
+ # حفظ الدالة الأصلية
81
+ Qwen3OmniMoeForConditionalGeneration._original_initialize_weights = (
82
+ Qwen3OmniMoeForConditionalGeneration.initialize_weights
83
+ )
84
+ Qwen3OmniMoeForConditionalGeneration.initialize_weights = _patched_initialize_weights
85
+
86
+ # تطبيق الـ patches
87
+ apply_patches()
88
+
89
+ # =========================================================
90
+ # إعدادات عامة
91
+ # =========================================================
92
+
93
+ MODEL_PATH = os.getenv("MODEL_PATH", "Qwen/Qwen3-Omni-30B-A3B-Instruct")
94
+ USE_AUDIO_IN_VIDEO = True # استخدام الصوت داخل الفيديو إذا وجد
95
+
96
+ VOICE_CHOICES = ["Ethan", "Chelsie", "Aiden"]
97
+ DEFAULT_VOICE = "Ethan"
98
+
99
+ # سنحمّل النموذج كسولياً (عن�� أول استدعاء فقط)
100
+ model = None
101
+ processor = None
102
+
103
+
104
+ def load_model():
105
+ """
106
+ تحميل Qwen3-Omni والمعالج عند أول استدعاء فقط.
107
+ - نستخدم attn_implementation="eager" لتفادي الحاجة لـ flash-attn.
108
+ - نضيف low_cpu_mem_usage=True لتحسين الأداء
109
+ - نضيف ignore_mismatched_sizes=True لتجاوز مشاكل الأحجام
110
+ """
111
+ global model, processor
112
+
113
+ if model is not None and processor is not None:
114
+ return
115
+
116
+ print(f"[ZeroGPU] Loading model from: {MODEL_PATH}")
117
+
118
+ # اختيار نوع البيانات والجهاز
119
+ if torch.cuda.is_available():
120
+ torch_dtype = torch.bfloat16
121
+ device = "cuda"
122
+ else:
123
+ torch_dtype = torch.float32
124
+ device = "cpu"
125
+
126
+ try:
127
+ # محاولة تحميل النموذج مع خيارات إضافية للأمان
128
+ local_model = Qwen3OmniMoeForConditionalGeneration.from_pretrained(
129
+ MODEL_PATH,
130
+ torch_dtype=torch_dtype,
131
+ attn_implementation="eager", # آمن على ZeroGPU بدون flash-attn
132
+ low_cpu_mem_usage=True, # تحسين استخدام الذاكرة
133
+ ignore_mismatched_sizes=True, # تجاوز مشاكل الأحجام
134
+ trust_remote_code=True, # السماح بالكود المخصص
135
+ )
136
+
137
+ # نقل النموذج إلى الجهاز المناسب
138
+ local_model = local_model.to(device)
139
+
140
+ # وضع النموذج في وضع التقييم (inference)
141
+ local_model.eval()
142
+
143
+ except Exception as e:
144
+ print(f"Error loading model: {e}")
145
+ print("Attempting alternative loading method...")
146
+
147
+ # محاولة بديلة مع تعطيل _init_weights
148
+ try:
149
+ # تعطيل _init_weights مؤقتاً
150
+ original_init_weights = None
151
+ if hasattr(Qwen3OmniMoeForConditionalGeneration, "_init_weights"):
152
+ original_init_weights = Qwen3OmniMoeForConditionalGeneration._init_weights
153
+ Qwen3OmniMoeForConditionalGeneration._init_weights = lambda self, module: None
154
+
155
+ local_model = Qwen3OmniMoeForConditionalGeneration.from_pretrained(
156
+ MODEL_PATH,
157
+ torch_dtype=torch_dtype,
158
+ attn_implementation="eager",
159
+ low_cpu_mem_usage=True,
160
+ )
161
+
162
+ # استعادة _init_weights
163
+ if original_init_weights:
164
+ Qwen3OmniMoeForConditionalGeneration._init_weights = original_init_weights
165
+
166
+ local_model = local_model.to(device)
167
+ local_model.eval()
168
+
169
+ except Exception as e2:
170
+ raise RuntimeError(f"Failed to load model: {e2}")
171
+
172
+ # تحميل المعالج
173
+ try:
174
+ local_processor = Qwen3OmniMoeProcessor.from_pretrained(
175
+ MODEL_PATH,
176
+ trust_remote_code=True
177
+ )
178
+ except Exception as e:
179
+ print(f"Error loading processor: {e}")
180
+ raise
181
+
182
+ model = local_model
183
+ processor = local_processor
184
+ print(f"[ZeroGPU] Model loaded successfully on {device} with dtype {torch_dtype}.")
185
+
186
+
187
+ def build_messages_from_history(
188
+ history,
189
+ system_prompt,
190
+ user_text,
191
+ image,
192
+ audio_path,
193
+ video_path,
194
+ ):
195
+ """
196
+ تحويل تاريخ الدردشة + المدخل الحالي إلى conversation بالـ format
197
+ المطلوب من Qwen3-Omni.
198
+ history: list of [user_text, assistant_text]
199
+ """
200
+ messages = []
201
+
202
+ if system_prompt:
203
+ messages.append(
204
+ {
205
+ "role": "system",
206
+ "content": [{"type": "text", "text": system_prompt}],
207
+ }
208
+ )
209
+
210
+ # تاريخ المحادثة
211
+ for user_msg, assistant_msg in history:
212
+ if user_msg:
213
+ messages.append(
214
+ {
215
+ "role": "user",
216
+ "content": [{"type": "text", "text": user_msg}],
217
+ }
218
+ )
219
+ if assistant_msg:
220
+ messages.append(
221
+ {
222
+ "role": "assistant",
223
+ "content": [{"type": "text", "text": assistant_msg}],
224
+ }
225
+ )
226
+
227
+ # محتوى رسالة المستخدم الحالية
228
+ user_content = []
229
+
230
+ if image is not None:
231
+ user_content.append({"type": "image", "image": image})
232
+
233
+ if audio_path is not None and audio_path != "":
234
+ user_content.append({"type": "audio", "audio": audio_path})
235
+
236
+ if video_path is not None and video_path != "":
237
+ user_content.append({"type": "video", "video": video_path})
238
+
239
+ if user_text and user_text.strip():
240
+ user_content.append({"type": "text", "text": user_text.strip()})
241
+
242
+ if user_content:
243
+ messages.append(
244
+ {
245
+ "role": "user",
246
+ "content": user_content,
247
+ }
248
+ )
249
+
250
+ return messages
251
+
252
+
253
+ # =========================================================
254
+ # دالة الاستدلال (تعمل على ZeroGPU)
255
+ # =========================================================
256
+
257
+ @spaces.GPU(duration=120)
258
+ def qwen3_omni_inference(
259
+ history,
260
+ user_text,
261
+ image,
262
+ audio_path,
263
+ video_path,
264
+ system_prompt,
265
+ return_audio,
266
+ speaker,
267
+ temperature,
268
+ top_p,
269
+ max_tokens,
270
+ ):
271
+ """
272
+ - تنفيذ الاستدلال على ZeroGPU.
273
+ - يدعم نص + صورة + صوت + فيديو في نفس الرسالة.
274
+ - مخرج نصي دائماً، ومخرج صوتي اختياري.
275
+ """
276
+
277
+ # في حالة عدم وجود مداخل من المستخدم
278
+ if not (user_text or image is not None or audio_path or video_path):
279
+ return history, None, "", None, None, None
280
+
281
+ try:
282
+ load_model()
283
+ global model, processor
284
+
285
+ messages = build_messages_from_history(
286
+ history=history,
287
+ system_prompt=system_prompt,
288
+ user_text=user_text,
289
+ image=image,
290
+ audio_path=audio_path,
291
+ video_path=video_path,
292
+ )
293
+
294
+ # بناء نص المحادثة باستخدام chat_template
295
+ text_prompt = processor.apply_chat_template(
296
+ messages,
297
+ add_generation_prompt=True,
298
+ tokenize=False,
299
+ )
300
+
301
+ # تجهيز الوسائط المتعددة
302
+ audios, images, videos = process_mm_info(
303
+ messages,
304
+ use_audio_in_video=USE_AUDIO_IN_VIDEO,
305
+ )
306
+
307
+ # تحويل إلى تينسورات
308
+ inputs = processor(
309
+ text=text_prompt,
310
+ audio=audios,
311
+ images=images,
312
+ videos=videos,
313
+ return_tensors="pt",
314
+ padding=True,
315
+ use_audio_in_video=USE_AUDIO_IN_VIDEO,
316
+ )
317
+
318
+ # نقل إلى جهاز النموذج ونفس dtype
319
+ first_param = next(model.parameters())
320
+ device = first_param.device
321
+
322
+ # تحويل المدخلات إلى الجهاز المناسب
323
+ for key in inputs:
324
+ if hasattr(inputs[key], 'to'):
325
+ inputs[key] = inputs[key].to(device)
326
+
327
+ # إعدادات التوليد
328
+ gen_kwargs = dict(
329
+ temperature=float(temperature) if temperature > 0 else 1e-7,
330
+ top_p=float(top_p),
331
+ max_new_tokens=int(max_tokens),
332
+ do_sample=temperature > 0,
333
+ use_audio_in_video=USE_AUDIO_IN_VIDEO,
334
+ )
335
+
336
+ # إضافة thinker_return_dict_in_generate فقط إذا كان مدعوماً
337
+ if hasattr(model, 'config') and hasattr(model.config, 'thinker_return_dict_in_generate'):
338
+ gen_kwargs["thinker_return_dict_in_generate"] = True
339
+
340
+ # توليد نص فقط أو نص + صوت
341
+ with torch.no_grad():
342
+ if not return_audio:
343
+ gen_kwargs["return_audio"] = False
344
+ outputs = model.generate(**inputs, **gen_kwargs)
345
+ text_ids = outputs
346
+ audio_out = None
347
+ else:
348
+ gen_kwargs["speaker"] = speaker
349
+ gen_kwargs["return_audio"] = True
350
+ outputs = model.generate(**inputs, **gen_kwargs)
351
+ if isinstance(outputs, tuple):
352
+ text_ids, audio_out = outputs
353
+ else:
354
+ text_ids = outputs
355
+ audio_out = None
356
+
357
+ # استخراج النص الناتج (بدون مدخل prompt)
358
+ input_len = inputs["input_ids"].shape[1]
359
+
360
+ # التعامل مع الأنواع المختلفة من المخرجات
361
+ if hasattr(text_ids, 'sequences'):
362
+ generated_ids = text_ids.sequences[:, input_len:]
363
+ else:
364
+ generated_ids = text_ids[:, input_len:]
365
+
366
+ generated_text = processor.batch_decode(
367
+ generated_ids,
368
+ skip_special_tokens=True,
369
+ clean_up_tokenization_spaces=False,
370
+ )[0]
371
+
372
+ # تحديث تاريخ الدردشة
373
+ user_display = (
374
+ user_text if (user_text and user_text.strip()) else "[Multimodal message]"
375
+ )
376
+ history = history + [[user_display, generated_text]]
377
+
378
+ # تجهيز الصوت الناتج إن وجد
379
+ gr_audio = None
380
+ if audio_out is not None:
381
+ try:
382
+ audio_np = audio_out.reshape(-1).detach().cpu().numpy()
383
+ sample_rate = 24000
384
+ gr_audio = (sample_rate, audio_np.astype(np.float32))
385
+ except Exception as e:
386
+ print(f"Warning: Could not process audio output: {e}")
387
+ gr_audio = None
388
+
389
+ # نعيد: history الجديد + صوت الرد + تفريغ مدخلات المستخدم
390
+ return history, gr_audio, "", None, None, None
391
+
392
+ except Exception as e:
393
+ print(f"Error during inference: {e}")
394
+ import traceback
395
+ traceback.print_exc()
396
+
397
+ # في حالة الخطأ، نضيف رسالة خطأ للمحادثة
398
+ user_display = (
399
+ user_text if (user_text and user_text.strip()) else "[Multimodal message]"
400
+ )
401
+ error_message = f"عذراً، حدث خطأ أثناء معالجة الرسالة: {str(e)}"
402
+ history = history + [[user_display, error_message]]
403
+ return history, None, "", None, None, None
404
+
405
+
406
+ # =========================================================
407
+ # دوال واجهة Gradio
408
+ # =========================================================
409
+
410
+ def clear_chat():
411
+ """إعادة تعيين المحادثة ومخرج الصوت."""
412
+ return [], None
413
+
414
+
415
+ def create_interface():
416
+ with gr.Blocks(
417
+ title="Qwen3-Omni-30B-A3B – ZeroGPU Chat",
418
+ theme=gr.themes.Soft(),
419
+ ) as demo:
420
+ gr.Markdown(
421
+ """
422
+ <h1 style="text-align:center;">🤖 Qwen3-Omni-30B-A3B – ZeroGPU Chat</h1>
423
+ <p style="text-align:center;">
424
+ دردشة متعددة الوسائط (نص + صورة + صوت + فيديو) تعمل على ZeroGPU.<br/>
425
+ اكتب رسالتك، ويمكنك إضافة صورة/صوت/فيديو، ثم اضغط <b>إرسال</b> أو Enter.<br/>
426
+ (لإضافة سطر جديد استخدم Shift+Enter)
427
+ </p>
428
+ """
429
+ )
430
+
431
+ with gr.Row():
432
+ # العمود الأيسر: المحادثة
433
+ with gr.Column(scale=3):
434
+ chatbot = gr.Chatbot(
435
+ label="المحادثة",
436
+ height=480,
437
+ elem_id="chatbot",
438
+ )
439
+
440
+ audio_output = gr.Audio(
441
+ label="رد النموذج (صوت)",
442
+ type="numpy",
443
+ autoplay=True,
444
+ visible=True,
445
+ )
446
+
447
+ with gr.Row():
448
+ user_text = gr.Textbox(
449
+ label="رسالتك",
450
+ placeholder="اكتب رسالتك هنا (يمكنك أيضاً إرفاق صورة/صوت/فيديو من الأسفل)...",
451
+ lines=3,
452
+ show_label=False,
453
+ elem_id="message",
454
+ )
455
+
456
+ with gr.Row():
457
+ image_input = gr.Image(
458
+ label="📷 صورة (اختياري)",
459
+ type="pil",
460
+ sources=["upload", "webcam"],
461
+ height=150,
462
+ )
463
+ audio_input = gr.Audio(
464
+ label="🎙️ صوت (اختياري)",
465
+ type="filepath",
466
+ sources=["microphone", "upload"],
467
+ )
468
+ video_input = gr.Video(
469
+ label="🎬 فيديو (اختياري)",
470
+ height=150,
471
+ )
472
+
473
+ with gr.Row():
474
+ send_btn = gr.Button("إرسال", variant="primary", scale=2)
475
+ clear_btn = gr.Button("مسح المحادثة", variant="secondary")
476
+
477
+ # العمود الأيمن: الإعدادات
478
+ with gr.Column(scale=1):
479
+ gr.Markdown("### ⚙️ إعدادات النموذج")
480
+
481
+ system_prompt = gr.Textbox(
482
+ label="System Prompt",
483
+ value="You are a helpful, multilingual assistant.",
484
+ lines=4,
485
+ placeholder="يمكنك التحكم في شخصية النموذج من هنا (اختياري).",
486
+ )
487
+
488
+ return_audio = gr.Checkbox(
489
+ label="تفعيل مخرج صوتي (النموذج يتكلم)؟",
490
+ value=True,
491
+ )
492
+
493
+ speaker = gr.Dropdown(
494
+ label="صوت المتحدث (speaker)",
495
+ choices=VOICE_CHOICES,
496
+ value=DEFAULT_VOICE,
497
+ )
498
+
499
+ with gr.Accordion("إعدادات متقدمة", open=False):
500
+ temperature = gr.Slider(
501
+ label="Temperature (العشوائية)",
502
+ minimum=0.0,
503
+ maximum=1.5,
504
+ value=0.6,
505
+ step=0.05,
506
+ )
507
+
508
+ top_p = gr.Slider(
509
+ label="Top-p (حجم العينة)",
510
+ minimum=0.1,
511
+ maximum=1.0,
512
+ value=0.95,
513
+ step=0.05,
514
+ )
515
+
516
+ max_tokens = gr.Slider(
517
+ label="Max new tokens (طول الرد الأقصى)",
518
+ minimum=16,
519
+ maximum=1024,
520
+ value=384,
521
+ step=16,
522
+ )
523
+
524
+ gr.Markdown(
525
+ """
526
+ **📝 ملاحظات:**
527
+ - يمكنك إرسال نص فقط، أو نص مع صورة/صوت/فيديو في رسالة واحدة
528
+ - Enter للإرسال، Shift+Enter لسطر جديد
529
+ - تشغيل النموذج على ZeroGPU قد يستغرق عدة ثوانٍ حسب طول الرسالة
530
+ - النموذج يدعم اللغات المتعددة بما فيها العربية والإنجليزية
531
+ """
532
+ )
533
+
534
+ # حالة المحادثة
535
+ history_state = gr.State([])
536
+
537
+ # مدخلات دالة الإرسال
538
+ send_inputs = [
539
+ history_state,
540
+ user_text,
541
+ image_input,
542
+ audio_input,
543
+ video_input,
544
+ system_prompt,
545
+ return_audio,
546
+ speaker,
547
+ temperature,
548
+ top_p,
549
+ max_tokens,
550
+ ]
551
+
552
+ send_outputs = [
553
+ history_state,
554
+ audio_output,
555
+ user_text,
556
+ image_input,
557
+ audio_input,
558
+ video_input,
559
+ ]
560
+
561
+ # إرسال بالزر
562
+ send_btn.click(
563
+ fn=qwen3_omni_inference,
564
+ inputs=send_inputs,
565
+ outputs=send_outputs,
566
+ queue=True,
567
+ ).then(
568
+ lambda h: h,
569
+ inputs=history_state,
570
+ outputs=chatbot,
571
+ )
572
+
573
+ # إرسال بالـ Enter من Textbox
574
+ user_text.submit(
575
+ fn=qwen3_omni_inference,
576
+ inputs=send_inputs,
577
+ outputs=send_outputs,
578
+ queue=True,
579
+ ).then(
580
+ lambda h: h,
581
+ inputs=history_state,
582
+ outputs=chatbot,
583
+ )
584
+
585
+ # مسح المحادثة
586
+ clear_btn.click(
587
+ fn=clear_chat,
588
+ inputs=None,
589
+ outputs=[history_state, audio_output],
590
+ ).then(
591
+ lambda: [],
592
+ inputs=None,
593
+ outputs=chatbot,
594
+ ).then(
595
+ lambda: ("", None, None, None),
596
+ inputs=None,
597
+ outputs=[user_text, image_input, audio_input, video_input],
598
+ )
599
+
600
+ # رسالة تحميل النموذج عند بدء التطبيق
601
+ demo.load(
602
+ lambda: gr.Info("جاري تحميل النموذج... قد يستغرق هذا بضع دقائق في المرة الأولى."),
603
+ inputs=None,
604
+ outputs=None,
605
+ )
606
+
607
+ return demo
608
+
609
+
610
+ # إنشاء الواجهة
611
+ demo = create_interface()
612
+
613
+ if __name__ == "__main__":
614
+ # إطلاق التطبيق
615
+ demo.launch(
616
+ ssr_mode=False, # تعطيل SSR لتجنب مشاكل "Starting..."
617
+ show_error=True, # عرض الأخطاء بشكل واضح
618
+ )