from flask import Flask, request, jsonify, render_template_string, Response import torch from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer from threading import Thread import json app = Flask(__name__) # ==================== CSS STİLLERİ ==================== STYLES = """ :root { --primary: #2c3e50; --secondary: #3498db; --accent: #e74c3c; --success: #27ae60; --warning: #f39c12; --light: #ecf0f1; --dark: #1a252f; --gray: #95a5a6; --white: #ffffff; --shadow: 0 4px 20px rgba(0,0,0,0.1); --radius: 12px; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; color: var(--dark); line-height: 1.6; } .navbar { background: var(--dark); padding: 1rem 2rem; display: flex; justify-content: space-between; align-items: center; box-shadow: var(--shadow); } .navbar h1 { color: var(--white); font-size: 1.5rem; font-weight: 600; } .nav-links { display: flex; gap: 1.5rem; } .nav-links a { color: var(--light); text-decoration: none; font-weight: 500; padding: 0.5rem 1rem; border-radius: 6px; transition: all 0.3s ease; } .nav-links a:hover, .nav-links a.active { background: var(--secondary); color: var(--white); } .container { max-width: 1000px; margin: 2rem auto; padding: 0 1rem; } .card { background: var(--white); border-radius: var(--radius); box-shadow: var(--shadow); padding: 2rem; margin-bottom: 1.5rem; } .card-header { border-bottom: 2px solid var(--light); padding-bottom: 1rem; margin-bottom: 1.5rem; } .card-header h2 { color: var(--primary); font-size: 1.5rem; } .card-header p { color: var(--gray); margin-top: 0.5rem; } label { display: block; font-weight: 600; color: var(--primary); margin-bottom: 0.5rem; } textarea { width: 100%; height: 140px; padding: 1rem; border: 2px solid var(--light); border-radius: 8px; font-size: 1rem; font-family: inherit; resize: vertical; transition: border-color 0.3s ease; } textarea:focus { outline: none; border-color: var(--secondary); } select { width: 100%; padding: 0.8rem 1rem; border: 2px solid var(--light); border-radius: 8px; font-size: 1rem; font-family: inherit; background: var(--white); cursor: pointer; margin-bottom: 1rem; } select:focus { outline: none; border-color: var(--secondary); } .btn { display: inline-block; padding: 1rem 2rem; font-size: 1rem; font-weight: 600; border: none; border-radius: 8px; cursor: pointer; transition: all 0.3s ease; width: 100%; text-align: center; } .btn-primary { background: linear-gradient(135deg, var(--secondary), #2980b9); color: var(--white); } .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(52, 152, 219, 0.4); } .btn-primary:disabled { background: var(--gray); cursor: not-allowed; transform: none; } .btn-stop { background: linear-gradient(135deg, var(--accent), #c0392b); color: var(--white); margin-top: 0.5rem; display: none; } .btn-stop.active { display: block; } .form-group { margin-bottom: 1.5rem; } /* ==================== SETTINGS PANEL ==================== */ .settings-panel { background: linear-gradient(135deg, #f8f9fa, #e9ecef); border-radius: var(--radius); padding: 1.5rem; margin-bottom: 1.5rem; } .settings-header { display: flex; justify-content: space-between; align-items: center; cursor: pointer; margin-bottom: 1rem; } .settings-header h3 { color: var(--primary); font-size: 1.1rem; display: flex; align-items: center; gap: 0.5rem; } .settings-toggle { background: var(--secondary); color: var(--white); border: none; padding: 0.3rem 0.8rem; border-radius: 6px; cursor: pointer; font-size: 0.85rem; transition: all 0.3s ease; } .settings-toggle:hover { background: #2980b9; } .settings-content { display: none; } .settings-content.active { display: block; } .settings-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; } .setting-item { background: var(--white); padding: 1rem; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); } .setting-item label { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.8rem; } .setting-item label span.value { background: var(--secondary); color: var(--white); padding: 0.2rem 0.6rem; border-radius: 4px; font-size: 0.85rem; min-width: 50px; text-align: center; } .setting-item input[type="range"] { width: 100%; height: 8px; border-radius: 4px; background: var(--light); outline: none; -webkit-appearance: none; } .setting-item input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 20px; height: 20px; border-radius: 50%; background: var(--secondary); cursor: pointer; box-shadow: 0 2px 6px rgba(0,0,0,0.2); transition: transform 0.2s ease; } .setting-item input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.1); } .setting-item input[type="number"] { width: 100%; padding: 0.6rem; border: 2px solid var(--light); border-radius: 6px; font-size: 1rem; text-align: center; } .setting-item input[type="number"]:focus { outline: none; border-color: var(--secondary); } .setting-description { font-size: 0.8rem; color: var(--gray); margin-top: 0.5rem; } /* Stream Toggle */ .stream-toggle { display: flex; align-items: center; gap: 1rem; padding: 0.5rem 0; } .toggle-switch { position: relative; width: 60px; height: 30px; } .toggle-switch input { opacity: 0; width: 0; height: 0; } .toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: var(--gray); transition: 0.4s; border-radius: 30px; } .toggle-slider:before { position: absolute; content: ""; height: 22px; width: 22px; left: 4px; bottom: 4px; background-color: white; transition: 0.4s; border-radius: 50%; } .toggle-switch input:checked + .toggle-slider { background-color: var(--success); } .toggle-switch input:checked + .toggle-slider:before { transform: translateX(30px); } .toggle-label { font-weight: 600; color: var(--primary); } /* ==================== END SETTINGS PANEL ==================== */ .response-box { background: linear-gradient(135deg, #f8f9fa, #e9ecef); border-radius: var(--radius); padding: 1.5rem; margin-top: 1.5rem; display: none; } .response-box.active { display: block; } .response-header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--gray); } .model-badge { background: var(--secondary); color: var(--white); padding: 0.3rem 0.8rem; border-radius: 20px; font-size: 0.85rem; font-weight: 600; } .stream-badge { background: var(--success); color: var(--white); padding: 0.2rem 0.6rem; border-radius: 12px; font-size: 0.75rem; font-weight: 600; margin-left: 0.5rem; } .response-content { white-space: pre-wrap; font-size: 1rem; } .response-content strong { color: var(--primary); } .cursor-blink { display: inline-block; width: 8px; height: 1.2em; background: var(--secondary); margin-left: 2px; animation: blink 1s infinite; vertical-align: text-bottom; } @keyframes blink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0; } } .loading { text-align: center; padding: 2rem; color: var(--gray); display: none; } .loading.active { display: block; } .spinner { width: 40px; height: 40px; border: 4px solid var(--light); border-top: 4px solid var(--secondary); border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 1rem; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .error-box { background: #fdeaea; border: 1px solid var(--accent); color: var(--accent); padding: 1rem; border-radius: 8px; margin-top: 1rem; } .info-section { margin-bottom: 2rem; } .info-section h3 { color: var(--primary); margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 2px solid var(--secondary); } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin: 1rem 0; } .stat-card { background: linear-gradient(135deg, var(--light), #dfe6e9); padding: 1.5rem; border-radius: 8px; text-align: center; } .stat-value { font-size: 2rem; font-weight: 700; color: var(--secondary); } .stat-label { color: var(--gray); font-size: 0.9rem; margin-top: 0.3rem; } table { width: 100%; border-collapse: collapse; margin: 1rem 0; } th, td { padding: 0.8rem; text-align: left; border-bottom: 1px solid var(--light); } th { background: var(--primary); color: var(--white); } tr:hover { background: var(--light); } .badge { display: inline-block; padding: 0.2rem 0.6rem; border-radius: 4px; font-size: 0.8rem; font-weight: 600; } .badge-success { background: #d4edda; color: var(--success); } .badge-danger { background: #f8d7da; color: var(--accent); } .badge-warning { background: #fff3cd; color: #856404; } .badge-info { background: #d1ecf1; color: #0c5460; } .code-block { background: var(--dark); color: #a6e22e; padding: 1rem; border-radius: 8px; overflow-x: auto; font-family: 'Consolas', 'Monaco', monospace; font-size: 0.9rem; margin: 1rem 0; } .highlight { background: linear-gradient(135deg, #fff3cd, #ffeeba); padding: 1rem; border-radius: 8px; border-left: 4px solid var(--warning); margin: 1rem 0; } .comparison-table tr:nth-child(even) { background: #f8f9fa; } .footer { text-align: center; padding: 2rem; color: var(--white); opacity: 0.8; } .footer a { color: var(--white); } @media (max-width: 768px) { .navbar { flex-direction: column; gap: 1rem; } .stats-grid { grid-template-columns: 1fr; } .two-column { grid-template-columns: 1fr; } .settings-grid { grid-template-columns: 1fr; } } .two-column { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; } /* Preset buttons */ .preset-buttons { display: flex; gap: 0.5rem; margin-bottom: 1rem; flex-wrap: wrap; } .preset-btn { padding: 0.4rem 0.8rem; border: 2px solid var(--secondary); background: var(--white); color: var(--secondary); border-radius: 6px; cursor: pointer; font-size: 0.85rem; font-weight: 500; transition: all 0.3s ease; } .preset-btn:hover { background: var(--secondary); color: var(--white); } .preset-btn.active { background: var(--secondary); color: var(--white); } /* Token counter */ .token-info { display: flex; justify-content: space-between; font-size: 0.85rem; color: var(--gray); margin-top: 1rem; padding-top: 0.5rem; border-top: 1px dashed var(--light); } .token-count { color: var(--secondary); font-weight: 600; } """ # ==================== ANA SAYFA HTML ==================== INDEX_HTML = """ Kayra - Türkçe Dil Modeli Test

