from flask import Flask, request, render_template_string, Response, stream_with_context from llama_cpp import Llama import os import time import json app = Flask(__name__) # --- ১. সঠিক মডেল লোকেশন --- # Q1_0 (এন্ড-টু-এন্ড ১-বিট) ফরম্যাট এখন mainline llama.cpp-তে upstream সাপোর্টেড, # তাই llama-cpp-python (রিসেন্ট ভার্সন) দিয়ে এটা সরাসরি চলে। # (শুধু তাদের ternary/Q2_0 ফরম্যাটের জন্য বিশেষ ফর্ক লাগে, Q1_0-এর জন্য না।) # ✅ বর্তমানে সক্রিয়: Bonsai 8B (~1.16 GB) #MODEL_REPO = "prism-ml/Bonsai-8B-gguf" #MODEL_FILE = "Bonsai-8B-Q1_0.gguf" # 🔽 ছোট মডেল চাইলে উপরের দুই লাইন কমেন্ট করে নিচের যেকোনো একটা আনকমেন্ট করুন 🔽 # --- Bonsai 4B (~0.57 GB) — মাঝারি সাইজ, আরও দ্রুত --- #MODEL_REPO = "prism-ml/Bonsai-4B-gguf" #MODEL_FILE = "Bonsai-4B-Q1_0.gguf" # Google gemma3 MODEL_REPO = "DevQuasar/google.gemma-3n-E2B-it-GGUF" MODEL_FILE = "google.gemma-3n-E2B-it.Q2_K.gguf" # --- Bonsai 1.7B (~0.25 GB) — সবচেয়ে ছোট (১ বিলিয়নের কাছাকাছি), সবচেয়ে দ্রুত --- #MODEL_REPO = "prism-ml/Bonsai-1.7B-gguf" #MODEL_FILE = "Bonsai-1.7B-Q1_0.gguf" print("⏳ মডেল ডাউনলোড হচ্ছে... (প্রথমবার একটু সময় লাগবে)") from huggingface_hub import hf_hub_download model_path = hf_hub_download( repo_id=MODEL_REPO, filename=MODEL_FILE, local_dir="./models", token=None # পাবলিক রিপো, টোকেন লাগবে না ) print("✅ ডাউনলোড সম্পূর্ণ!") print(f"📁 ফাইল লোকেশন: {model_path}") print("⏳ মডেল লোড হচ্ছে (CPU)...") try: # llama-cpp-python: GGUF মেটাডেটা থেকে নিজেই আর্কিটেকচার (qwen3) বুঝে নেয়, # তাই model_type জাতীয় কিছু দিতে হয় না। # CPU-এর সব কোর ব্যবহার করা হচ্ছে যাতে prompt processing (prefill) দ্রুত হয়। # n_threads -> টোকেন জেনারেশনের সময় ব্যবহৃত থ্রেড সংখ্যা # n_threads_batch -> prompt/prefill প্রসেসিংয়ের সময় ব্যবহৃত থ্রেড সংখ্যা (এটাই প্রথম টোকেনের দেরির মূল কারণ) cpu_count = os.cpu_count() or 4 llm = Llama( model_path=model_path, n_ctx=2048, n_threads=cpu_count, n_threads_batch=cpu_count, n_batch=512, # prefill ব্যাচ সাইজ, বড় করলে prompt processing দ্রুত হয় verbose=False ) # --- ওয়ার্মআপ --- # প্রথম ইনফারেন্স কলে llama.cpp কিছু অভ্যন্তরীণ বাফার/গ্রাফ তৈরি করে যা এক্সট্রা সময় নেয়। # সার্ভার চালু হওয়ার সময়ই একটা ডামি জেনারেশন চালিয়ে সেই ওয়ান-টাইম খরচ আগেই সেরে ফেলা হচ্ছে, # যাতে ইউজারের প্রথম আসল রিকোয়েস্টে এই পেনাল্টি না লাগে। print("🔥 মডেল ওয়ার্মআপ হচ্ছে...") list(llm("<|im_start|>user\nহাই<|im_end|>\n<|im_start|>assistant\n\n\n\n\n", max_tokens=1, stream=True)) print("✅ ওয়ার্মআপ সম্পন্ন!") print("✅ মডেল প্রস্তুত! সার্ভার চালু হচ্ছে...") except Exception as e: print(f"⚠️ llama-cpp-python লোড করতে সমস্যা: {e}") llm = None # --- ২. HTML টেমপ্লেট --- HTML_TEMPLATE = """ Bonsai 8B চ্যাট (Hugging Face Space)

