sixfinger-api / app.py
sixfingerdev's picture
Update app.py
db0ff49 verified
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 = """
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kayra - Türkçe Dil Modeli Test</title>
<style>{{ styles }}</style>
</head>
<body>
<nav class="navbar">
<h1>🤖 Kayra Türkçe Dil Modeli</h1>
<div class="nav-links">
<a href="/" class="active">Test</a>
<a href="/info">Bilgi</a>
</div>
</nav>
<div class="container">
<div class="card">
<div class="card-header">
<h2>Model Test Arayüzü</h2>
<p>Küçük ölçekli Türkçe dil modellerini karşılaştırmalı olarak test edin.</p>
</div>
<div class="form-group">
<label for="prompt">Soru veya mesajınız:</label>
<textarea id="prompt" placeholder="Örnek: Türkiye'nin başkenti neresidir?"></textarea>
</div>
<div class="form-group">
<label for="model">Model seçin:</label>
<select id="model">
<option value="stable">kayra-1 (Stable - Instruction Tuned)</option>
<option value="exp">kayra-1-exp (Deneysel - Sadece Pretrained)</option>
</select>
</div>
<!-- ==================== SETTINGS PANEL ==================== -->
<div class="settings-panel">
<div class="settings-header" onclick="toggleSettings()">
<h3>⚙️ Gelişmiş Ayarlar</h3>
<button class="settings-toggle" id="settingsToggleBtn">Göster</button>
</div>
<div class="settings-content" id="settingsContent">
<!-- Preset Buttons -->
<div class="preset-buttons">
<button class="preset-btn" onclick="applyPreset('creative')">🎨 Yaratıcı</button>
<button class="preset-btn active" onclick="applyPreset('balanced')">⚖️ Dengeli</button>
<button class="preset-btn" onclick="applyPreset('precise')">🎯 Kesin</button>
<button class="preset-btn" onclick="applyPreset('random')">🎲 Rastgele</button>
</div>
<div class="settings-grid">
<!-- Temperature -->
<div class="setting-item">
<label>
🌡️ Temperature
<span class="value" id="tempValue">0.7</span>
</label>
<input type="range" id="temperature" min="0.1" max="2.0" step="0.1" value="0.7"
oninput="updateSlider('temperature', 'tempValue')">
<p class="setting-description">
Düşük = daha tutarlı, Yüksek = daha yaratıcı
</p>
</div>
<!-- Max Tokens -->
<div class="setting-item">
<label>
📝 Max Tokens
<span class="value" id="maxTokensValue">150</span>
</label>
<input type="range" id="maxTokens" min="10" max="512" step="10" value="150"
oninput="updateSlider('maxTokens', 'maxTokensValue')">
<p class="setting-description">
Üretilecek maksimum token sayısı
</p>
</div>
<!-- Top K -->
<div class="setting-item">
<label>
🔝 Top K
<span class="value" id="topKValue">50</span>
</label>
<input type="range" id="topK" min="1" max="100" step="1" value="50"
oninput="updateSlider('topK', 'topKValue')">
<p class="setting-description">
Her adımda değerlendirilecek en olası K token
</p>
</div>
<!-- Top P -->
<div class="setting-item">
<label>
📊 Top P (Nucleus)
<span class="value" id="topPValue">0.9</span>
</label>
<input type="range" id="topP" min="0.1" max="1.0" step="0.05" value="0.9"
oninput="updateSlider('topP', 'topPValue')">
<p class="setting-description">
Kümülatif olasılık eşiği
</p>
</div>
<!-- Repetition Penalty -->
<div class="setting-item">
<label>
🔄 Repetition Penalty
<span class="value" id="repPenaltyValue">1.3</span>
</label>
<input type="range" id="repPenalty" min="1.0" max="2.0" step="0.1" value="1.3"
oninput="updateSlider('repPenalty', 'repPenaltyValue')">
<p class="setting-description">
Tekrar eden kelimeleri engelleme gücü
</p>
</div>
<!-- Stream Toggle -->
<div class="setting-item">
<label>🌊 Stream Modu</label>
<div class="stream-toggle">
<label class="toggle-switch">
<input type="checkbox" id="streamMode" checked>
<span class="toggle-slider"></span>
</label>
<span class="toggle-label" id="streamLabel">Açık</span>
</div>
<p class="setting-description">
Cevabı kelime kelime göster
</p>
</div>
</div>
</div>
</div>
<!-- ==================== END SETTINGS PANEL ==================== -->
<button class="btn btn-primary" id="submitBtn" onclick="generate()">
🚀 Gönder ve Cevap Al
</button>
<button class="btn btn-stop" id="stopBtn" onclick="stopGeneration()">
⏹️ Durdur
</button>
<div class="loading" id="loading">
<div class="spinner"></div>
<p>Yanıt oluşturuluyor, lütfen bekleyin...</p>
</div>
<div class="response-box" id="responseBox">
<div class="response-header">
<span>Model:</span>
<span class="model-badge" id="modelBadge"></span>
<span class="stream-badge" id="streamBadge" style="display:none;">STREAM</span>
</div>
<div class="response-content" id="responseContent"></div>
<div class="token-info">
<span>Üretilen token: <span class="token-count" id="tokenCount">0</span></span>
<span>Süre: <span class="token-count" id="genTime">0</span>s</span>
</div>
</div>
<div class="error-box" id="errorBox" style="display:none;"></div>
</div>
</div>
<div class="footer">
<p>Kayra - Sıfırdan Türkçe ile eğitilmiş GPT modeli</p>
<p><a href="https://huggingface.co/sixfingerdev" target="_blank">Hugging Face</a></p>
</div>
<script>
let eventSource = null;
let isGenerating = false;
let startTime = null;
// Settings toggle
function toggleSettings() {
const content = document.getElementById('settingsContent');
const btn = document.getElementById('settingsToggleBtn');
content.classList.toggle('active');
btn.textContent = content.classList.contains('active') ? 'Gizle' : 'Göster';
}
// Update slider value display
function updateSlider(sliderId, valueId) {
const slider = document.getElementById(sliderId);
const valueSpan = document.getElementById(valueId);
valueSpan.textContent = slider.value;
}
// Stream toggle
document.getElementById('streamMode').addEventListener('change', function() {
document.getElementById('streamLabel').textContent = this.checked ? 'Açık' : 'Kapalı';
});
// Presets
const presets = {
creative: { temperature: 1.2, maxTokens: 200, topK: 80, topP: 0.95, repPenalty: 1.1 },
balanced: { temperature: 0.7, maxTokens: 150, topK: 50, topP: 0.9, repPenalty: 1.3 },
precise: { temperature: 0.3, maxTokens: 100, topK: 20, topP: 0.8, repPenalty: 1.5 },
random: { temperature: 1.8, maxTokens: 250, topK: 100, topP: 1.0, repPenalty: 1.0 }
};
function applyPreset(presetName) {
const preset = presets[presetName];
document.getElementById('temperature').value = preset.temperature;
document.getElementById('tempValue').textContent = preset.temperature;
document.getElementById('maxTokens').value = preset.maxTokens;
document.getElementById('maxTokensValue').textContent = preset.maxTokens;
document.getElementById('topK').value = preset.topK;
document.getElementById('topKValue').textContent = preset.topK;
document.getElementById('topP').value = preset.topP;
document.getElementById('topPValue').textContent = preset.topP;
document.getElementById('repPenalty').value = preset.repPenalty;
document.getElementById('repPenaltyValue').textContent = preset.repPenalty;
// Update active button
document.querySelectorAll('.preset-btn').forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
}
// Stop generation
function stopGeneration() {
if (eventSource) {
eventSource.close();
eventSource = null;
}
isGenerating = false;
document.getElementById('submitBtn').disabled = false;
document.getElementById('stopBtn').classList.remove('active');
document.getElementById('loading').classList.remove('active');
// Remove cursor
const cursor = document.querySelector('.cursor-blink');
if (cursor) cursor.remove();
}
// Main generate function
async function generate() {
const prompt = document.getElementById("prompt").value.trim();
const model = document.getElementById("model").value;
const streamMode = document.getElementById("streamMode").checked;
const settings = {
temperature: parseFloat(document.getElementById("temperature").value),
max_tokens: parseInt(document.getElementById("maxTokens").value),
top_k: parseInt(document.getElementById("topK").value),
top_p: parseFloat(document.getElementById("topP").value),
repetition_penalty: parseFloat(document.getElementById("repPenalty").value)
};
const responseBox = document.getElementById("responseBox");
const loading = document.getElementById("loading");
const errorBox = document.getElementById("errorBox");
const submitBtn = document.getElementById("submitBtn");
const stopBtn = document.getElementById("stopBtn");
const streamBadge = document.getElementById("streamBadge");
if (!prompt) {
errorBox.textContent = "Lütfen bir mesaj girin!";
errorBox.style.display = "block";
responseBox.classList.remove("active");
return;
}
errorBox.style.display = "none";
responseBox.classList.remove("active");
submitBtn.disabled = true;
isGenerating = true;
startTime = Date.now();
document.getElementById("tokenCount").textContent = "0";
document.getElementById("genTime").textContent = "0";
if (streamMode) {
// Stream mode
loading.classList.add("active");
stopBtn.classList.add("active");
streamBadge.style.display = "inline-block";
const params = new URLSearchParams({
prompt: prompt,
model: model,
...settings
});
eventSource = new EventSource("/generate_stream?" + params.toString());
let fullResponse = "";
let tokenCount = 0;
document.getElementById("modelBadge").textContent = model === "stable" ? "kayra-1 (Stable)" : "kayra-1-exp (Deneysel)";
document.getElementById("responseContent").innerHTML =
"<strong>Soru:</strong> " + escapeHtml(prompt) +
"\\n\\n<strong>Cevap:</strong>\\n<span id='streamOutput'></span><span class='cursor-blink'></span>";
responseBox.classList.add("active");
loading.classList.remove("active");
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.token) {
fullResponse += data.token;
tokenCount++;
document.getElementById("streamOutput").textContent = fullResponse;
document.getElementById("tokenCount").textContent = tokenCount;
document.getElementById("genTime").textContent = ((Date.now() - startTime) / 1000).toFixed(1);
}
if (data.done) {
stopGeneration();
}
if (data.error) {
errorBox.textContent = "Hata: " + data.error;
errorBox.style.display = "block";
stopGeneration();
}
};
eventSource.onerror = function() {
stopGeneration();
};
} else {
// Non-stream mode
loading.classList.add("active");
streamBadge.style.display = "none";
try {
const response = await fetch("/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt, model, ...settings })
});
const data = await response.json();
if (data.error) {
errorBox.textContent = "Hata: " + data.error;
errorBox.style.display = "block";
} else {
document.getElementById("modelBadge").textContent = data.model;
document.getElementById("responseContent").innerHTML =
"<strong>Soru:</strong> " + escapeHtml(data.prompt) +
"\\n\\n<strong>Cevap:</strong>\\n" + escapeHtml(data.response);
document.getElementById("tokenCount").textContent = data.token_count || "N/A";
document.getElementById("genTime").textContent = ((Date.now() - startTime) / 1000).toFixed(1);
responseBox.classList.add("active");
}
} catch (err) {
errorBox.textContent = "Bağlantı hatası: " + err.message;
errorBox.style.display = "block";
} finally {
loading.classList.remove("active");
submitBtn.disabled = false;
isGenerating = false;
}
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
document.getElementById("prompt").addEventListener("keydown", function(e) {
if (e.ctrlKey && e.key === "Enter") {
generate();
}
});
</script>
</body>
</html>
"""
# ==================== BİLGİ SAYFASI HTML ====================
INFO_HTML = """
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kayra - Model Bilgileri</title>
<style>{{ styles }}</style>
</head>
<body>
<nav class="navbar">
<h1>🤖 Kayra Türkçe Dil Modeli</h1>
<div class="nav-links">
<a href="/">Test</a>
<a href="/info" class="active">Bilgi</a>
</div>
</nav>
<div class="container">
<!-- Genel Bakış -->
<div class="card">
<div class="card-header">
<h2>Kayra Nedir?</h2>
<p>Sıfırdan Türkçe ile eğitilmiş deneysel GPT modelleri</p>
</div>
<p>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:</p>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">85M</div>
<div class="stat-label">Parametre</div>
</div>
<div class="stat-card">
<div class="stat-value">500K</div>
<div class="stat-label">Eğitim Dokümanı</div>
</div>
<div class="stat-card">
<div class="stat-value">42.7</div>
<div class="stat-label">Validation PPL</div>
</div>
<div class="stat-card">
<div class="stat-value">MIT</div>
<div class="stat-label">Lisans</div>
</div>
</div>
</div>
<!-- Parametre Açıklamaları -->
<div class="card">
<div class="card-header">
<h2>⚙️ Parametre Açıklamaları</h2>
</div>
<table>
<thead>
<tr>
<th>Parametre</th>
<th>Aralık</th>
<th>Varsayılan</th>
<th>Açıklama</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Temperature</strong></td>
<td>0.1 - 2.0</td>
<td>0.7</td>
<td>Çıktının rastgeleliğini kontrol eder. Düşük değerler daha tutarlı, yüksek değerler daha yaratıcı sonuçlar üretir.</td>
</tr>
<tr>
<td><strong>Max Tokens</strong></td>
<td>10 - 512</td>
<td>150</td>
<td>Üretilecek maksimum token (kelime parçası) sayısı.</td>
</tr>
<tr>
<td><strong>Top K</strong></td>
<td>1 - 100</td>
<td>50</td>
<td>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.</td>
</tr>
<tr>
<td><strong>Top P (Nucleus)</strong></td>
<td>0.1 - 1.0</td>
<td>0.9</td>
<td>Kümülatif olasılığı P'yi geçen tokenlerden seçim yapılır. Temperature ile birlikte kullanılır.</td>
</tr>
<tr>
<td><strong>Repetition Penalty</strong></td>
<td>1.0 - 2.0</td>
<td>1.3</td>
<td>Tekrarlayan kelimeleri cezalandırır. Yüksek değerler tekrarı azaltır ama anlam bozulabilir.</td>
</tr>
<tr>
<td><strong>Stream Mode</strong></td>
<td>Açık/Kapalı</td>
<td>Açık</td>
<td>Açıkken cevap kelime kelime gösterilir, kapalıyken tamamı bir anda gösterilir.</td>
</tr>
</tbody>
</table>
</div>
<!-- Preset Açıklamaları -->
<div class="card">
<div class="card-header">
<h2>🎛️ Hazır Ayarlar (Presets)</h2>
</div>
<div class="two-column">
<div class="setting-item">
<h4>🎨 Yaratıcı</h4>
<p>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.</p>
<code>temp=1.2, top_k=80, top_p=0.95, rep=1.1</code>
</div>
<div class="setting-item">
<h4>⚖️ Dengeli</h4>
<p>Varsayılan ayarlar. Çoğu kullanım senaryosu için uygundur.</p>
<code>temp=0.7, top_k=50, top_p=0.9, rep=1.3</code>
</div>
<div class="setting-item">
<h4>🎯 Kesin</h4>
<p>Düşük temperature ile daha tutarlı ve tahmin edilebilir çıktılar. Bilgi gerektiren sorular için.</p>
<code>temp=0.3, top_k=20, top_p=0.8, rep=1.5</code>
</div>
<div class="setting-item">
<h4>🎲 Rastgele</h4>
<p>Maksimum rastgelelik. Çılgın ve beklenmedik sonuçlar için. Dikkatli kullanın!</p>
<code>temp=1.8, top_k=100, top_p=1.0, rep=1.0</code>
</div>
</div>
</div>
<!-- Model Karşılaştırması -->
<div class="card">
<div class="card-header">
<h2>Model Karşılaştırması</h2>
</div>
<table>
<thead>
<tr>
<th>Özellik</th>
<th>kayra-1 (Stable)</th>
<th>kayra-1-exp (Deneysel)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Tür</td>
<td>Instruction-tuned</td>
<td>Sadece Pretrained</td>
</tr>
<tr>
<td>Kullanım</td>
<td>Soru-Cevap formatında</td>
<td>Metin tamamlama</td>
</tr>
<tr>
<td>Stabilite</td>
<td><span class="badge badge-success">Stabil</span></td>
<td><span class="badge badge-warning">Deneysel</span></td>
</tr>
<tr>
<td>Prompt Formatı</td>
<td>### Soru: ... ### Cevap:</td>
<td>Düz metin</td>
</tr>
<tr>
<td>Önerilen Kullanım</td>
<td>Genel test ve demo</td>
<td>Araştırma ve analiz</td>
</tr>
</tbody>
</table>
</div>
<!-- Mimari Detaylar -->
<div class="card">
<div class="card-header">
<h2>Teknik Mimari</h2>
</div>
<table>
<tbody>
<tr><td><strong>Model Türü</strong></td><td>Decoder-only Transformer (GPT-style)</td></tr>
<tr><td><strong>Katman Sayısı</strong></td><td>10</td></tr>
<tr><td><strong>Hidden Size</strong></td><td>640</td></tr>
<tr><td><strong>Attention Heads</strong></td><td>10</td></tr>
<tr><td><strong>FFN Size</strong></td><td>2560</td></tr>
<tr><td><strong>Vocabulary</strong></td><td>32,000 BPE tokens</td></tr>
<tr><td><strong>Context Length</strong></td><td>512 tokens</td></tr>
<tr><td><strong>Toplam Parametre</strong></td><td>~85 milyon</td></tr>
</tbody>
</table>
</div>
<!-- Eğitim Verisi -->
<div class="card">
<div class="card-header">
<h2>Eğitim Verisi</h2>
</div>
<table>
<thead>
<tr>
<th>Kaynak</th>
<th>Doküman Sayısı</th>
<th>Açıklama</th>
</tr>
</thead>
<tbody>
<tr>
<td>Wikipedia TR</td>
<td>~170,000</td>
<td>Türkçe Vikipedi makaleleri</td>
</tr>
<tr>
<td>mC4 Turkish</td>
<td>~330,000</td>
<td>Common Crawl web dokümanları</td>
</tr>
<tr>
<td><strong>Toplam</strong></td>
<td><strong>~500,000</strong></td>
<td>MinHash LSH ile dedupe edilmiş</td>
</tr>
</tbody>
</table>
</div>
<!-- Kullanım Örneği -->
<div class="card">
<div class="card-header">
<h2>Kod Örneği</h2>
</div>
<div class="code-block">
<pre>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))</pre>
</div>
</div>
<!-- Lisans ve Atıf -->
<div class="card">
<div class="card-header">
<h2>Lisans ve Atıf</h2>
</div>
<p><strong>Lisans:</strong> MIT License - Ticari ve akademik kullanım serbesttir.</p>
<div class="code-block">
<pre>@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}
}</pre>
</div>
<div class="highlight" style="margin-top: 1rem;">
<strong>Uyarı:</strong> 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.
</div>
</div>
</div>
<div class="footer">
<p>Kayra - Türkçe'yi Yaratan Zeka</p>
<p><a href="https://huggingface.co/sixfingerdev" target="_blank">Hugging Face</a></p>
</div>
</body>
</html>
"""
# ==================== 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)