Model Test Arayüzü

Küçük ölçekli Türkçe dil modellerini karşılaştırmalı olarak test edin.

⚙️ Gelişmiş Ayarlar

Düşük = daha tutarlı, Yüksek = daha yaratıcı

Üretilecek maksimum token sayısı

Her adımda değerlendirilecek en olası K token

Kümülatif olasılık eşiği

Tekrar eden kelimeleri engelleme gücü

Açık

Cevabı kelime kelime göster

Yanıt oluşturuluyor, lütfen bekleyin...

Model:
Üretilen token: 0 Süre: 0s
""" # ==================== BİLGİ SAYFASI HTML ==================== INFO_HTML = """ Kayra - Model Bilgileri

Kayra Nedir?

Sıfırdan Türkçe ile eğitilmiş deneysel GPT modelleri

Kayra, Türkçe dil işleme araştırmaları için geliştirilmiş, sıfırdan eğitilmiş küçük ölçekli GPT tabanlı dil modelleridir. Bu projede iki farklı model bulunmaktadır:

85M
Parametre
500K
Eğitim Dokümanı
42.7
Validation PPL
MIT
Lisans

⚙️ Parametre Açıklamaları

Parametre Aralık Varsayılan Açıklama
Temperature 0.1 - 2.0 0.7 Çıktının rastgeleliğini kontrol eder. Düşük değerler daha tutarlı, yüksek değerler daha yaratıcı sonuçlar üretir.
Max Tokens 10 - 512 150 Üretilecek maksimum token (kelime parçası) sayısı.
Top K 1 - 100 50 Her adımda sadece en olası K tokenden seçim yapılır. Düşük K daha odaklı, yüksek K daha çeşitli çıktı üretir.
Top P (Nucleus) 0.1 - 1.0 0.9 Kümülatif olasılığı P'yi geçen tokenlerden seçim yapılır. Temperature ile birlikte kullanılır.
Repetition Penalty 1.0 - 2.0 1.3 Tekrarlayan kelimeleri cezalandırır. Yüksek değerler tekrarı azaltır ama anlam bozulabilir.
Stream Mode Açık/Kapalı Açık Açıkken cevap kelime kelime gösterilir, kapalıyken tamamı bir anda gösterilir.