🌳 Bonsai 8B (১-বিট) চ্যাট

Hugging Face Space • CPU Inference • মডেল সাইজ: ~১.১৫ GB (Q1_0, 1-bit)
👋 হ্যালো! আমি Bonsai 8B। আপনি কী জানতে চান?
প্রস্তুত
""" # --- ৩. Flask রাউট --- @app.route('/') def index(): return render_template_string(HTML_TEMPLATE) @app.route('/chat', methods=['POST']) def chat(): data = request.get_json() user_prompt = data.get('prompt', '') # ফ্রন্টএন্ড থেকে পাঠানো সর্বশেষ (সর্বোচ্চ ৩টা) প্রশ্ন-উত্তরের হিস্টরি # প্রতিটা আইটেম: {"user": "...", "assistant": "..."} history = data.get('history', []) if not user_prompt: return {"error": "কোনো প্রশ্ন নেই"} if llm is None: return {"error": "মডেল লোড হয়নি। সার্ভার লগ চেক করুন।"} # শুধু সর্বশেষ ৩টা এক্সচেঞ্জ ব্যবহার করা হচ্ছে (কনটেক্সট উইন্ডো সীমিত রাখতে) if isinstance(history, list): history = history[-3:] else: history = [] # থিংকিং সম্পূর্ণভাবে বন্ধ — খালি প্রি-ফিল করে মডেলকে সরাসরি উত্তরে পাঠানো হচ্ছে # হিস্টরির প্রতিটা টার্ন প্রম্পটে জুড়ে দেওয়া হচ্ছে, যাতে মডেল আগের কনভারসেশন মনে রাখে full_prompt = "" for turn in history: h_user = str(turn.get('user', '')).strip() h_assistant = str(turn.get('assistant', '')).strip() if not h_user: continue full_prompt += f"<|im_start|>user\n{h_user}<|im_end|>\n<|im_start|>assistant\n\n\n\n\n{h_assistant}<|im_end|>\n" full_prompt += f"<|im_start|>user\n{user_prompt}<|im_end|>\n<|im_start|>assistant\n\n\n\n\n" def generate(): start_time = time.time() start_str = time.strftime('%H:%M:%S', time.localtime(start_time)) first_token_time = None try: # stream=True দিলে llama-cpp-python টোকেন-বাই-টোকেন জেনারেট করে দেয় stream = llm( full_prompt, max_tokens=256, # সর্বোচ্চ টোকেন সংখ্যা temperature=0.1, # তাপমাত্রা (কম = ডিটারমিনিস্টিক) top_p=0.2, # নিউক্লিয়াস স্যাম্পলিং top_k=20, # টপ-কে স্যাম্পলিং repeat_penalty=1.4, # পুনরাবৃত্তি শাস্তি stop=["<|im_end|>", "user:", "User:"], stream=True ) for chunk in stream: token_text = chunk["choices"][0]["text"] if token_text: # প্রথম টোকেনের সময় রেকর্ড if first_token_time is None: first_token_time = time.time() - start_time yield token_text except Exception as e: print(f"❌ জেনারেশন ত্রুটি: {e}") yield f"\n⚠️ মডেল ত্রুটি: {str(e)}" finally: end_time = time.time() end_str = time.strftime('%H:%M:%S', time.localtime(end_time)) elapsed = round(end_time - start_time, 2) first_token = round(first_token_time, 2) if first_token_time else 0 print(f"⏱️ প্রথম টোকেন: {first_token} সেকেন্ড | মোট সময়: {elapsed} সেকেন্ড") print(f"📊 প্যারামিটার: max_tokens=256, temp=0.1, top_p=0.2, top_k=40, repeat_penalty=1.1") # স্ট্রিমের একদম শেষে টাইমিং তথ্য পাঠানো meta = { "start": start_str, "end": end_str, "elapsed": elapsed, "first_token_time": first_token } yield f"\n[[META]]{json.dumps(meta, ensure_ascii=False)}" return Response(stream_with_context(generate()), mimetype='text/plain') # --- ৪. সার্ভার চালানো --- if __name__ == '__main__': app.run(host='0.0.0.0', port=7860)