Upload 11 files
Browse files- LICENSE.md +129 -0
- README.md +396 -0
- chat_format.json +91 -0
- config.py +50 -0
- generation_config.json +13 -0
- model.py +307 -0
- model.safetensors +3 -0
- model_config.json +16 -0
- tokenizer.json +0 -0
- tokenizer_config.json +16 -0
- tokenizer_info.json +60 -0
LICENSE.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Yedikule Model Lisansı
|
| 2 |
+
|
| 3 |
+
Sürüm 2.0
|
| 4 |
+
|
| 5 |
+
Telif hakkı sahibi: **Ahmet Karakuş** (Marul AI)
|
| 6 |
+
|
| 7 |
+
## Kısaca
|
| 8 |
+
|
| 9 |
+
Modeli özgürce kullanabilir, üzerine kendi modelinizi eğitebilir ve paylaşabilirsiniz. Üç şartla: ticari kullanım için izin alın, Yedikule'ye atıf yapın, türev modelinizin adında Yedikule geçsin.
|
| 10 |
+
|
| 11 |
+
## 1. Kapsam
|
| 12 |
+
|
| 13 |
+
Bu lisans Yedikule modelinin şu bileşenlerini kapsar:
|
| 14 |
+
|
| 15 |
+
- Model ağırlıkları ve bunlardan türetilen her biçim (GGUF, ONNX, nicelenmiş sürümler, ince ayar sonucu ağırlıklar, birleştirilmiş modeller)
|
| 16 |
+
- Tokenizer ve sözlüğü
|
| 17 |
+
- Model mimarisi kodu (`model.py`, `config.py`)
|
| 18 |
+
- Yapılandırma dosyaları ve dokümantasyon
|
| 19 |
+
|
| 20 |
+
Bunların tamamı bundan sonra "Model" olarak anılır. Model üzerindeki fikri mülkiyet hakları Ahmet Karakuş'a aittir.
|
| 21 |
+
|
| 22 |
+
## 2. İzin verilen kullanım
|
| 23 |
+
|
| 24 |
+
Aşağıdakiler için ayrıca izin almanıza gerek yoktur. Hepsi ticari olmayan kullanımla sınırlıdır ve 3. bölümdeki şartlara tabidir.
|
| 25 |
+
|
| 26 |
+
**2.1 Kullanım.** Modeli kendi cihazınızda veya sunucunuzda çalıştırabilirsiniz.
|
| 27 |
+
|
| 28 |
+
**2.2 Eğitim ve araştırma.** Ders, atölye, tez, akademik araştırma ve ölçüt çalışmalarında kullanabilirsiniz.
|
| 29 |
+
|
| 30 |
+
**2.3 Yeniden eğitim ve türev model.** Model üzerinde ince ayar (fine-tuning) yapabilir, sürekli eğitimle geliştirebilir, LoRA veya benzeri adaptör ekleyebilir, damıtabilir, başka modellerle birleştirebilirsiniz. Ortaya çıkan modeli yayımlayabilirsiniz.
|
| 31 |
+
|
| 32 |
+
**2.4 Biçim dönüştürme.** Modeli GGUF, ONNX ve benzeri biçimlere çevirebilir, niceleyebilir (Q4, Q8 ve diğerleri) ve bu dosyaları paylaşabilirsiniz.
|
| 33 |
+
|
| 34 |
+
**2.5 Yeniden dağıtım.** Modeli olduğu gibi veya değiştirilmiş halde başka platformlarda yayımlayabilirsiniz.
|
| 35 |
+
|
| 36 |
+
## 3. Şartlar
|
| 37 |
+
|
| 38 |
+
İzin verilen kullanımların tamamı aşağıdaki şartlara bağlıdır.
|
| 39 |
+
|
| 40 |
+
**3.1 Atıf.** Modeli kullandığınız veya ondan türettiğiniz her yerde Yedikule'ye ve geliştiricisine atıf yapmalısınız. Yayımladığınız model kartında, dokümantasyonda veya akademik yayında şu bilgi bulunmalıdır:
|
| 41 |
+
|
| 42 |
+
```
|
| 43 |
+
Bu çalışma Yedikule modeline dayanmaktadır.
|
| 44 |
+
Yedikule, 202M parametreli Türkçe dil modeli.
|
| 45 |
+
Ahmet Karakuş, Marul AI, 2026.
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
**3.2 İsim.** Yedikule'den türettiğiniz bir modeli yayımlarsanız, modelin adında **Yedikule** geçmelidir. Örnek: `Yedikule-Tip`, `Yedikule-Kod`, `Firma-Yedikule-Asistan`. Modelin Yedikule'den türediğini gizleyemez, kendi sıfırdan eğitiminiz gibi sunamazsınız.
|
| 49 |
+
|
| 50 |
+
**3.3 Türevler de bu lisansa tabidir.** Bu modelden türetilen her çalışma (ince ayar, sürekli eğitim, adaptör, damıtma, birleştirme, niceleme, biçim dönüşümü ve benzeri) bu lisansın kapsamındadır ve aynı koşullara bağlıdır. Türev modelinizi dağıtırken bu lisans dosyasını da birlikte vermeniz zorunludur. Türev modelinize daha gevşek bir lisans veremez, kendi lisansınızla yeniden lisanslayamazsınız. Bu yükümlülük türevin türevi için de sürer.
|
| 51 |
+
|
| 52 |
+
**3.4 Telif bilgisinin korunması.** Telif hakkı beyanını ve geliştirici bilgisini kaldıramaz veya değiştiremezsiniz.
|
| 53 |
+
|
| 54 |
+
## 4. Ticari kullanım
|
| 55 |
+
|
| 56 |
+
Ticari kullanım, telif hakkı sahibinin yazılı izni olmadan yasaktır. Ticari kullanım şunları kapsar:
|
| 57 |
+
|
| 58 |
+
- Modeli doğrudan veya dolaylı gelir getiren bir ürüne veya hizmete gömmek
|
| 59 |
+
- API olarak ücretli sunmak, abonelik veya reklam geliri elde etmek
|
| 60 |
+
- Kurum içi ticari süreçlerde çalıştırmak
|
| 61 |
+
- Modelden türetilmiş bir modeli ticari amaçla kullanmak veya satmak
|
| 62 |
+
- Danışmanlık hizmetinde kullanmak
|
| 63 |
+
|
| 64 |
+
Ticari kullanım için telif hakkı sahibinden izin alın. Talebinizi şu adrese gönderin:
|
| 65 |
+
|
| 66 |
+
**marulai.resmi@gmail.com**
|
| 67 |
+
|
| 68 |
+
Talepler ayrıca değerlendirilir ve yazılı sözleşmeye bağlanır. Bu, modelden türetilmiş modellerin ticari kullanımı için de geçerlidir; türev modeli siz eğitmiş olsanız bile ticari kullanım izne bağlıdır.
|
| 69 |
+
|
| 70 |
+
Akademik araştırma, kişisel projeler, öğrenme ve açık kaynak topluluk çalışmaları ticari kullanım sayılmaz.
|
| 71 |
+
|
| 72 |
+
## 5. Eğitim verisi
|
| 73 |
+
|
| 74 |
+
Bu model iki aşamada eğitildi. Ön eğitim corpus'u kısmen `uonlp/CulturaX` Türkçe alt kümesinden, kısmen telif hakkı sahibinin kendi web taramasından derlendi; CulturaX'in koşulları `mC4` ve `OSCAR` kümelerini takip eder ve her ikisi de Common Crawl üzerinden üretilmiştir.
|
| 75 |
+
|
| 76 |
+
Talimat ayarı aşamasında telif hakkı sahibinin hazırladığı `MarulAI/Marul-SFT-TR` veri seti kullanıldı. Bu veri seti Apache 2.0 lisansıyla ayrıca yayımlanmıştır.
|
| 77 |
+
|
| 78 |
+
Kaynak veri kümelerinin lisansları bu lisanstan bağımsız olarak yürürlüktedir. Modeli ticari amaçla kullanmak isteyen taraf, telif hakkı sahibinden izin almanın yanında ön eğitim verisinin kaynak koşullarının (mC4, OSCAR ve Common Crawl) kendi kullanım biçimine uygunluğunu da ayrıca değerlendirmelidir. Telif hakkı sahibinin verdiği ticari izin, kaynak veri koşullarından doğan yükümlülükleri ortadan kaldırmaz.
|
| 79 |
+
|
| 80 |
+
## 6. Yasaklar
|
| 81 |
+
|
| 82 |
+
**6.1** İzinsiz ticari kullanım.
|
| 83 |
+
|
| 84 |
+
**6.2** Modeli veya türevini, Yedikule kaynaklı olduğunu gizleyerek sunmak.
|
| 85 |
+
|
| 86 |
+
**6.3** Telif hakkı beyanını, lisans dosyasını veya geliştirici bilgisini kaldırmak.
|
| 87 |
+
|
| 88 |
+
**6.4** Modeli yasa dışı içerik üretmek, kişileri hedef almak, yanıltıcı bilgi yaymak veya zarar verecek biçimde kullanmak.
|
| 89 |
+
|
| 90 |
+
## 7. Garanti reddi
|
| 91 |
+
|
| 92 |
+
Model olduğu gibi sunulur. Telif hakkı sahibi, Modelin belirli bir amaca uygunluğu, kesintisiz çalışması veya ürettiği bilginin doğruluğu konusunda garanti vermez.
|
| 93 |
+
|
| 94 |
+
Model istatistiksel bir dil modelidir; yanlış, eksik veya yanıltıcı bilgi üretebilir. Tıbbi, hukuki, finansal veya güvenlik açısından kritik kararlarda tek başına kullanılmamalıdır. Çıktıların doğrulanması kullanıcının sorumluluğundadır.
|
| 95 |
+
|
| 96 |
+
## 8. Sorumluluğun sınırlandırılması
|
| 97 |
+
|
| 98 |
+
Telif hakkı sahibi, Modelin kullanımından doğan doğrudan veya dolaylı hiçbir zarardan sorumlu tutulamaz. Modeli kullanan taraf, kullanımdan doğacak tüm hukuki ve mali sorumluluğu kabul eder.
|
| 99 |
+
|
| 100 |
+
## 9. Lisansın feshi
|
| 101 |
+
|
| 102 |
+
Bu lisansın şartlarını ihlal etmeniz durumunda kullanım izniniz kendiliğinden sona erer. İhlali öğrendikten sonra otuz gün içinde düzeltirseniz izniniz geri kazanılır. Fesih, ihlal nedeniyle doğan hukuki sorumluluğu ortadan kaldırmaz.
|
| 103 |
+
|
| 104 |
+
## 10. Uygulanacak hukuk
|
| 105 |
+
|
| 106 |
+
Bu lisans Türkiye Cumhuriyeti hukukuna tabidir. Uyuşmazlıklarda İstanbul mahkemeleri ve icra daireleri yetkilidir.
|
| 107 |
+
|
| 108 |
+
## 11. İletişim
|
| 109 |
+
|
| 110 |
+
Ticari lisans, izin talepleri ve ihlal bildirimleri için:
|
| 111 |
+
|
| 112 |
+
Ahmet Karakuş, Marul AI
|
| 113 |
+
marulai.resmi@gmail.com
|
| 114 |
+
|
| 115 |
+
---
|
| 116 |
+
|
| 117 |
+
## English summary
|
| 118 |
+
|
| 119 |
+
This is a non-binding summary. The Turkish text above is the governing license.
|
| 120 |
+
|
| 121 |
+
Copyright Ahmet Karakuş (Marul AI).
|
| 122 |
+
|
| 123 |
+
**You may**, without asking: run the model, use it for education and research, fine-tune it, distill it, merge it, quantize it, convert it to other formats, and publish the result. All of this is limited to non-commercial use.
|
| 124 |
+
|
| 125 |
+
**You must**: give attribution to Yedikule and its developer; include the name **Yedikule** in the name of any derivative model you publish; ship this license with the model or any derivative; keep the copyright notice intact. Any derivative work remains subject to this same license and cannot be relicensed under different terms.
|
| 126 |
+
|
| 127 |
+
**You may not**, without written permission: use the model or any derivative commercially; present the model as your own work or hide that it derives from Yedikule; remove the license or copyright notice; or use it for illegal, harmful or deceptive purposes.
|
| 128 |
+
|
| 129 |
+
Provided as is, without warranty. Contact the copyright holder for commercial licensing: Ahmet Karakuş, Marul AI, marulai.resmi@gmail.com
|
README.md
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: other
|
| 3 |
+
license_name: yedikule-model-lisansi-v2
|
| 4 |
+
license_link: LICENSE.md
|
| 5 |
+
base_model: MarulAI/Yedikule-202M-Base
|
| 6 |
+
datasets:
|
| 7 |
+
- MarulAI/Marul-SFT-TR
|
| 8 |
+
language:
|
| 9 |
+
- tr
|
| 10 |
+
pipeline_tag: text-generation
|
| 11 |
+
tags:
|
| 12 |
+
- turkish
|
| 13 |
+
- turkce
|
| 14 |
+
- marul-ai
|
| 15 |
+
- yedikule
|
| 16 |
+
- instruct
|
| 17 |
+
- conversational
|
| 18 |
+
- reasoning
|
| 19 |
+
- tool-calling
|
| 20 |
+
---
|
| 21 |
+
|
| 22 |
+
# Yedikule 1.4
|
| 23 |
+
|
| 24 |
+
Yedikule 1.4, Marul AI'ın Türkçe için sıfırdan eğittiği 202 milyon parametreli dil modelinin en yeni talimat ayarlı sürümüdür. Cevap vermeden önce kısa bir düşünme yapar, sistem mesajındaki kurallara uyar, kendisine verilen araçları açıklamalarına bakarak seçip çağırır ve çok turlu sohbetlerde bağlamı korur.
|
| 25 |
+
|
| 26 |
+
Bu sürüm, tamamen yeni ve bu model için hazırlanmış [Marul-SFT-TR](https://huggingface.co/datasets/MarulAI/Marul-SFT-TR) veri setiyle eğitildi. Önceki sürüme göre en büyük ilerleme düşünme, talimat takibi ve araç kullanımındadır: model artık eğitimde hiç görmediği araçlarla da çalışabiliyor. Ölçütlerde de yedi testin ortalamasında önceki sürümün ve temel modelin önüne geçti; okuduğunu anlamada ve zor fen sorularında açık farkla en iyi sonucu verdi. Üstelik modelin Türkçe dil bilgisi ve genel metin anlama yeteneği eksiksiz korundu.
|
| 27 |
+
|
| 28 |
+
| | |
|
| 29 |
+
|---|---|
|
| 30 |
+
| Geliştirici | Ahmet Karakuş, Marul AI |
|
| 31 |
+
| Model türü | Decoder-only Transformer, talimat ayarlı |
|
| 32 |
+
| Dil | Türkçe |
|
| 33 |
+
| Parametre | 202.1M |
|
| 34 |
+
| Bağlam | 2048 token |
|
| 35 |
+
| Ağırlık biçimi | bf16, safetensors |
|
| 36 |
+
| Temel model | [Yedikule 202M Base](https://huggingface.co/MarulAI/Yedikule-202M-Base) |
|
| 37 |
+
| Eğitim verisi | [Marul-SFT-TR](https://huggingface.co/datasets/MarulAI/Marul-SFT-TR) |
|
| 38 |
+
| Lisans | Yedikule Model Lisansı 2.0 |
|
| 39 |
+
| Sürüm | 1.4 (Ekim 2026) |
|
| 40 |
+
|
| 41 |
+
GGUF sürümü llama.cpp, Ollama ve LM Studio ile doğrudan çalışır: [MarulAI/Yedikule-1.4-GGUF](https://huggingface.co/MarulAI/Yedikule-1.4-GGUF). Depoda ana modelle bit düzeyinde aynı BF16 dosyasının yanında, bu model için ölçülerek hazırlanmış Q8_0, Q6_K, Q5_K_M ve Q4_K_M sürümleri de vardır; en küçüğü 140 MB'tır.
|
| 42 |
+
|
| 43 |
+
## Öne çıkanlar
|
| 44 |
+
|
| 45 |
+
- **Düşünerek cevap verme.** Her cevaptan önce kullanıcının ne istediğini ve nasıl cevap vereceğini kısaca planlar. Düşünme ayrı bir bölümdür; arayüzde gizlenebilir.
|
| 46 |
+
- **Araç çağırma.** Araç tanımları sistem turunda verilir. Model doğru aracı adına değil açıklamasına bakarak seçer, parametreleri kullanıcının söylediklerinden çıkarır, gerektiğinde art arda veya aynı anda birden fazla araç çağırır ve sonucu doğal bir dille aktarır. Araç gerekmeyen sorularda araç çağırmaz.
|
| 47 |
+
- **Esnek sistem mesajı.** Tek bir sabit sistem mesajına bağlı değildir. Persona, kural, biçim ve uzunluk kısıtlarını, kurumsal asistan senaryolarını ve sohbet ortasında değişen talimatları izler.
|
| 48 |
+
- **Çok turlu sohbet.** Önceki turlarda söylenenleri hatırlar, kullanıcı fikir değiştirdiğinde ya da yeni bir kısıt eklediğinde cevabını buna göre günceller.
|
| 49 |
+
- **Doğal Türkçe.** Resmî yazan kullanıcıyı da, Türkçe karakter kullanmadan, yazım hatasıyla ya da argoyla yazanı da anlar; her zaman açık ve düzgün bir Türkçeyle cevap verir.
|
| 50 |
+
|
| 51 |
+
## Örnek
|
| 52 |
+
|
| 53 |
+
Aşağıdaki çıktılar, bu kartın Kullanım bölümündeki kodla ve önerilen üretim ayarlarıyla üretildi.
|
| 54 |
+
|
| 55 |
+
```
|
| 56 |
+
Kullanıcı : Fotosentezi kısaca anlatır mısın?
|
| 57 |
+
Düşünme : Kullanıcı fotosentezi tek cümleyle istiyor. Işığa bağlı tepkimeler ve Calvin döngüsü olarak
|
| 58 |
+
iki adımı, kısa ve net vermeliyim.
|
| 59 |
+
Yedikule : Fotosentez, bitkilerin güneş ışığını kullanarak besin üretmesi sürecidir.
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
Araç çağırma (sistem turunda `hava_durumu` aracı tanımlı):
|
| 63 |
+
|
| 64 |
+
```
|
| 65 |
+
Kullanıcı : Yarın İzmir'de hava nasıl olacak?
|
| 66 |
+
Araç çağrısı : {"name":"hava_durumu","arguments":{"sehir":"İzmir"}}
|
| 67 |
+
Araç sonucu : {"sehir":"İzmir","durum":"parçalı bulutlu","en_yuksek":27,"en_dusuk":19}
|
| 68 |
+
Düşünme : Sonuç yarın İzmir'de parçalı bulutlu, en yüksek 27, en düşük 19 derece. Kullanıcıya bunu
|
| 69 |
+
sade biçimde aktarmalıyım; sonuçta olmayan bilgi eklemeden.
|
| 70 |
+
Yedikule : Yarın İzmir'de hava parçalı bulutlu olacak. En yüksek 27, en düşük 19 derece.
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
## Değerlendirme
|
| 74 |
+
|
| 75 |
+
Bütün modeller aynı kodla, aynı veriyle ve aynı koşullarda ölçüldü. Çoktan seçmeli görevlerde her seçeneğin bağlama göre olasılığı hesaplanır ve en olası seçenek modelin cevabı sayılır (0-shot, lm-evaluation-harness yöntemi). Üretim yapılmadığı için sonuçlar örnekleme ayarlarından bağımsızdır ve birebir tekrarlanabilir.
|
| 76 |
+
|
| 77 |
+
| Ölçüt | Rastgele | Yedikule 202M Base | Yedikule 202M Instruct | Yedikule 1.4 |
|
| 78 |
+
|---|---:|---:|---:|---:|
|
| 79 |
+
| Belebele, okuduğunu anlama | 25,0 | 28,8 | 29,1 | **31,1** |
|
| 80 |
+
| XCOPA, neden sonuç çıkarımı | 50,0 | 60,2 | **60,6** | 59,2 |
|
| 81 |
+
| TurkishMMLU, lise düzeyi bilgi | 20,0 | **23,0** | 20,6 | 21,7 |
|
| 82 |
+
| ARC-TR kolay, fen soruları | 25,0 | 37,3 | **39,8** | 38,8 |
|
| 83 |
+
| ARC-TR zor, fen soruları | 25,0 | 24,1 | 26,5 | **28,5** |
|
| 84 |
+
| HellaSwag-TR, sağduyu | 25,0 | 35,0 | **35,4** | **35,4** |
|
| 85 |
+
| TurBLiMP, dil bilgisi | 50,0 | 92,8 | **93,1** | 92,6 |
|
| 86 |
+
| **Ortalama** | | 43,0 | 43,6 | **43,9** |
|
| 87 |
+
|
| 88 |
+
Belebele, TurkishMMLU, ARC ve HellaSwag'de uzunluğa göre normalleştirilmiş doğruluk (acc_norm), XCOPA ve TurBLiMP'te doğruluk (acc) verilmiştir. Yedikule 202M Instruct, bir önceki talimat ayarlı sürümdür.
|
| 89 |
+
|
| 90 |
+
Yedikule 1.4 yedi ölçütün ortalamasında üç modelin en yükseğidir. En belirgin ilerleme okuduğunu anlamada (Belebele: temel modele göre 2,3, önceki sürüme göre 2,0 puan) ve zor fen sorularındadır (ARC zor: temel modele göre 4,4, önceki sürüme göre 2,0 puan); her ikisi de modelin metni okuyup soruya göre akıl yürütmesini gerektirir. Dil bilgisi (TurBLiMP yüzde 92,6) ve sağduyu (HellaSwag) korunmuştur: talimat ayarı modele yeni beceriler kazandırırken Türkçesine zarar vermemiştir. XCOPA, TurkishMMLU ve ARC kolaydaki farklar ölçüm belirsizliği sınırındadır. Bu tür bilgi ölçütlerinin sonucunu esas olarak ön eğitim belirler; 202 milyon parametrelik bir modelin geniş dünya bilgisi isteyen TurkishMMLU gibi ölçütlerde rastgele düzeye yakın kalması beklenen bir durumdur.
|
| 91 |
+
|
| 92 |
+
Talimat ayarının asıl kazancı bu ölçütlerin ölçmediği yerdedir: düşünme, talimat takibi ve araç kullanımı. Aşağıdaki sonuçlar, Marul-SFT-TR'nin eğitimde hiç kullanılmayan doğrulama sohbetlerinde, modelin ilk cevabı en olası tokenler seçilerek üretilip ölçüldü.
|
| 93 |
+
|
| 94 |
+
| Araç kullanımı | Sonuç |
|
| 95 |
+
|---|---:|
|
| 96 |
+
| Araç gereken durumlarda geçerli bir çağrı üretme (282 sohbet) | yüzde 89,7 |
|
| 97 |
+
| Doğru aracı seçme | yüzde 75,9 |
|
| 98 |
+
| Zorunlu parametreleri eksiksiz doldurma | yüzde 87,9 |
|
| 99 |
+
| Eğitimde hiç görülmemiş araç adlarında doğru aracı seçme (30 sohbet) | yüzde 73,3 |
|
| 100 |
+
| Araç gerekmeyen durumlarda araç çağırmama (159 sohbet) | yüzde 73,0 |
|
| 101 |
+
|
| 102 |
+
Önceki sürüm yalnızca eğitimde gördüğü 15 araçla güvenilir biçimde çalışabiliyordu. Yedikule 1.4 ise araçları adlarından değil açıklamalarından tanımayı öğrendi; eğitimde hiç görmediği araçlarla da çalışabiliyor.
|
| 103 |
+
|
| 104 |
+
Ölçütler: [Belebele](https://huggingface.co/datasets/facebook/belebele) (tur_Latn, okuduğunu anlama), [XCOPA](https://huggingface.co/datasets/cambridgeltl/xcopa) (tr, neden sonuç çıkarımı), [TurkishMMLU](https://huggingface.co/datasets/AYueksel/TurkishMMLU) (lise düzeyi bilgi), [ARC-TR](https://huggingface.co/datasets/malhajar/arc-tr) (fen soruları, kolay ve zor), [HellaSwag-TR](https://huggingface.co/datasets/malhajar/hellaswag_tr-v0.2) (sağduyu ile cümle tamamlama), [TurBLiMP](https://huggingface.co/datasets/juletxara/turblimp) (Türkçe dil bilgisi, 16 olguda 15.980 cümle çifti).
|
| 105 |
+
|
| 106 |
+
## Eğitim verisi: Marul-SFT-TR
|
| 107 |
+
|
| 108 |
+
Yedikule 1.4'ün sohbet yeteneklerinin kaynağı, bu model için baştan Türkçe olarak tasarlanan [Marul-SFT-TR](https://huggingface.co/datasets/MarulAI/Marul-SFT-TR) veri setidir. Çeviri değildir; Türk kültürü, günlük hayat, kurumlar ve konuşma alışkanlıkları gözetilerek hazırlandı.
|
| 109 |
+
|
| 110 |
+
| | |
|
| 111 |
+
|---|---|
|
| 112 |
+
| Sohbet | 99.847 |
|
| 113 |
+
| Kategori ve görev türü | 18 kategori, 244 görev türü |
|
| 114 |
+
| Çok turlu sohbet | yüzde 85,7 |
|
| 115 |
+
| Farklı sistem mesajı | 55.208 |
|
| 116 |
+
| Araç tanımı içeren sohbet | 16.724 (9.571 farklı araç adı) |
|
| 117 |
+
| Araç çağrısı | 24.510 (ardışık ve paralel çağrılar dahil) |
|
| 118 |
+
| Düşünme metni olan asistan cevabı | yüzde 100 |
|
| 119 |
+
|
| 120 |
+
Her sohbet ayrıntılı bir tariften üretildi: görev türü, kategori, sistem mesajı, kullanıcı profili ve yazım tarzı, hedef uzunluk, tur sayısı ve araç görevlerinde o senaryoya özel araç tanımları. Ardından çok katmanlı bir kalite kontrolünden geçti: biçim, dil ve araç çağrısı kontrolleri, birbirine benzeyen sohbetlerin ayıklanması ve her sohbetin bağımsız bir hakem model tarafından olgusal doğruluk, hesap doğruluğu, talimata uyum, araç kullanımı ve Türkçenin doğallığı açısından tek tek incelenmesi. 231.718 üretim denemesinin yalnızca yaklaşık yüzde 43'ü bütün kontrolleri geçti. Veri setinin kendisi Apache 2.0 lisansıyla ayrıca yayımlanmıştır.
|
| 121 |
+
|
| 122 |
+
## Eğitim
|
| 123 |
+
|
| 124 |
+
Model, [Yedikule 202M Base](https://huggingface.co/MarulAI/Yedikule-202M-Base) ağırlıklarından başlatılarak Marul-SFT-TR ile tam parametre ince ayarla eğitildi.
|
| 125 |
+
|
| 126 |
+
| | |
|
| 127 |
+
|---|---|
|
| 128 |
+
| Eğitim / doğrulama | 97.261 / 2.586 sohbet |
|
| 129 |
+
| Epoch | 2 (2.353 optimizer adımı) |
|
| 130 |
+
| Optimizer | AdamW, tepe öğrenme hızı 1,5e-4, kosinüs azalma |
|
| 131 |
+
| Kayıp | yalnızca asistan turları: düşünme, cevap, araç çağrısı ve `<\|im_end\|>` |
|
| 132 |
+
| Doğrulama kaybı | 3,577'den 1,672'ye |
|
| 133 |
+
| Biçim tokeni isabeti | yüzde 95 |
|
| 134 |
+
|
| 135 |
+
Talimat ayarı sırasında modelin genel Türkçe bilgisini kaybetmemesi için eğitime bir miktar düz Türkçe metin karıştırıldı. Eğitimden sonra, eğitimde hiç görülmeyen genel web metinlerinde ölçülen perplexity değişimi yalnızca yüzde 1,2 oldu; model yeni beceriler kazanırken dil bilgisini korudu.
|
| 136 |
+
|
| 137 |
+
## Sohbet şablonu
|
| 138 |
+
|
| 139 |
+
Şablon eğitimde görülen biçimin birebir aynısıdır.
|
| 140 |
+
|
| 141 |
+
```
|
| 142 |
+
<|bos|><|im_start|><|system|>
|
| 143 |
+
{sistem mesajı}<|im_end|>
|
| 144 |
+
<|im_start|><|user|>
|
| 145 |
+
{kullanıcı mesajı}<|im_end|>
|
| 146 |
+
<|im_start|><|assistant|>
|
| 147 |
+
<think>{düşünme}</think>
|
| 148 |
+
{cevap}<|im_end|>
|
| 149 |
+
```
|
| 150 |
+
|
| 151 |
+
Standart ChatML değildir: rol adları düz metin değil, ayrı özel tokenlerdir. Sistem turu isteğe bağlıdır.
|
| 152 |
+
|
| 153 |
+
| Kimlik | Token | Anlamı |
|
| 154 |
+
|---:|---|---|
|
| 155 |
+
| 3 | `<\|bos\|>` | dizinin başı |
|
| 156 |
+
| 4 | `<\|im_start\|>` | tur başlangıcı |
|
| 157 |
+
| 5 | `<\|im_end\|>` | tur sonu, üretim burada durur |
|
| 158 |
+
| 6, 7, 8 | `<\|system\|>`, `<\|user\|>`, `<\|assistant\|>` | roller |
|
| 159 |
+
| 9 | `<think>` | düşünme başlangıcı |
|
| 160 |
+
| 10 | `</think>` | düşünme sonu |
|
| 161 |
+
| 11 | `<tool_call>` | araç çağrısı başlangıcı |
|
| 162 |
+
| 12 | `</tool_call>` | araç çağrısı sonu |
|
| 163 |
+
| 13 | `<\|tool\|>` | araç sonucu rolü |
|
| 164 |
+
| 14 | `<tools>` | araç tanımları başlangıcı |
|
| 165 |
+
| 15 | `</tools>` | araç tanımları sonu |
|
| 166 |
+
|
| 167 |
+
Token adları ve kimlikleri GGUF dosyasında da aynıdır. Taban modelin tokenizer'ında 9-15 kimlikleri `<|reserved_0|>` ile `<|reserved_6|>` arasındaki adlarla durur. Kimlikler aynı olduğu için iki tokenizer metni aynı token dizisine çevirir: talimat verisinin tamamında (99.847 sohbet, 83,4 milyon token) ve düz Türkçe metinde sonuç birebir aynıdır. Taban modelden ince ayar yaparken bu tokenizer'ı doğrudan kullanabilirsiniz.
|
| 168 |
+
|
| 169 |
+
Üretimi `<|im_end|>` (5) tokeninde durdurun. İstemi her zaman `<|im_start|><|assistant|>\n` ile bitirin; model cevabına kendiliğinden düşünme bloğuyla başlar. Çok turlu sohbetlerde önceki asistan turlarını düşünmeleriyle birlikte geçmişe ekleyin; model eğitimde bu biçimi gördü. Aynı bilgiler `chat_format.json` dosyasında da bulunur.
|
| 170 |
+
|
| 171 |
+
### Araç çağırma biçimi
|
| 172 |
+
|
| 173 |
+
Araç tanımları sistem turunun sonuna, her biri tek satırlık ve boşluksuz bir JSON olarak eklenir:
|
| 174 |
+
|
| 175 |
+
```
|
| 176 |
+
<|im_start|><|system|>
|
| 177 |
+
{sistem mesajı}
|
| 178 |
+
|
| 179 |
+
<tools>
|
| 180 |
+
{"name":"hava_durumu","description":"Bir şehrin hava tahminini getirir.","parameters":{"sehir":{"type":"string","description":"Şehir adı"},"gun":{"type":"integer","description":"Kaç gün sonrası (0 = bugün, 1 = yarın)"}},"required":["sehir"]}
|
| 181 |
+
</tools><|im_end|>
|
| 182 |
+
```
|
| 183 |
+
|
| 184 |
+
`parameters` alanı yalnızca özellikleri içerir; zorunlu alanlar `required` listesinde verilir, liste boşsa yazılmaz. Model bir araç çağırmak istediğinde cevabının sonuna şunu yazar:
|
| 185 |
+
|
| 186 |
+
```
|
| 187 |
+
<tool_call>{"name":"hava_durumu","arguments":{"sehir":"İzmir","gun":1}}</tool_call><|im_end|>
|
| 188 |
+
```
|
| 189 |
+
|
| 190 |
+
Aracı çalıştırıp sonucunu ayrı bir turda verin, ardından modelden devam etmesini isteyin:
|
| 191 |
+
|
| 192 |
+
```
|
| 193 |
+
<|im_start|><|tool|>
|
| 194 |
+
{"sehir":"İzmir","durum":"parçalı bulutlu","en_yuksek":27,"en_dusuk":19}<|im_end|>
|
| 195 |
+
<|im_start|><|assistant|>
|
| 196 |
+
```
|
| 197 |
+
|
| 198 |
+
Bir turda birden fazla çağrı art arda yazılabilir; sonuçlar çağrılarla aynı sırada, her biri ayrı bir araç turunda verilir.
|
| 199 |
+
|
| 200 |
+
## Kullanım
|
| 201 |
+
|
| 202 |
+
```bash
|
| 203 |
+
pip install torch tokenizers safetensors
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
Dosyaları indirdiğiniz klasörde çalıştırın; `model.py` ve `config.py` modelin kendi mimari dosyalarıdır.
|
| 207 |
+
|
| 208 |
+
```python
|
| 209 |
+
import json
|
| 210 |
+
import re
|
| 211 |
+
|
| 212 |
+
import torch
|
| 213 |
+
from safetensors.torch import load_file
|
| 214 |
+
from tokenizers import Tokenizer
|
| 215 |
+
|
| 216 |
+
from config import ModelConfig
|
| 217 |
+
from model import MarulLLM
|
| 218 |
+
|
| 219 |
+
cfg = ModelConfig.load("model_config.json")
|
| 220 |
+
model = MarulLLM(cfg)
|
| 221 |
+
model.load_state_dict({k: v.float() for k, v in load_file("model.safetensors").items()})
|
| 222 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 223 |
+
model.eval().to(device)
|
| 224 |
+
tok = Tokenizer.from_file("tokenizer.json")
|
| 225 |
+
|
| 226 |
+
THINK, THINK_END = "<think>", "</think>"
|
| 227 |
+
CALL, CALL_END = "<tool_call>", "</tool_call>"
|
| 228 |
+
TOOL, TOOLS, TOOLS_END = "<|tool|>", "<tools>", "</tools>"
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def compact(obj):
|
| 232 |
+
return json.dumps(obj, ensure_ascii=False, separators=(",", ":"))
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
def tool_line(tool):
|
| 236 |
+
"""OpenAI biçimindeki araç tanımını modelin eğitimde gördüğü tek satırlık biçime çevirir."""
|
| 237 |
+
f = tool.get("function", tool)
|
| 238 |
+
p = f.get("parameters") or {}
|
| 239 |
+
d = {"name": f["name"], "description": f.get("description", ""), "parameters": p.get("properties", {})}
|
| 240 |
+
if p.get("required"):
|
| 241 |
+
d["required"] = p["required"]
|
| 242 |
+
return compact(d)
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def build_prompt(messages, tools=None):
|
| 246 |
+
system = None
|
| 247 |
+
if messages and messages[0]["role"] == "system":
|
| 248 |
+
system, messages = messages[0]["content"].strip(), messages[1:]
|
| 249 |
+
parts = [system] if system else []
|
| 250 |
+
if tools:
|
| 251 |
+
parts.append(TOOLS + "\n" + "\n".join(tool_line(t) for t in tools) + "\n" + TOOLS_END)
|
| 252 |
+
s = "<|bos|>"
|
| 253 |
+
if parts:
|
| 254 |
+
s += "<|im_start|><|system|>\n" + "\n\n".join(parts) + "<|im_end|>\n"
|
| 255 |
+
for m in messages:
|
| 256 |
+
if m["role"] == "assistant":
|
| 257 |
+
calls = "".join(CALL + compact({"name": c["name"], "arguments": c["arguments"]}) + CALL_END
|
| 258 |
+
for c in m.get("tool_calls", []))
|
| 259 |
+
s += ("<|im_start|><|assistant|>\n" + THINK + m.get("reasoning", "").strip() + THINK_END + "\n"
|
| 260 |
+
+ m.get("content", "").strip() + calls + "<|im_end|>\n")
|
| 261 |
+
elif m["role"] == "tool":
|
| 262 |
+
s += "<|im_start|>" + TOOL + "\n" + m["content"].strip() + "<|im_end|>\n"
|
| 263 |
+
else:
|
| 264 |
+
s += "<|im_start|><|user|>\n" + m["content"].strip() + "<|im_end|>\n"
|
| 265 |
+
return s + "<|im_start|><|assistant|>\n"
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def chat(messages, tools=None, max_new_tokens=600):
|
| 269 |
+
ids = tok.encode(build_prompt(messages, tools), add_special_tokens=False).ids
|
| 270 |
+
out = model.generate(torch.tensor([ids], device=device), max_new_tokens=max_new_tokens,
|
| 271 |
+
temperature=0.4, top_k=50, top_p=0.9, min_p=0.05,
|
| 272 |
+
repetition_penalty=1.12, no_repeat_ngram_size=0, eos_token_id=5)
|
| 273 |
+
text = tok.decode(out[0, len(ids):].tolist(), skip_special_tokens=False).split("<|im_end|>")[0]
|
| 274 |
+
reasoning, _, rest = text.partition(THINK_END)
|
| 275 |
+
calls = [json.loads(c) for c in re.findall(re.escape(CALL) + "(.*?)" + re.escape(CALL_END), rest, re.S)]
|
| 276 |
+
content = re.sub(re.escape(CALL) + ".*?" + re.escape(CALL_END), "", rest, flags=re.S).strip()
|
| 277 |
+
return {"role": "assistant", "reasoning": reasoning.replace(THINK, "").strip(), "content": content,
|
| 278 |
+
"tool_calls": calls}
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
torch.manual_seed(0)
|
| 282 |
+
messages = [
|
| 283 |
+
{"role": "system", "content": "Adın Yedikule. Marul AI tarafından Türkçe için geliştirilmiş bir yapay zeka "
|
| 284 |
+
"asistanısın. Kullanıcılara yardım ederken açık, doğru ve nazik ol."},
|
| 285 |
+
{"role": "user", "content": "Fotosentezi kısaca anlatır mısın?"},
|
| 286 |
+
]
|
| 287 |
+
reply = chat(messages)
|
| 288 |
+
print(reply["reasoning"]) # düşünme
|
| 289 |
+
print(reply["content"]) # cevap
|
| 290 |
+
|
| 291 |
+
# Araç çağırma
|
| 292 |
+
tools = [{"type": "function", "function": {
|
| 293 |
+
"name": "hava_durumu",
|
| 294 |
+
"description": "Bir şehrin hava tahminini getirir.",
|
| 295 |
+
"parameters": {"type": "object",
|
| 296 |
+
"properties": {"sehir": {"type": "string", "description": "Şehir adı"},
|
| 297 |
+
"gun": {"type": "integer", "description": "Kaç gün sonrası (0 = bugün, 1 = yarın)"}},
|
| 298 |
+
"required": ["sehir"]}}}]
|
| 299 |
+
messages = [{"role": "user", "content": "Yarın İzmir'de hava nasıl olacak?"}]
|
| 300 |
+
reply = chat(messages, tools)
|
| 301 |
+
print(reply["tool_calls"])
|
| 302 |
+
|
| 303 |
+
result = {"sehir": "İzmir", "durum": "parçalı bulutlu", "en_yuksek": 27, "en_dusuk": 19}
|
| 304 |
+
messages += [reply, {"role": "tool", "content": compact(result)}]
|
| 305 |
+
print(chat(messages, tools)["content"])
|
| 306 |
+
```
|
| 307 |
+
|
| 308 |
+
`build_prompt` eğitimdeki sohbet biçimini birebir üretir; `chat` cevabı düşünme, metin ve araç çağrıları olarak ayırır. Araç sonucunu `{"role": "tool", ...}` mesajıyla geçmişe ekleyip `chat`'i yeniden çağırdığınızda model sonucu kullanarak cevabını yazar. `model.generate` önbelleksiz çalışır; daha hızlı ve hafif kullanım için [GGUF sürümünü](https://huggingface.co/MarulAI/Yedikule-1.4-GGUF) llama.cpp veya Ollama ile çalıştırabilirsiniz.
|
| 309 |
+
|
| 310 |
+
## Önerilen üretim ayarları
|
| 311 |
+
|
| 312 |
+
Önerilen değerler `generation_config.json` dosyasındadır:
|
| 313 |
+
|
| 314 |
+
| Ayar | Değer |
|
| 315 |
+
|---|---|
|
| 316 |
+
| temperature | 0,4 |
|
| 317 |
+
| top_k | 50 |
|
| 318 |
+
| top_p | 0,9 |
|
| 319 |
+
| min_p | 0,05 |
|
| 320 |
+
| repetition_penalty | 1,12 |
|
| 321 |
+
| no_repeat_ngram_size | 0 |
|
| 322 |
+
| max_new_tokens | 1024 |
|
| 323 |
+
|
| 324 |
+
Bu değerler; şablon metinler (e-posta, dilekçe, liste gibi), genel sorular ve araç çağırma görevlerinden oluşan bir ölçümle, altı farklı ayar karşılaştırılarak seçildi. Önceki sürümün ayarlarına (temperature 0,3, repetition_penalty 1,08) göre tekrar döngüsüne girme oranı yüzde 6,2'den yüzde 2,5'e indi; araç çağırma doğruluğu korundu.
|
| 325 |
+
|
| 326 |
+
Daha olgusal ve tutarlı cevaplar için sıcaklığı 0,3'e indirebilir, daha yaratıcı metinler için 0,6'ya kadar çıkarabilirsiniz. Tekrar cezasını 1,15'in üzerine çıkarmayın: Türkçe ekleri bozmaya başlar ve gereksiz araç çağrılarını artırır. `no_repeat_ngram_size` kapalı kalmalıdır; açık olduğunda model araç adlarını ve JSON yapılarını istemden kopyalayamaz.
|
| 327 |
+
|
| 328 |
+
## Sistem mesajı
|
| 329 |
+
|
| 330 |
+
Model 55 binden fazla farklı sistem mesajıyla eğitildi; kendi sistem mesajınızı rahatlıkla yazabilirsiniz. Genel bir asistan için şu metin iyi bir başlangıçtır:
|
| 331 |
+
|
| 332 |
+
```
|
| 333 |
+
Adın Yedikule. Marul AI tarafından Türkçe için geliştirilmiş bir yapay zeka asistanısın. Kullanıcılara yardım ederken açık, doğru ve nazik ol.
|
| 334 |
+
```
|
| 335 |
+
|
| 336 |
+
Sistem mesajı vermeden de kullanılabilir; eğitim sohbetlerinin yaklaşık beşte biri sistem mesajı içermiyordu.
|
| 337 |
+
|
| 338 |
+
## Mimari
|
| 339 |
+
|
| 340 |
+
- 24 katman, 768 gizli boyut, 2304 ara boyut
|
| 341 |
+
- Grouped Query Attention: 12 sorgu başlığı, 4 anahtar/değer başlığı, başlık boyutu 64
|
| 342 |
+
- Pre-norm RMSNorm, RoPE (theta 10000), SwiGLU
|
| 343 |
+
- Başlık boyutunda QK-Norm, RoPE'den önce uygulanır
|
| 344 |
+
- Bağlı giriş ve çıkış gömmeleri, hiçbir katmanda bias yok
|
| 345 |
+
- Tokenizer: Türkçe için eğitilmiş 48 bin tokenlik Byte-Level BPE
|
| 346 |
+
|
| 347 |
+
Mimari `model.py` içinde tanımlıdır; torch dışında bağımlılığı yoktur.
|
| 348 |
+
|
| 349 |
+
## Sınırlar
|
| 350 |
+
|
| 351 |
+
Yedikule 1.4 202 milyon parametrelik küç��k bir modeldir. Günlük sohbet, açıklama, özetleme, metin düzeltme, yazım ve araçlarla çalışma gibi işlerde iyi sonuç verir; geniş dünya bilgisi isteyen sorularda ve çok adımlı aritmetikte hata yapabilir. Hesap gerektiren işlerde ona bir hesap makinesi aracı vermek doğruluğu belirgin biçimde artırır. Çok kurallı ve uzun sistem mesajlarındaki her ayrıntıyı (örneğin bir fiyat listesinin tamamını) her zaman doğru kullanamayabilir; kritik bilgileri kısa ve açık yazın. Bilgisi 2026 yılına kadardır ve internete erişimi yoktur.
|
| 352 |
+
|
| 353 |
+
Model yanlış veya eksik bilgi üretebilir. Ürettiği bilgiyi doğrulamadan kullanmayın; tıbbi, hukuki, finansal veya güvenlik açısından kritik kararlarda tek başına kullanmayın.
|
| 354 |
+
|
| 355 |
+
## Dosyalar
|
| 356 |
+
|
| 357 |
+
| Dosya | İçerik |
|
| 358 |
+
|---|---|
|
| 359 |
+
| `model.safetensors` | bf16 ağırlıklar, 404 MB |
|
| 360 |
+
| `model.py` | model mimarisi |
|
| 361 |
+
| `config.py` | `ModelConfig` veri sınıfı |
|
| 362 |
+
| `model_config.json` | mimari parametreleri |
|
| 363 |
+
| `tokenizer.json` | 48 bin tokenlik Byte-Level BPE |
|
| 364 |
+
| `tokenizer_config.json` | tokenizer meta bilgisi |
|
| 365 |
+
| `tokenizer_info.json` | özel tokenler ve tokenizasyon örnekleri |
|
| 366 |
+
| `chat_format.json` | sohbet şablonu ve özel tokenlerin anlamları |
|
| 367 |
+
| `generation_config.json` | önerilen üretim ayarları |
|
| 368 |
+
| `LICENSE.md` | lisans tam metni |
|
| 369 |
+
|
| 370 |
+
## Lisans
|
| 371 |
+
|
| 372 |
+
Yedikule Model Lisansı 2.0. Telif hakkı sahibi Ahmet Karakuş (Marul AI). Bağlayıcı metin `LICENSE.md` dosyasındadır.
|
| 373 |
+
|
| 374 |
+
İzin almadan yapabilecekleriniz: modeli çalıştırmak, eğitim ve araştırmada kullanmak, üzerine ince ayar yapmak, damıtmak, birleştirmek, nicelemek, başka biçimlere çevirmek ve sonucu yayımlamak. Hepsi ticari olmayan kullanımla sınırlıdır.
|
| 375 |
+
|
| 376 |
+
Uymanız gerekenler: Yedikule'ye atıf yapmak, türev modelinizin adında **Yedikule** geçirmek, lisansı model ile birlikte dağıtmak ve telif bilgisini korumak.
|
| 377 |
+
|
| 378 |
+
**Türevler de bu lisansa tabidir.** Modelden türettiğiniz her çalışma aynı koşullara bağlı kalır; kendi lisansınızla yeniden lisanslayamazsınız. Türev modeli siz eğitmiş olsanız bile ticari kullanımı izne bağlıdır.
|
| 379 |
+
|
| 380 |
+
Ticari kullanım yazılı izne bağlıdır. Ticari lisans ve izin talepleri: **marulai.resmi@gmail.com** (Ahmet Karakuş, Marul AI)
|
| 381 |
+
|
| 382 |
+
## Atıf
|
| 383 |
+
|
| 384 |
+
```
|
| 385 |
+
Bu çalışma Yedikule modeline dayanmaktadır.
|
| 386 |
+
Yedikule, 202M parametreli Türkçe dil modeli.
|
| 387 |
+
Ahmet Karakuş, Marul AI, 2026.
|
| 388 |
+
https://huggingface.co/MarulAI/Yedikule-1.4
|
| 389 |
+
```
|
| 390 |
+
|
| 391 |
+
Eğitim verisi:
|
| 392 |
+
|
| 393 |
+
```
|
| 394 |
+
Marul-SFT-TR, Ahmet Karakuş (Marul AI), 2026.
|
| 395 |
+
https://huggingface.co/datasets/MarulAI/Marul-SFT-TR
|
| 396 |
+
```
|
chat_format.json
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"aciklama": "Yedikule 1.4 sohbet biçimi. Kimlikler ve adlar tokenizer.json ve GGUF dosyasıyla aynıdır. 9-15 kimlikli tokenler taban modelin (Yedikule-202M-Base) tokenizer'ında <|reserved_0|> ile <|reserved_6|> arasındaki adlarla durur; kimlikler aynıdır.",
|
| 3 |
+
"ozel_tokenler": {
|
| 4 |
+
"<|endoftext|>": {
|
| 5 |
+
"kimlik": 0,
|
| 6 |
+
"anlam": "metin sonu"
|
| 7 |
+
},
|
| 8 |
+
"<|pad|>": {
|
| 9 |
+
"kimlik": 1,
|
| 10 |
+
"anlam": "dolgu"
|
| 11 |
+
},
|
| 12 |
+
"<|unk|>": {
|
| 13 |
+
"kimlik": 2,
|
| 14 |
+
"anlam": "bilinmeyen"
|
| 15 |
+
},
|
| 16 |
+
"<|bos|>": {
|
| 17 |
+
"kimlik": 3,
|
| 18 |
+
"anlam": "dizinin başı, her istemin ilk tokeni"
|
| 19 |
+
},
|
| 20 |
+
"<|im_start|>": {
|
| 21 |
+
"kimlik": 4,
|
| 22 |
+
"anlam": "tur başlangıcı"
|
| 23 |
+
},
|
| 24 |
+
"<|im_end|>": {
|
| 25 |
+
"kimlik": 5,
|
| 26 |
+
"anlam": "tur sonu, üretim burada durdurulur"
|
| 27 |
+
},
|
| 28 |
+
"<|system|>": {
|
| 29 |
+
"kimlik": 6,
|
| 30 |
+
"anlam": "sistem rolü"
|
| 31 |
+
},
|
| 32 |
+
"<|user|>": {
|
| 33 |
+
"kimlik": 7,
|
| 34 |
+
"anlam": "kullanıcı rolü"
|
| 35 |
+
},
|
| 36 |
+
"<|assistant|>": {
|
| 37 |
+
"kimlik": 8,
|
| 38 |
+
"anlam": "asistan rolü"
|
| 39 |
+
},
|
| 40 |
+
"<think>": {
|
| 41 |
+
"kimlik": 9,
|
| 42 |
+
"anlam": "düşünme başlangıcı"
|
| 43 |
+
},
|
| 44 |
+
"</think>": {
|
| 45 |
+
"kimlik": 10,
|
| 46 |
+
"anlam": "düşünme sonu"
|
| 47 |
+
},
|
| 48 |
+
"<tool_call>": {
|
| 49 |
+
"kimlik": 11,
|
| 50 |
+
"anlam": "araç çağrısı başlangıcı"
|
| 51 |
+
},
|
| 52 |
+
"</tool_call>": {
|
| 53 |
+
"kimlik": 12,
|
| 54 |
+
"anlam": "araç çağrısı sonu"
|
| 55 |
+
},
|
| 56 |
+
"<|tool|>": {
|
| 57 |
+
"kimlik": 13,
|
| 58 |
+
"anlam": "araç sonucu rolü"
|
| 59 |
+
},
|
| 60 |
+
"<tools>": {
|
| 61 |
+
"kimlik": 14,
|
| 62 |
+
"anlam": "araç tanımları başlangıcı"
|
| 63 |
+
},
|
| 64 |
+
"</tools>": {
|
| 65 |
+
"kimlik": 15,
|
| 66 |
+
"anlam": "araç tanımları sonu"
|
| 67 |
+
}
|
| 68 |
+
},
|
| 69 |
+
"sablon": {
|
| 70 |
+
"sistem": "<|im_start|><|system|>\n{sistem mesajı}\n\n<tools>\n{araç tanımı, satır başına bir}\n</tools><|im_end|>\n",
|
| 71 |
+
"kullanici": "<|im_start|><|user|>\n{mesaj}<|im_end|>\n",
|
| 72 |
+
"asistan": "<|im_start|><|assistant|>\n<think>{düşünme}</think>\n{cevap}<tool_call>{araç çağrısı JSON}</tool_call><|im_end|>\n",
|
| 73 |
+
"arac_sonucu": "<|im_start|><|tool|>\n{araç sonucu}<|im_end|>\n",
|
| 74 |
+
"uretim_istemi": "<|im_start|><|assistant|>\n"
|
| 75 |
+
},
|
| 76 |
+
"kurallar": [
|
| 77 |
+
"İstem <|bos|> ile başlar.",
|
| 78 |
+
"Sistem turu isteğe bağlıdır. Araç verilmişse ve sistem mesajı yoksa, sistem turu yalnızca araç tanımlarını içerir.",
|
| 79 |
+
"Araç tanımı tek satırlık, boşluksuz JSON'dur: {\"name\":...,\"description\":...,\"parameters\":{özellikler},\"required\":[...]}. required boşsa yazılmaz.",
|
| 80 |
+
"Araç çağrısı boşluksuz JSON'dur: {\"name\":...,\"arguments\":{...}}. Bir turda birden fazla çağrı art arda yazılabilir.",
|
| 81 |
+
"Asistan her turda önce düşünür. Çok turlu sohbetlerde önceki asistan turlarının düşünmelerini de geçmişe ekleyin.",
|
| 82 |
+
"Üretimi <|im_end|> (5) tokeninde durdurun."
|
| 83 |
+
],
|
| 84 |
+
"stop_token_id": 5,
|
| 85 |
+
"prompt_suffix": "<|im_start|><|assistant|>\n",
|
| 86 |
+
"prompt_suffix_ids": [
|
| 87 |
+
4,
|
| 88 |
+
8,
|
| 89 |
+
214
|
| 90 |
+
]
|
| 91 |
+
}
|
config.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
import json
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@dataclass
|
| 9 |
+
class ModelConfig:
|
| 10 |
+
vocab_size: int = 48000
|
| 11 |
+
max_seq_len: int = 2048
|
| 12 |
+
|
| 13 |
+
hidden_size: int = 768
|
| 14 |
+
intermediate_size: int = 2304
|
| 15 |
+
num_layers: int = 24
|
| 16 |
+
num_heads: int = 12
|
| 17 |
+
num_kv_heads: int = 4
|
| 18 |
+
|
| 19 |
+
rope_theta: float = 10_000.0
|
| 20 |
+
|
| 21 |
+
rms_norm_eps: float = 1.0e-5
|
| 22 |
+
qk_norm: bool = True
|
| 23 |
+
|
| 24 |
+
initializer_range: float = 0.02
|
| 25 |
+
|
| 26 |
+
eos_token_id: int = 0
|
| 27 |
+
pad_token_id: int = 1
|
| 28 |
+
bos_token_id: int = 3
|
| 29 |
+
|
| 30 |
+
tie_word_embeddings: bool = True
|
| 31 |
+
|
| 32 |
+
z_loss_coef: float = 1.0e-4
|
| 33 |
+
attn_dropout: float = 0.0
|
| 34 |
+
resid_dropout: float = 0.0
|
| 35 |
+
|
| 36 |
+
@property
|
| 37 |
+
def head_dim(self) -> int:
|
| 38 |
+
assert self.hidden_size % self.num_heads == 0
|
| 39 |
+
return self.hidden_size // self.num_heads
|
| 40 |
+
|
| 41 |
+
@property
|
| 42 |
+
def kv_groups(self) -> int:
|
| 43 |
+
assert self.num_heads % self.num_kv_heads == 0
|
| 44 |
+
return self.num_heads // self.num_kv_heads
|
| 45 |
+
|
| 46 |
+
@classmethod
|
| 47 |
+
def load(cls, path: str | Path) -> "ModelConfig":
|
| 48 |
+
d = json.loads(Path(path).read_text(encoding="utf-8"))
|
| 49 |
+
valid = {f for f in cls.__dataclass_fields__}
|
| 50 |
+
return cls(**{k: v for k, v in d.items() if k in valid})
|
generation_config.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bos_token_id": 3,
|
| 3 |
+
"eos_token_id": 5,
|
| 4 |
+
"pad_token_id": 1,
|
| 5 |
+
"do_sample": true,
|
| 6 |
+
"temperature": 0.4,
|
| 7 |
+
"top_k": 50,
|
| 8 |
+
"top_p": 0.9,
|
| 9 |
+
"min_p": 0.05,
|
| 10 |
+
"repetition_penalty": 1.12,
|
| 11 |
+
"no_repeat_ngram_size": 0,
|
| 12 |
+
"max_new_tokens": 1024
|
| 13 |
+
}
|
model.py
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import Optional
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
from torch import Tensor
|
| 9 |
+
|
| 10 |
+
from config import ModelConfig
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class RMSNorm(nn.Module):
|
| 14 |
+
def __init__(self, dim: int, eps: float = 1.0e-5):
|
| 15 |
+
super().__init__()
|
| 16 |
+
self.weight = nn.Parameter(torch.ones(dim))
|
| 17 |
+
self.eps = eps
|
| 18 |
+
|
| 19 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 20 |
+
dtype = x.dtype
|
| 21 |
+
x32 = x.float()
|
| 22 |
+
rms = x32.pow(2).mean(dim=-1, keepdim=True).add_(self.eps).rsqrt_()
|
| 23 |
+
out = (x32 * rms).to(dtype)
|
| 24 |
+
return out * self.weight.to(dtype)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class RotaryEmbedding(nn.Module):
|
| 28 |
+
def __init__(self, head_dim: int, max_seq_len: int, theta: float = 10_000.0):
|
| 29 |
+
super().__init__()
|
| 30 |
+
self.head_dim = head_dim
|
| 31 |
+
self.max_seq_len = max_seq_len
|
| 32 |
+
self.theta = theta
|
| 33 |
+
self._cached_len: int = 0
|
| 34 |
+
self._cos_cache: Optional[Tensor] = None
|
| 35 |
+
self._sin_cache: Optional[Tensor] = None
|
| 36 |
+
|
| 37 |
+
def _build_cache(self, seq_len: int, device, dtype):
|
| 38 |
+
inv_freq = 1.0 / (
|
| 39 |
+
self.theta ** (torch.arange(0, self.head_dim, 2, dtype=torch.float32, device=device) / self.head_dim)
|
| 40 |
+
)
|
| 41 |
+
t = torch.arange(seq_len, dtype=torch.float32, device=device)
|
| 42 |
+
freqs = torch.outer(t, inv_freq)
|
| 43 |
+
emb = torch.cat([freqs, freqs], dim=-1)
|
| 44 |
+
self._cos_cache = emb.cos().to(dtype)
|
| 45 |
+
self._sin_cache = emb.sin().to(dtype)
|
| 46 |
+
self._cached_len = seq_len
|
| 47 |
+
|
| 48 |
+
def forward(self, seq_len: int, device, dtype) -> tuple[Tensor, Tensor]:
|
| 49 |
+
if (
|
| 50 |
+
self._cos_cache is None
|
| 51 |
+
or seq_len > self._cached_len
|
| 52 |
+
or self._cos_cache.device != device
|
| 53 |
+
or self._cos_cache.dtype != dtype
|
| 54 |
+
):
|
| 55 |
+
self._build_cache(max(seq_len, self.max_seq_len), device, dtype)
|
| 56 |
+
return self._cos_cache[:seq_len], self._sin_cache[:seq_len]
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def _rotate_half(x: Tensor) -> Tensor:
|
| 60 |
+
x1, x2 = x.chunk(2, dim=-1)
|
| 61 |
+
return torch.cat([-x2, x1], dim=-1)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def apply_rope(q: Tensor, k: Tensor, cos: Tensor, sin: Tensor) -> tuple[Tensor, Tensor]:
|
| 65 |
+
cos = cos.unsqueeze(0).unsqueeze(0)
|
| 66 |
+
sin = sin.unsqueeze(0).unsqueeze(0)
|
| 67 |
+
q_rot = (q * cos) + (_rotate_half(q) * sin)
|
| 68 |
+
k_rot = (k * cos) + (_rotate_half(k) * sin)
|
| 69 |
+
return q_rot, k_rot
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class Attention(nn.Module):
|
| 73 |
+
def __init__(self, cfg: ModelConfig, layer_idx: int):
|
| 74 |
+
super().__init__()
|
| 75 |
+
self.cfg = cfg
|
| 76 |
+
self.layer_idx = layer_idx
|
| 77 |
+
self.num_heads = cfg.num_heads
|
| 78 |
+
self.num_kv_heads = cfg.num_kv_heads
|
| 79 |
+
self.head_dim = cfg.head_dim
|
| 80 |
+
self.kv_groups = cfg.kv_groups
|
| 81 |
+
self.scale = self.head_dim ** -0.5
|
| 82 |
+
|
| 83 |
+
h, hd = cfg.hidden_size, self.head_dim
|
| 84 |
+
self.q_proj = nn.Linear(h, self.num_heads * hd, bias=False)
|
| 85 |
+
self.k_proj = nn.Linear(h, self.num_kv_heads * hd, bias=False)
|
| 86 |
+
self.v_proj = nn.Linear(h, self.num_kv_heads * hd, bias=False)
|
| 87 |
+
self.o_proj = nn.Linear(self.num_heads * hd, h, bias=False)
|
| 88 |
+
|
| 89 |
+
if cfg.qk_norm:
|
| 90 |
+
self.q_norm = RMSNorm(hd, eps=cfg.rms_norm_eps)
|
| 91 |
+
self.k_norm = RMSNorm(hd, eps=cfg.rms_norm_eps)
|
| 92 |
+
else:
|
| 93 |
+
self.q_norm = nn.Identity()
|
| 94 |
+
self.k_norm = nn.Identity()
|
| 95 |
+
|
| 96 |
+
self.attn_dropout = cfg.attn_dropout
|
| 97 |
+
|
| 98 |
+
def forward(self, x: Tensor, cos: Tensor, sin: Tensor) -> Tensor:
|
| 99 |
+
B, S, _ = x.shape
|
| 100 |
+
q = self.q_proj(x).view(B, S, self.num_heads, self.head_dim)
|
| 101 |
+
k = self.k_proj(x).view(B, S, self.num_kv_heads, self.head_dim)
|
| 102 |
+
v = self.v_proj(x).view(B, S, self.num_kv_heads, self.head_dim)
|
| 103 |
+
|
| 104 |
+
q = self.q_norm(q)
|
| 105 |
+
k = self.k_norm(k)
|
| 106 |
+
|
| 107 |
+
q = q.transpose(1, 2)
|
| 108 |
+
k = k.transpose(1, 2)
|
| 109 |
+
v = v.transpose(1, 2)
|
| 110 |
+
|
| 111 |
+
q, k = apply_rope(q, k, cos, sin)
|
| 112 |
+
|
| 113 |
+
if self.kv_groups > 1:
|
| 114 |
+
k = k.repeat_interleave(self.kv_groups, dim=1)
|
| 115 |
+
v = v.repeat_interleave(self.kv_groups, dim=1)
|
| 116 |
+
|
| 117 |
+
out = F.scaled_dot_product_attention(
|
| 118 |
+
q, k, v,
|
| 119 |
+
attn_mask=None,
|
| 120 |
+
dropout_p=self.attn_dropout if self.training else 0.0,
|
| 121 |
+
is_causal=True,
|
| 122 |
+
)
|
| 123 |
+
out = out.transpose(1, 2).contiguous().view(B, S, self.num_heads * self.head_dim)
|
| 124 |
+
return self.o_proj(out)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
class SwiGLU(nn.Module):
|
| 128 |
+
def __init__(self, cfg: ModelConfig):
|
| 129 |
+
super().__init__()
|
| 130 |
+
h, i = cfg.hidden_size, cfg.intermediate_size
|
| 131 |
+
self.gate_proj = nn.Linear(h, i, bias=False)
|
| 132 |
+
self.up_proj = nn.Linear(h, i, bias=False)
|
| 133 |
+
self.down_proj = nn.Linear(i, h, bias=False)
|
| 134 |
+
|
| 135 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 136 |
+
return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
class Block(nn.Module):
|
| 140 |
+
def __init__(self, cfg: ModelConfig, layer_idx: int):
|
| 141 |
+
super().__init__()
|
| 142 |
+
self.input_norm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps)
|
| 143 |
+
self.attn = Attention(cfg, layer_idx)
|
| 144 |
+
self.post_attn_norm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps)
|
| 145 |
+
self.mlp = SwiGLU(cfg)
|
| 146 |
+
self.resid_drop = nn.Dropout(cfg.resid_dropout) if cfg.resid_dropout > 0 else nn.Identity()
|
| 147 |
+
|
| 148 |
+
def forward(self, x: Tensor, cos: Tensor, sin: Tensor) -> Tensor:
|
| 149 |
+
x = x + self.resid_drop(self.attn(self.input_norm(x), cos, sin))
|
| 150 |
+
x = x + self.resid_drop(self.mlp(self.post_attn_norm(x)))
|
| 151 |
+
return x
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
class MarulLLM(nn.Module):
|
| 155 |
+
def __init__(self, cfg: ModelConfig):
|
| 156 |
+
super().__init__()
|
| 157 |
+
self.cfg = cfg
|
| 158 |
+
|
| 159 |
+
self.embed_tokens = nn.Embedding(cfg.vocab_size, cfg.hidden_size)
|
| 160 |
+
self.rotary = RotaryEmbedding(cfg.head_dim, cfg.max_seq_len, cfg.rope_theta)
|
| 161 |
+
self.layers = nn.ModuleList(Block(cfg, i) for i in range(cfg.num_layers))
|
| 162 |
+
self.final_norm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps)
|
| 163 |
+
|
| 164 |
+
if cfg.tie_word_embeddings:
|
| 165 |
+
self.lm_head = None
|
| 166 |
+
else:
|
| 167 |
+
self.lm_head = nn.Linear(cfg.hidden_size, cfg.vocab_size, bias=False)
|
| 168 |
+
|
| 169 |
+
self.apply(self._init_weights)
|
| 170 |
+
self._scale_residual_inits()
|
| 171 |
+
|
| 172 |
+
self.num_params = sum(p.numel() for p in self.parameters())
|
| 173 |
+
self.num_params_trainable = sum(p.numel() for p in self.parameters() if p.requires_grad)
|
| 174 |
+
embed_params = cfg.vocab_size * cfg.hidden_size
|
| 175 |
+
self.num_params_non_embed = self.num_params - embed_params
|
| 176 |
+
|
| 177 |
+
def _init_weights(self, module: nn.Module) -> None:
|
| 178 |
+
std = self.cfg.initializer_range
|
| 179 |
+
if isinstance(module, nn.Linear):
|
| 180 |
+
nn.init.normal_(module.weight, mean=0.0, std=std)
|
| 181 |
+
if module.bias is not None:
|
| 182 |
+
nn.init.zeros_(module.bias)
|
| 183 |
+
elif isinstance(module, nn.Embedding):
|
| 184 |
+
nn.init.normal_(module.weight, mean=0.0, std=std)
|
| 185 |
+
|
| 186 |
+
def _scale_residual_inits(self) -> None:
|
| 187 |
+
scale = (2 * self.cfg.num_layers) ** -0.5
|
| 188 |
+
with torch.no_grad():
|
| 189 |
+
for block in self.layers:
|
| 190 |
+
block.attn.o_proj.weight.mul_(scale)
|
| 191 |
+
block.mlp.down_proj.weight.mul_(scale)
|
| 192 |
+
|
| 193 |
+
def forward(
|
| 194 |
+
self,
|
| 195 |
+
input_ids: Tensor,
|
| 196 |
+
targets: Optional[Tensor] = None,
|
| 197 |
+
return_logits: bool = True,
|
| 198 |
+
) -> tuple[Optional[Tensor], Optional[Tensor]]:
|
| 199 |
+
B, S = input_ids.shape
|
| 200 |
+
assert S <= self.cfg.max_seq_len, (
|
| 201 |
+
f"dizi uzunluğu {S}, modelin bağlam sınırı {self.cfg.max_seq_len}")
|
| 202 |
+
|
| 203 |
+
x = self.embed_tokens(input_ids)
|
| 204 |
+
cos, sin = self.rotary(S, x.device, x.dtype)
|
| 205 |
+
|
| 206 |
+
for block in self.layers:
|
| 207 |
+
x = block(x, cos, sin)
|
| 208 |
+
x = self.final_norm(x)
|
| 209 |
+
|
| 210 |
+
if self.cfg.tie_word_embeddings:
|
| 211 |
+
logits = F.linear(x, self.embed_tokens.weight)
|
| 212 |
+
else:
|
| 213 |
+
logits = self.lm_head(x)
|
| 214 |
+
|
| 215 |
+
loss: Optional[Tensor] = None
|
| 216 |
+
if targets is not None:
|
| 217 |
+
flat_logits = logits.view(-1, logits.size(-1))
|
| 218 |
+
flat_targets = targets.view(-1)
|
| 219 |
+
ce = F.cross_entropy(
|
| 220 |
+
flat_logits, flat_targets, ignore_index=-100, reduction="mean"
|
| 221 |
+
)
|
| 222 |
+
loss = ce
|
| 223 |
+
if self.cfg.z_loss_coef > 0:
|
| 224 |
+
mask = flat_targets != -100
|
| 225 |
+
lse = torch.logsumexp(flat_logits, dim=-1)
|
| 226 |
+
if mask.any():
|
| 227 |
+
z = (lse[mask].float().pow(2)).mean()
|
| 228 |
+
loss = loss + self.cfg.z_loss_coef * z
|
| 229 |
+
|
| 230 |
+
if not return_logits and targets is not None:
|
| 231 |
+
logits = None
|
| 232 |
+
return logits, loss
|
| 233 |
+
|
| 234 |
+
@torch.no_grad()
|
| 235 |
+
def generate(
|
| 236 |
+
self,
|
| 237 |
+
input_ids: Tensor,
|
| 238 |
+
max_new_tokens: int = 128,
|
| 239 |
+
temperature: float = 0.6,
|
| 240 |
+
top_k: int = 40,
|
| 241 |
+
top_p: float = 0.88,
|
| 242 |
+
repetition_penalty: float = 1.20,
|
| 243 |
+
no_repeat_ngram_size: int = 4,
|
| 244 |
+
min_p: float = 0.05,
|
| 245 |
+
eos_token_id: Optional[int] = None,
|
| 246 |
+
) -> Tensor:
|
| 247 |
+
self.eval()
|
| 248 |
+
eos = eos_token_id if eos_token_id is not None else self.cfg.eos_token_id
|
| 249 |
+
out = input_ids.clone()
|
| 250 |
+
device = out.device
|
| 251 |
+
|
| 252 |
+
for _ in range(max_new_tokens):
|
| 253 |
+
ctx = out[:, -self.cfg.max_seq_len:]
|
| 254 |
+
logits, _ = self.forward(ctx)
|
| 255 |
+
logits = logits[:, -1, :].float()
|
| 256 |
+
|
| 257 |
+
if repetition_penalty is not None and repetition_penalty != 1.0:
|
| 258 |
+
for b in range(out.size(0)):
|
| 259 |
+
seen = torch.unique(out[b])
|
| 260 |
+
vals = logits[b, seen]
|
| 261 |
+
logits[b, seen] = torch.where(
|
| 262 |
+
vals > 0, vals / repetition_penalty, vals * repetition_penalty
|
| 263 |
+
)
|
| 264 |
+
|
| 265 |
+
if no_repeat_ngram_size and no_repeat_ngram_size > 0:
|
| 266 |
+
n = no_repeat_ngram_size
|
| 267 |
+
if out.size(1) >= n - 1:
|
| 268 |
+
for b in range(out.size(0)):
|
| 269 |
+
seq = out[b].tolist()
|
| 270 |
+
ngrams: dict = {}
|
| 271 |
+
for i in range(len(seq) - n + 1):
|
| 272 |
+
prefix = tuple(seq[i : i + n - 1])
|
| 273 |
+
ngrams.setdefault(prefix, set()).add(seq[i + n - 1])
|
| 274 |
+
curr = tuple(seq[-(n - 1):])
|
| 275 |
+
if curr in ngrams:
|
| 276 |
+
banned = torch.tensor(list(ngrams[curr]), device=device, dtype=torch.long)
|
| 277 |
+
logits[b, banned] = float("-inf")
|
| 278 |
+
|
| 279 |
+
if temperature is not None and temperature != 1.0:
|
| 280 |
+
logits = logits / max(temperature, 1.0e-6)
|
| 281 |
+
|
| 282 |
+
if top_k is not None and top_k > 0:
|
| 283 |
+
v, _ = torch.topk(logits, k=min(top_k, logits.size(-1)))
|
| 284 |
+
logits[logits < v[:, -1:]] = float("-inf")
|
| 285 |
+
|
| 286 |
+
if min_p is not None and min_p > 0.0:
|
| 287 |
+
probs_tmp = F.softmax(logits, dim=-1)
|
| 288 |
+
max_probs, _ = probs_tmp.max(dim=-1, keepdim=True)
|
| 289 |
+
logits = logits.masked_fill(probs_tmp < (max_probs * min_p), float("-inf"))
|
| 290 |
+
|
| 291 |
+
if top_p is not None and 0.0 < top_p < 1.0:
|
| 292 |
+
sorted_logits, sorted_idx = torch.sort(logits, descending=True, dim=-1)
|
| 293 |
+
probs = F.softmax(sorted_logits, dim=-1)
|
| 294 |
+
cumprobs = probs.cumsum(dim=-1)
|
| 295 |
+
mask = cumprobs > top_p
|
| 296 |
+
mask[..., 1:] = mask[..., :-1].clone()
|
| 297 |
+
mask[..., 0] = False
|
| 298 |
+
sorted_logits = sorted_logits.masked_fill(mask, float("-inf"))
|
| 299 |
+
logits = torch.full_like(logits, float("-inf")).scatter(-1, sorted_idx, sorted_logits)
|
| 300 |
+
|
| 301 |
+
probs = F.softmax(logits, dim=-1)
|
| 302 |
+
next_token = torch.multinomial(probs, num_samples=1)
|
| 303 |
+
out = torch.cat([out, next_token], dim=1)
|
| 304 |
+
|
| 305 |
+
if eos is not None and (next_token == eos).all():
|
| 306 |
+
break
|
| 307 |
+
return out
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6adb029e2780130dc227eb49a443f8cddde5c593636c922e8bb2c7aa880605ae
|
| 3 |
+
size 404137888
|
model_config.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"vocab_size": 48000,
|
| 3 |
+
"max_seq_len": 2048,
|
| 4 |
+
"hidden_size": 768,
|
| 5 |
+
"intermediate_size": 2304,
|
| 6 |
+
"num_layers": 24,
|
| 7 |
+
"num_heads": 12,
|
| 8 |
+
"num_kv_heads": 4,
|
| 9 |
+
"rope_theta": 10000.0,
|
| 10 |
+
"rms_norm_eps": 1e-05,
|
| 11 |
+
"qk_norm": true,
|
| 12 |
+
"tie_word_embeddings": true,
|
| 13 |
+
"bos_token_id": 3,
|
| 14 |
+
"eos_token_id": 5,
|
| 15 |
+
"pad_token_id": 1
|
| 16 |
+
}
|
tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"backend": "tokenizers",
|
| 3 |
+
"bos_token": "<|bos|>",
|
| 4 |
+
"eos_token": "<|endoftext|>",
|
| 5 |
+
"extra_special_tokens": [
|
| 6 |
+
"<|im_start|>",
|
| 7 |
+
"<|im_end|>",
|
| 8 |
+
"<|system|>",
|
| 9 |
+
"<|user|>",
|
| 10 |
+
"<|assistant|>"
|
| 11 |
+
],
|
| 12 |
+
"model_max_length": 2048,
|
| 13 |
+
"pad_token": "<|pad|>",
|
| 14 |
+
"tokenizer_class": "TokenizersBackend",
|
| 15 |
+
"unk_token": "<|unk|>"
|
| 16 |
+
}
|
tokenizer_info.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"tur": "Byte-Level BPE",
|
| 3 |
+
"sozluk_boyutu": 48000,
|
| 4 |
+
"dil": "tr",
|
| 5 |
+
"kucuk_harfe_cevirir_mi": false,
|
| 6 |
+
"normalizasyon": "NFC",
|
| 7 |
+
"on_bolumleyici": "ByteLevel(add_prefix_space=False, use_regex=True)",
|
| 8 |
+
"cozucu": "ByteLevel",
|
| 9 |
+
"son_islemci": "ByteLevel(trim_offsets=False)",
|
| 10 |
+
"ozel_tokenler": {
|
| 11 |
+
"<|endoftext|>": 0,
|
| 12 |
+
"<|pad|>": 1,
|
| 13 |
+
"<|unk|>": 2,
|
| 14 |
+
"<|bos|>": 3,
|
| 15 |
+
"<|im_start|>": 4,
|
| 16 |
+
"<|im_end|>": 5,
|
| 17 |
+
"<|system|>": 6,
|
| 18 |
+
"<|user|>": 7,
|
| 19 |
+
"<|assistant|>": 8,
|
| 20 |
+
"<think>": 9,
|
| 21 |
+
"</think>": 10,
|
| 22 |
+
"<tool_call>": 11,
|
| 23 |
+
"</tool_call>": 12,
|
| 24 |
+
"<|tool|>": 13,
|
| 25 |
+
"<tools>": 14,
|
| 26 |
+
"</tools>": 15
|
| 27 |
+
},
|
| 28 |
+
"ornekler": [
|
| 29 |
+
{
|
| 30 |
+
"metin": "Türkiye'nin başkenti Ankara'dır.",
|
| 31 |
+
"token_sayisi": 8,
|
| 32 |
+
"parcalar": ["Türkiye", "'", "nin", " başkenti", " Ankara", "'d", "ır", "."]
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"metin": "İstanbul'da yaşıyorum, çalışıyorum, eğleniyorum.",
|
| 36 |
+
"token_sayisi": 10,
|
| 37 |
+
"parcalar": ["İstanbul", "'d", "a", " yaşıyorum", ",", " çalışıyorum", ",", " eğlen", "iyorum", "."]
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"metin": "Evlerimizdeydiniz galiba.",
|
| 41 |
+
"token_sayisi": 6,
|
| 42 |
+
"parcalar": ["Ev", "lerimizde", "y", "diniz", " galiba", "."]
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"metin": "15 Temmuz 2026 tarihinde saat 09.30'da toplantımız var.",
|
| 46 |
+
"token_sayisi": 15,
|
| 47 |
+
"parcalar": ["15", " Temmuz", " 20", "26", " tarihinde", " saat", " 09", ".", "30", "'d", "a", " toplant", "ımız", " var", "."]
|
| 48 |
+
},
|
| 49 |
+
{
|
| 50 |
+
"metin": "Yapay zeka modelleri Türkçenin sondan eklemeli yapısıyla başa çıkmalı.",
|
| 51 |
+
"token_sayisi": 14,
|
| 52 |
+
"parcalar": ["Yapay", " zeka", " modelleri", " Türkç", "enin", " son", "dan", " eklem", "eli", " yapısıyla", " başa", " çık", "malı", "."]
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"metin": "Muvaffakiyetsizleştiricileştiremeyebileceklerimizdenmişsinizcesine.",
|
| 56 |
+
"token_sayisi": 17,
|
| 57 |
+
"parcalar": ["M", "uv", "af", "fak", "iyet", "siz", "leştir", "ic", "ileştir", "em", "ey", "ebilecek", "lerimizden", "miş", "siniz", "cesine", "."]
|
| 58 |
+
}
|
| 59 |
+
]
|
| 60 |
+
}
|