🎛️ Hazır Ayarlar (Presets)

🎨 Yaratıcı

Yüksek temperature ve top-k ile daha özgün ve beklenmedik çıktılar üretir. Hikaye yazımı, beyin fırtınası için ideal.

temp=1.2, top_k=80, top_p=0.95, rep=1.1

⚖️ Dengeli

Varsayılan ayarlar. Çoğu kullanım senaryosu için uygundur.

temp=0.7, top_k=50, top_p=0.9, rep=1.3

🎯 Kesin

Düşük temperature ile daha tutarlı ve tahmin edilebilir çıktılar. Bilgi gerektiren sorular için.

temp=0.3, top_k=20, top_p=0.8, rep=1.5

🎲 Rastgele

Maksimum rastgelelik. Çılgın ve beklenmedik sonuçlar için. Dikkatli kullanın!

temp=1.8, top_k=100, top_p=1.0, rep=1.0

Model Karşılaştırması

Özellik kayra-1 (Stable) kayra-1-exp (Deneysel)
Tür Instruction-tuned Sadece Pretrained
Kullanım Soru-Cevap formatında Metin tamamlama
Stabilite Stabil Deneysel
Prompt Formatı ### Soru: ... ### Cevap: Düz metin
Önerilen Kullanım Genel test ve demo Araştırma ve analiz

Teknik Mimari

Model TürüDecoder-only Transformer (GPT-style)
Katman Sayısı10
Hidden Size640
Attention Heads10
FFN Size2560
Vocabulary32,000 BPE tokens
Context Length512 tokens
Toplam Parametre~85 milyon

Eğitim Verisi

Kaynak Doküman Sayısı Açıklama
Wikipedia TR ~170,000 Türkçe Vikipedi makaleleri
mC4 Turkish ~330,000 Common Crawl web dokümanları
Toplam ~500,000 MinHash LSH ile dedupe edilmiş

Kod Örneği

from transformers import AutoModelForCausalLM, AutoTokenizer

model = AutoModelForCausalLM.from_pretrained(
    "sixfingerdev/kayra-1-exp",
    trust_remote_code=True  # ONEMLI!
)
tokenizer = AutoTokenizer.from_pretrained("sixfingerdev/kayra-1-exp")

prompt = "Türkiye'nin başkenti"
inputs = tokenizer(prompt, return_tensors="pt")

outputs = model.generate(
    inputs.input_ids,
    max_new_tokens=100,
    temperature=0.8,
    top_k=50,
    top_p=0.9,
    repetition_penalty=1.2,
    do_sample=True
)

print(tokenizer.decode(outputs[0], skip_special_tokens=True))

Lisans ve Atıf

Lisans: MIT License - Ticari ve akademik kullanım serbesttir.

@misc{kayra2024hallucination,
  title={Why Small Turkish GPTs Hallucinate Facts: An Experimental 85M Model},
  author={sixfingerdev},
  year={2024},
  publisher={HuggingFace},
  howpublished={\\url{https://huggingface.co/sixfingerdev/kayra-1-exp}},
  note={Research on loss-factuality divergence in low-resource language models}
}
Uyarı: Bu model bilerek kusurlarıyla birlikte paylaşılmıştır. Küçük LM'lerin neden hallucination yaptığını gösteren bir öğrenme kaynağı olarak hizmet eder, production aracı olarak değil.
""" # ==================== MODEL YÜKLEME ==================== print("Modeller yükleniyor... Bu biraz sürebilir (özellikle ilk seferde).") device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model_stable = AutoModelForCausalLM.from_pretrained( "sixfingerdev/kayra-1", trust_remote_code=True, torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32 ).to(device) tokenizer_stable = AutoTokenizer.from_pretrained("sixfingerdev/kayra-1") model_exp = AutoModelForCausalLM.from_pretrained( "sixfingerdev/kayra-1-exp", trust_remote_code=True, torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32 ).to(device) tokenizer_exp = AutoTokenizer.from_pretrained("sixfingerdev/kayra-1-exp") print("Modeller başarıyla yüklendi!") # ==================== YARDIMCI FONKSİYONLAR ==================== def generate_response(model, tokenizer, prompt, max_new_tokens=150, temperature=0.7, top_k=50, top_p=0.9, repetition_penalty=1.3): if model == model_stable: formatted_prompt = f"### Soru: {prompt}\n\n### Cevap:" else: formatted_prompt = prompt inputs = tokenizer(formatted_prompt, return_tensors="pt") inputs = inputs.to(model.device) if "token_type_ids" in inputs: inputs.pop("token_type_ids") with torch.no_grad(): outputs = model.generate( **inputs, max_new_tokens=max_new_tokens, temperature=temperature, do_sample=True, top_k=top_k, top_p=top_p, repetition_penalty=repetition_penalty, pad_token_id=tokenizer.eos_token_id ) response = tokenizer.decode(outputs[0], skip_special_tokens=True) token_count = outputs.shape[1] - inputs['input_ids'].shape[1] if model == model_stable: if "### Cevap:" in response: response = response.split("### Cevap:")[-1].strip() return response, token_count def generate_stream(model, tokenizer, prompt, max_new_tokens=150, temperature=0.7, top_k=50, top_p=0.9, repetition_penalty=1.3): """Generator function for streaming responses""" if model == model_stable: formatted_prompt = f"### Soru: {prompt}\n\n### Cevap:" else: formatted_prompt = prompt inputs = tokenizer(formatted_prompt, return_tensors="pt") inputs = inputs.to(model.device) if "token_type_ids" in inputs: inputs.pop("token_type_ids") streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True) generation_kwargs = dict( **inputs, max_new_tokens=max_new_tokens, temperature=temperature, do_sample=True, top_k=top_k, top_p=top_p, repetition_penalty=repetition_penalty, pad_token_id=tokenizer.eos_token_id, streamer=streamer ) thread = Thread(target=model.generate, kwargs=generation_kwargs) thread.start() for text in streamer: yield text thread.join() # ==================== ROUTE'LAR ==================== @app.route("/") def index(): return render_template_string(INDEX_HTML, styles=STYLES) @app.route("/info") def info(): return render_template_string(INFO_HTML, styles=STYLES) @app.route("/generate", methods=["POST"]) def generate(): data = request.json prompt = data.get("prompt", "").strip() model_choice = data.get("model", "stable") # Get settings with defaults max_tokens = data.get("max_tokens", 150) temperature = data.get("temperature", 0.7) top_k = data.get("top_k", 50) top_p = data.get("top_p", 0.9) repetition_penalty = data.get("repetition_penalty", 1.3) if not prompt: return jsonify({"error": "Lütfen bir soru veya mesaj girin."}) if model_choice == "stable": response, token_count = generate_response( model_stable, tokenizer_stable, prompt, max_new_tokens=max_tokens, temperature=temperature, top_k=top_k, top_p=top_p, repetition_penalty=repetition_penalty ) model_name = "kayra-1 (Stable)" else: response, token_count = generate_response( model_exp, tokenizer_exp, prompt, max_new_tokens=max_tokens, temperature=temperature, top_k=top_k, top_p=top_p, repetition_penalty=repetition_penalty ) model_name = "kayra-1-exp (Deneysel)" return jsonify({ "model": model_name, "prompt": prompt, "response": response, "token_count": token_count }) @app.route("/generate_stream") def generate_stream_route(): prompt = request.args.get("prompt", "").strip() model_choice = request.args.get("model", "stable") # Get settings with defaults max_tokens = int(request.args.get("max_tokens", 150)) temperature = float(request.args.get("temperature", 0.7)) top_k = int(request.args.get("top_k", 50)) top_p = float(request.args.get("top_p", 0.9)) repetition_penalty = float(request.args.get("repetition_penalty", 1.3)) if not prompt: def error_gen(): yield f"data: {json.dumps({'error': 'Prompt gerekli'})}\n\n" return Response(error_gen(), mimetype="text/event-stream") if model_choice == "stable": model = model_stable tokenizer = tokenizer_stable else: model = model_exp tokenizer = tokenizer_exp def event_stream(): try: for token in generate_stream( model, tokenizer, prompt, max_new_tokens=max_tokens, temperature=temperature, top_k=top_k, top_p=top_p, repetition_penalty=repetition_penalty ): yield f"data: {json.dumps({'token': token})}\n\n" yield f"data: {json.dumps({'done': True})}\n\n" except Exception as e: yield f"data: {json.dumps({'error': str(e)})}\n\n" return Response( event_stream(), mimetype="text/event-stream", headers={ "Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no" } ) # ==================== UYGULAMA BAŞLATMA ==================== if __name__ == "__main__": app.run(host="0.0.0.0", port=7860, debug=False, threaded=True)