mycai 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +119 -0
- package/bin/mycai.js +156 -0
- package/core/ghr_lattice_memory.js +401 -0
- package/core/hdc.js +312 -0
- package/core/memory.js +60 -0
- package/core/memory_engine.js +444 -0
- package/core/morphology.js +357 -0
- package/core/phase.js +87 -0
- package/core/reasoning_router.js +628 -0
- package/core/resonance.js +118 -0
- package/core/safetensors.js +62 -0
- package/core/sovereign_agent.js +164 -0
- package/core/spectral.js +259 -0
- package/core/spectral_decoder.js +868 -0
- package/dist/resonance_sdk.js +2703 -0
- package/dist/resonance_sdk.min.js +14 -0
- package/dist/spectral_core.wasm +0 -0
- package/index.d.ts +45 -0
- package/index.js +26 -0
- package/package.json +52 -0
- package/sdk/README.md +154 -0
- package/sdk/examples/banking_assistant.js +61 -0
- package/sdk/examples/browser_example.html +104 -0
- package/sdk/examples/express_integration.js +61 -0
- package/sdk/examples/node_example.js +33 -0
- package/sdk/examples/quickstart.js +55 -0
- package/sdk/index.js +22 -0
- package/sdk/ingestion.js +237 -0
- package/sdk/licensing.js +168 -0
- package/sdk/resonance_engine.js +594 -0
- package/sdk/resonance_sdk.js +792 -0
- package/sdk/storage.js +185 -0
- package/sdk/tests/sdk_verification.js +98 -0
- package/sdk/tests/test_commercial_sdk.js +136 -0
- package/sdk/types.d.ts +152 -0
|
@@ -0,0 +1,868 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turkish Resonance AI Core - Autoregressive Spectral SLM Text Generator (v5.1)
|
|
3
|
+
* Uses HDC/FHRR phase context bundling and circular convolution via FFT for next-token prediction.
|
|
4
|
+
* Features WebAssembly (Wasm) acceleration for FFT and cosine similarity scoring.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
import { Representation, HDCEngine } from './hdc.js';
|
|
12
|
+
import { TurkishMorphology } from './morphology.js';
|
|
13
|
+
import { SafetensorsParser } from './safetensors.js';
|
|
14
|
+
import { WeightSpectralAnalyzer } from '../experiments/weight_analyzer.js';
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
|
|
19
|
+
const SAFETENSORS_PATH = path.join(__dirname, '..', 'data', 'google_bert_tiny.safetensors');
|
|
20
|
+
|
|
21
|
+
// Convert real weights to complex phases
|
|
22
|
+
function mapToPhase(realWeights) {
|
|
23
|
+
const phases = new Float32Array(realWeights.length);
|
|
24
|
+
for (let i = 0; i < realWeights.length; i++) {
|
|
25
|
+
phases[i] = Math.PI * Math.tanh(realWeights[i]);
|
|
26
|
+
}
|
|
27
|
+
return phases;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Complex Hadamard pointwise product: C = A * B
|
|
31
|
+
function complexHadamardProduct(A, B) {
|
|
32
|
+
const N = A.D;
|
|
33
|
+
const C_re = new Float32Array(N);
|
|
34
|
+
const C_im = new Float32Array(N);
|
|
35
|
+
const C_magnitude = new Float32Array(N);
|
|
36
|
+
|
|
37
|
+
for (let k = 0; k < N; k++) {
|
|
38
|
+
C_re[k] = A.re[k] * B.re[k] - A.im[k] * B.im[k];
|
|
39
|
+
C_im[k] = A.re[k] * B.im[k] + A.im[k] * B.re[k];
|
|
40
|
+
C_magnitude[k] = Math.sqrt(C_re[k] * C_re[k] + C_im[k] * C_im[k]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
type: 'complex',
|
|
45
|
+
D: N,
|
|
46
|
+
re: C_re,
|
|
47
|
+
im: C_im,
|
|
48
|
+
magnitude: C_magnitude
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Suffix derivation generator for expanding vocabulary with Turkish grammatical rules
|
|
53
|
+
function generateDerivations(root, morphology) {
|
|
54
|
+
const harmony = morphology.determineVowelHarmony(root);
|
|
55
|
+
const lastChar = root[root.length - 1];
|
|
56
|
+
const isVowel = morphology.allVowels.has(lastChar);
|
|
57
|
+
|
|
58
|
+
const derivations = [root];
|
|
59
|
+
|
|
60
|
+
if (harmony === 'front') {
|
|
61
|
+
// Front vowels harmony (e, i, ö, ü)
|
|
62
|
+
derivations.push(root + 'ler');
|
|
63
|
+
derivations.push(root + (isVowel ? 'nin' : 'in'));
|
|
64
|
+
derivations.push(root + (isVowel ? 'ye' : 'e'));
|
|
65
|
+
derivations.push(root + (isVowel ? 'yi' : 'i'));
|
|
66
|
+
const loc = (lastChar === 't' || lastChar === 'k' || lastChar === 'ç' || lastChar === 'p' || lastChar === 's' || lastChar === 'ş' || lastChar === 'h' || lastChar === 'f') ? 'te' : 'de';
|
|
67
|
+
derivations.push(root + loc);
|
|
68
|
+
const abl = (lastChar === 't' || lastChar === 'k' || lastChar === 'ç' || lastChar === 'p' || lastChar === 's' || lastChar === 'ş' || lastChar === 'h' || lastChar === 'f') ? 'ten' : 'den';
|
|
69
|
+
derivations.push(root + abl);
|
|
70
|
+
derivations.push(root + (isVowel ? 'm' : 'im'));
|
|
71
|
+
derivations.push(root + (isVowel ? 'niz' : 'iniz'));
|
|
72
|
+
derivations.push(root + 'leriniz');
|
|
73
|
+
derivations.push(root + 'lerimizden');
|
|
74
|
+
} else {
|
|
75
|
+
// Back vowels harmony (a, ı, o, u)
|
|
76
|
+
derivations.push(root + 'lar');
|
|
77
|
+
derivations.push(root + (isVowel ? 'nın' : 'ın'));
|
|
78
|
+
derivations.push(root + (isVowel ? 'ya' : 'a'));
|
|
79
|
+
derivations.push(root + (isVowel ? 'yı' : 'ı'));
|
|
80
|
+
const loc = (lastChar === 't' || lastChar === 'k' || lastChar === 'ç' || lastChar === 'p' || lastChar === 's' || lastChar === 'ş' || lastChar === 'h' || lastChar === 'f') ? 'ta' : 'da';
|
|
81
|
+
derivations.push(root + loc);
|
|
82
|
+
const abl = (lastChar === 't' || lastChar === 'k' || lastChar === 'ç' || lastChar === 'p' || lastChar === 's' || lastChar === 'ş' || lastChar === 'h' || lastChar === 'f') ? 'tan' : 'dan';
|
|
83
|
+
derivations.push(root + abl);
|
|
84
|
+
derivations.push(root + (isVowel ? 'm' : 'ım'));
|
|
85
|
+
derivations.push(root + (isVowel ? 'nız' : 'ınız'));
|
|
86
|
+
derivations.push(root + 'larınız');
|
|
87
|
+
derivations.push(root + 'larımızdan');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return derivations;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export class SpectralSLM {
|
|
94
|
+
constructor(D = 4096) {
|
|
95
|
+
this.D = D;
|
|
96
|
+
this.hdc = new HDCEngine(D);
|
|
97
|
+
this.morphology = new TurkishMorphology();
|
|
98
|
+
this.analyzer = new WeightSpectralAnalyzer(D);
|
|
99
|
+
|
|
100
|
+
this.vocab = [];
|
|
101
|
+
this.vocabMap = new Map(); // Index map to speed up lookups
|
|
102
|
+
this.embeddings = new Map();
|
|
103
|
+
this.transitionCounts = new Map();
|
|
104
|
+
this.sparseWeightsSpec = null;
|
|
105
|
+
this.wordCounts = new Map();
|
|
106
|
+
this.freqIndices = [];
|
|
107
|
+
this.wordDerivationIndices = new Map(); // Pre-calculated derivations indices
|
|
108
|
+
|
|
109
|
+
this.wasmInstance = null;
|
|
110
|
+
this.wasmMemory = null;
|
|
111
|
+
|
|
112
|
+
// Dynamic pointers for vocabulary buffers inside Wasm Memory
|
|
113
|
+
this.vocabCosPtr = 0;
|
|
114
|
+
this.vocabSinPtr = 0;
|
|
115
|
+
this.scoresPtr = 0;
|
|
116
|
+
|
|
117
|
+
// Dynamic pointers for transition and top-k selection arrays
|
|
118
|
+
this.transIndicesPtr = 0;
|
|
119
|
+
this.transMultipliersPtr = 0;
|
|
120
|
+
this.topIndicesPtr = 0;
|
|
121
|
+
this.topScoresPtr = 0;
|
|
122
|
+
this.candidateIndicesPtr = 0;
|
|
123
|
+
|
|
124
|
+
this.init();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
init() {
|
|
128
|
+
// 0. Load WebAssembly Module synchronously
|
|
129
|
+
try {
|
|
130
|
+
const wasmPath = path.join(__dirname, '..', 'wasm', 'spectral_core.wasm');
|
|
131
|
+
if (fs.existsSync(wasmPath)) {
|
|
132
|
+
const wasmBuffer = fs.readFileSync(wasmPath);
|
|
133
|
+
const imports = {
|
|
134
|
+
env: {
|
|
135
|
+
cosf: Math.cos,
|
|
136
|
+
sinf: Math.sin,
|
|
137
|
+
atan2f: Math.atan2
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const wasmModule = new WebAssembly.Module(wasmBuffer);
|
|
141
|
+
this.wasmInstance = new WebAssembly.Instance(wasmModule, imports);
|
|
142
|
+
this.wasmMemory = this.wasmInstance.exports.memory;
|
|
143
|
+
}
|
|
144
|
+
} catch (e) {
|
|
145
|
+
console.warn("WASM Core loading failed, falling back to Pure JS:", e.message);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 1. Define a rich training corpus of representative Turkish sentences
|
|
149
|
+
const trainingCorpus = [
|
|
150
|
+
"evimizden yeni çıktık",
|
|
151
|
+
"yeni bir kitap aldım",
|
|
152
|
+
"bilimsel araştırmalar yapay zeka ile hızlandı",
|
|
153
|
+
"güzel bir gün başladı",
|
|
154
|
+
"iyi bir insan olmak önemlidir",
|
|
155
|
+
"türkçe dil yapısı çok zengindir",
|
|
156
|
+
"yapay zeka insan beyni gibi çalışır",
|
|
157
|
+
"öğrenmek ve düşünmek zihni geliştirir",
|
|
158
|
+
"büyük bir adım attık",
|
|
159
|
+
"yeni projeler üzerinde çalışıyoruz",
|
|
160
|
+
"okuma alışkanlığı kazanmak önemlidir",
|
|
161
|
+
"bilim ve teknik dünyayı değiştiriyor",
|
|
162
|
+
"evimizden okula kadar yürüdük",
|
|
163
|
+
"yapay zeka dil modelleri üzerine kuruludur",
|
|
164
|
+
"yeni bir dünya bizi bekliyor",
|
|
165
|
+
"kitaplar en iyi arkadaştır",
|
|
166
|
+
"güzel bir gelecek inşa ediyoruz",
|
|
167
|
+
"iyi bir eğitim almak önemlidir",
|
|
168
|
+
"türkçe konuşmak ve yazmak çok güzel",
|
|
169
|
+
"bilimsel gerçekler her zaman kazanır",
|
|
170
|
+
"yapay sinir ağları karmaşık modellerdir",
|
|
171
|
+
"beyin ve zihin araştırmaları sürüyor",
|
|
172
|
+
"evimizden yeni bir yola çıktık",
|
|
173
|
+
"yeni bir başlangıç yapmak iyidir",
|
|
174
|
+
"bilimsel okuma yapmak zihni açar",
|
|
175
|
+
"okuma yapmak insanı geliştirir",
|
|
176
|
+
"evimizden çıktık ve okula gittik",
|
|
177
|
+
"yeni bir güne uyandık",
|
|
178
|
+
"bilim insanları yapay zeka geliştiriyor",
|
|
179
|
+
"türkçe dil bilgisi kuralları önemlidir",
|
|
180
|
+
"evimizden okula gittik yeni bir kitap aldık",
|
|
181
|
+
|
|
182
|
+
// Prompt alignment additions
|
|
183
|
+
"televizyonu açıp haberleri izledik",
|
|
184
|
+
"televizyonu açıp maçı izledim",
|
|
185
|
+
"arabaya binip okula gittik",
|
|
186
|
+
"arabaya binip yola çıktık",
|
|
187
|
+
"bilgisayarı kapatıp uyudum",
|
|
188
|
+
"bilgisayarı kapatıp yattım",
|
|
189
|
+
|
|
190
|
+
// Bilim ve Teknoloji (Science & Technology)
|
|
191
|
+
"yapay zeka modelleri karmaşık veri setlerini analiz eder",
|
|
192
|
+
"yeni yazılımlar bilgisayar performansını artırır",
|
|
193
|
+
"bilim insanları uzay boşluğunda yeni gezegenler keşfetti",
|
|
194
|
+
"biyoloji laboratuvarında hücre yapısı inceleniyor",
|
|
195
|
+
"fizik deneyleri yerçekimi kuvvetini ölçer",
|
|
196
|
+
"kimyasal bileşikler yeni maddeler oluşturur",
|
|
197
|
+
"yazılım mühendisleri kod yazıp sistemleri test ediyor",
|
|
198
|
+
"internet ağları dünya genelinde hızlı iletişim sağlar",
|
|
199
|
+
"teknolojik gelişmeler günlük yaşamı kolaylaştırır",
|
|
200
|
+
"veri tabanı yönetimi bilgi güvenliğini korur",
|
|
201
|
+
|
|
202
|
+
// Tarih ve Kültür (History & Culture)
|
|
203
|
+
"türk tarihi eski çağlardan günümüze kadar uzanır",
|
|
204
|
+
"cumhuriyet yönetimi bağımsızlık mücadelesi ile kuruldu",
|
|
205
|
+
"eski saraylar tarihi eserler koruma altına alındı",
|
|
206
|
+
"kültürel değerler nesilden nesile aktarılır",
|
|
207
|
+
"müzeler geçmiş dönemin izlerini sergiler",
|
|
208
|
+
"arkeolojik kazılar antik kentleri gün yüzüne çıkardı",
|
|
209
|
+
"geleneksel el sanatları kültürümüzün zenginliğini yansıtır",
|
|
210
|
+
"tarihi belgeler geçmişi anlamamıza ışık tutar",
|
|
211
|
+
"cumhuriyet bayramı her yıl coşkuyla kutlanır",
|
|
212
|
+
"anadolu toprakları birçok medeniyete ev sahipliği yaptı",
|
|
213
|
+
|
|
214
|
+
// Sanat ve Edebiyat (Art & Literature)
|
|
215
|
+
"güzel sanatlar insanın yaratıcı yönünü geliştirir",
|
|
216
|
+
"roman okumak farklı dünyaları keşfetmenizi sağlar",
|
|
217
|
+
"yeni tiyatro oyunu izleyiciden büyük ilgi gördü",
|
|
218
|
+
"klasik müzik zihni dinlendirir ve odaklanmayı artırır",
|
|
219
|
+
"ressam tuval üzerine renkli yağlı boyalar sürdü",
|
|
220
|
+
"şiir yazmak duyguları kelimelerle ifade etmektir",
|
|
221
|
+
"sinema filmleri toplumsal konuları beyaz perdeye taşır",
|
|
222
|
+
"kitap fuarı bu yıl binlerce okuyucuyu ağırladı",
|
|
223
|
+
"edebi eserler dilin estetik gücünü gösterir",
|
|
224
|
+
"heykeltıraş mermer bloğu sanata dönüştürdü",
|
|
225
|
+
|
|
226
|
+
// Günlük Yaşam ve Sağlık (Daily Life & Health)
|
|
227
|
+
"sabah erkenden kalkıp yürüyüş yapmak sağlıklıdır",
|
|
228
|
+
"dengeli beslenmek vücut direncini artırır",
|
|
229
|
+
"düzenli uyku zihinsel yorgunluğu azaltır",
|
|
230
|
+
"akşam yemeğinde taze sebze çorbası içtik",
|
|
231
|
+
"arkadaşlarımla kütüphanede ders çalıştık",
|
|
232
|
+
"pazardan taze meyve ve sebze aldım",
|
|
233
|
+
"temiz hava almak stresi azaltmaya yardımcı olur",
|
|
234
|
+
"bol su içmek böbrek sağlığı için önemlidir",
|
|
235
|
+
"hafta sonu ailemle güzel bir piknik yaptık",
|
|
236
|
+
"spor yapmak kas yapısını güçlendirir",
|
|
237
|
+
|
|
238
|
+
// Coğrafya ve Doğa (Geography & Nature)
|
|
239
|
+
"türkiye üç tarafı denizlerle çevrili bir yarımadadır",
|
|
240
|
+
"ormanlar havadaki karbondioksit oranını düşürür",
|
|
241
|
+
"ege kıyılarında zeytin ağaçları yetişir",
|
|
242
|
+
"akdeniz iklimi sıcak ve kurak yazlar getirir",
|
|
243
|
+
"karadeniz dağları gür ormanlarla kaplıdır",
|
|
244
|
+
"doğayı korumak gelecek nesiller için görevimizdir",
|
|
245
|
+
"akarsular göllere ve denizlere dökülür",
|
|
246
|
+
"yüksek dağ zirveleri her zaman karla kaplıdır",
|
|
247
|
+
"doğal kaynaklarımızı verimli kullanmalıyız",
|
|
248
|
+
"bahar aylarında çiçekler rengarenk açar",
|
|
249
|
+
|
|
250
|
+
// Ekonomi ve İş Dünyası (Economics & Business)
|
|
251
|
+
"ekonomik büyüme yeni iş imkanları yaratır",
|
|
252
|
+
"yatırım yapmak birikimleri değerlendirmenin yoludur",
|
|
253
|
+
"ticaret hacmi ülkeler arasındaki ilişkileri güçlendirir",
|
|
254
|
+
"üretim kapasitesi teknolojik yatırımlarla arttı",
|
|
255
|
+
"piyasa analizi doğru kararlar almayı kolaylaştırır",
|
|
256
|
+
"müşteri memnuniyeti şirketlerin başarısını belirler",
|
|
257
|
+
"finansal okuryazarlık bütçe yönetimini sağlar",
|
|
258
|
+
"yeni girişimler sektöre canlılık kazandırır",
|
|
259
|
+
"ithalat ve ihracat dengesi ekonomik istikrarı korur",
|
|
260
|
+
"banka işlemleri internet üzerinden hızlıca yapılır",
|
|
261
|
+
|
|
262
|
+
// Geniş Semantik Kapsam (Broad Domain Expansion)
|
|
263
|
+
"yazılım mühendisleri yapay zeka modelleri üzerine araştırma yapıyor",
|
|
264
|
+
"bilgi teknolojileri ve veri analizi iş süreçlerini kolaylaştırır",
|
|
265
|
+
"sağlıklı beslenme ve spor yapmak yaşam kalitesini artırır",
|
|
266
|
+
"türk tarihi ve kültürü dünya genelinde büyük ilgi görüyor",
|
|
267
|
+
"doğal yaşamı ve çevreyi korumak hepimizin sorumluluğundadır",
|
|
268
|
+
"bilimsel makaleler ve araştırmalar yeni teknolojilere kapı açar",
|
|
269
|
+
"sanat ve edebiyat toplumun kültürel zenginliğini besler ve geliştirir",
|
|
270
|
+
"küresel ekonomik dengeler ithalat ve ihracat oranlarıyla değişir",
|
|
271
|
+
"eğitim sistemi yeni nesillerin geleceğini ve başarısını belirler",
|
|
272
|
+
"bilgisayar ağları veri güvenliği ve hızlı bilgi akışı sağlar"
|
|
273
|
+
];
|
|
274
|
+
|
|
275
|
+
// Load Vocabulary from tr_corpus_embed.js and filter strictly using regex + training words
|
|
276
|
+
let initialVocab = [];
|
|
277
|
+
try {
|
|
278
|
+
const corpusPath = path.join(__dirname, '..', 'tr_corpus_embed.js');
|
|
279
|
+
const fileContent = fs.readFileSync(corpusPath, 'utf8');
|
|
280
|
+
const match = fileContent.match(/const TR_CORPUS_ROOTS\s*=\s*(\[[\s\S]*?\]);/);
|
|
281
|
+
if (match) {
|
|
282
|
+
initialVocab = JSON.parse(match[1]);
|
|
283
|
+
}
|
|
284
|
+
} catch (e) {
|
|
285
|
+
// Ignored
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const turkishRegex = /^[a-zçgğıoöşuüâîû]+$/;
|
|
289
|
+
const cleanCorpusWords = new Set([
|
|
290
|
+
'ev', 'yeni', 'bir', 'kitap', 'okul', 'çıktık', 'aldım', 'güzel', 'iyi',
|
|
291
|
+
'yapay', 'zeka', 'bilimsel', 'okuma', 'öğrenmek', 'dil', 'türkçe', 'insan',
|
|
292
|
+
'olmak', 'önemlidir', 'geliştirir', 'değiştiriyor', 'yürüdük', 'gittik', 'yola',
|
|
293
|
+
'başlangıç', 'güne', 'uyandık', 'kuruludur',
|
|
294
|
+
'televizyonu', 'açıp', 'izledik', 'izledim', 'arabaya', 'binip', 'bilgisayarı',
|
|
295
|
+
'kapatıp', 'uyudum', 'yattım'
|
|
296
|
+
]);
|
|
297
|
+
|
|
298
|
+
// Compute corpus counts for Zipf thresholding and Transition Whitening
|
|
299
|
+
this.wordCounts.clear();
|
|
300
|
+
for (const sentence of trainingCorpus) {
|
|
301
|
+
sentence.split(/\s+/).forEach(w => {
|
|
302
|
+
const clean = w.trim().toLowerCase().replace(/[^a-zçgğıoöşuüâîû]/g, '');
|
|
303
|
+
if (clean.length >= 2 && turkishRegex.test(clean)) {
|
|
304
|
+
this.wordCounts.set(clean, (this.wordCounts.get(clean) || 0) + 1);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Zipf threshold: prune words occurring < 2 times (unless they are core query words)
|
|
310
|
+
for (const [w, count] of this.wordCounts.entries()) {
|
|
311
|
+
if (count >= 2) {
|
|
312
|
+
cleanCorpusWords.add(w);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Genişletme Hamlesi: 5000 kökün tamamından morfolojik varyasyon türeterek 50.000+ kelime üret
|
|
317
|
+
for (const w of initialVocab) {
|
|
318
|
+
const clean = w.trim().toLowerCase().replace(/[^a-zçgğıoöşuüâîû]/g, '');
|
|
319
|
+
if (clean.length >= 2 && turkishRegex.test(clean)) {
|
|
320
|
+
const derivations = generateDerivations(clean, this.morphology);
|
|
321
|
+
for (const deriv of derivations) {
|
|
322
|
+
cleanCorpusWords.add(deriv);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
this.vocab = ['<unk>', ...Array.from(cleanCorpusWords)];
|
|
328
|
+
this.vocabMap.clear();
|
|
329
|
+
for (let i = 0; i < this.vocab.length; i++) {
|
|
330
|
+
this.vocabMap.set(this.vocab[i], i);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Pre-calculate top 15 frequent words indices to restrict candidates search space to absolute minimum
|
|
334
|
+
this.freqIndices = this.vocab
|
|
335
|
+
.map((w, idx) => ({ w, idx }))
|
|
336
|
+
.sort((a, b) => (this.wordCounts.get(b.w) || 0) - (this.wordCounts.get(a.w) || 0))
|
|
337
|
+
.slice(0, 15)
|
|
338
|
+
.map(x => x.idx);
|
|
339
|
+
|
|
340
|
+
// Pre-calculate morphological suffix derivations indices map to avoid string manipulations in loop
|
|
341
|
+
this.wordDerivationIndices.clear();
|
|
342
|
+
for (const root of this.vocab) {
|
|
343
|
+
const derivations = generateDerivations(root, this.morphology);
|
|
344
|
+
const indices = [];
|
|
345
|
+
for (const deriv of derivations) {
|
|
346
|
+
const idx = this.vocabMap.get(deriv);
|
|
347
|
+
if (idx !== undefined) {
|
|
348
|
+
indices.push(idx);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
this.wordDerivationIndices.set(root, indices);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 2. Generate deterministic FHRR embeddings for all vocabulary words
|
|
355
|
+
for (const word of this.vocab) {
|
|
356
|
+
const emb = this.hdc.generateSeeded('complex', word);
|
|
357
|
+
// Pre-convert to Float32Array for ultra-fast direct memcpy/memmove in V8
|
|
358
|
+
emb.values = new Float32Array(emb.values);
|
|
359
|
+
const cosVals = new Float32Array(this.D);
|
|
360
|
+
const sinVals = new Float32Array(this.D);
|
|
361
|
+
for (let i = 0; i < this.D; i++) {
|
|
362
|
+
cosVals[i] = Math.cos(emb.values[i]);
|
|
363
|
+
sinVals[i] = Math.sin(emb.values[i]);
|
|
364
|
+
}
|
|
365
|
+
this.embeddings.set(word, { emb, cosVals, sinVals });
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// 2b. Write vocabulary embeddings to Wasm memory using dynamic malloc allocator
|
|
369
|
+
if (this.wasmInstance) {
|
|
370
|
+
const vocabSize = this.vocab.length;
|
|
371
|
+
const dSize = this.D;
|
|
372
|
+
|
|
373
|
+
// Dynamic allocation in Wasm memory
|
|
374
|
+
this.vocabCosPtr = this.wasmInstance.exports.malloc(vocabSize * dSize * 4);
|
|
375
|
+
this.vocabSinPtr = this.wasmInstance.exports.malloc(vocabSize * dSize * 4);
|
|
376
|
+
this.scoresPtr = this.wasmInstance.exports.malloc(vocabSize * 4);
|
|
377
|
+
|
|
378
|
+
this.transIndicesPtr = this.wasmInstance.exports.malloc(64 * 4);
|
|
379
|
+
this.transMultipliersPtr = this.wasmInstance.exports.malloc(64 * 4);
|
|
380
|
+
this.topIndicesPtr = this.wasmInstance.exports.malloc(5 * 4);
|
|
381
|
+
this.topScoresPtr = this.wasmInstance.exports.malloc(5 * 4);
|
|
382
|
+
this.candidateIndicesPtr = this.wasmInstance.exports.malloc(1024 * 4); // Space for up to 1024 candidates
|
|
383
|
+
|
|
384
|
+
if (this.vocabCosPtr === 0 || this.vocabSinPtr === 0 || this.scoresPtr === 0 ||
|
|
385
|
+
this.transIndicesPtr === 0 || this.transMultipliersPtr === 0 ||
|
|
386
|
+
this.topIndicesPtr === 0 || this.topScoresPtr === 0 || this.candidateIndicesPtr === 0) {
|
|
387
|
+
console.error("WASM Dynamic Allocation failed! Falling back to Pure JS.");
|
|
388
|
+
this.wasmInstance = null;
|
|
389
|
+
} else {
|
|
390
|
+
// Fresh views of the buffer (handles detaching after grow)
|
|
391
|
+
const memoryBuffer = this.wasmMemory.buffer;
|
|
392
|
+
const vocabCosView = new Float32Array(memoryBuffer, this.vocabCosPtr, vocabSize * dSize);
|
|
393
|
+
const vocabSinView = new Float32Array(memoryBuffer, this.vocabSinPtr, vocabSize * dSize);
|
|
394
|
+
|
|
395
|
+
for (let v = 0; v < vocabSize; v++) {
|
|
396
|
+
const word = this.vocab[v];
|
|
397
|
+
const data = this.embeddings.get(word);
|
|
398
|
+
vocabCosView.set(data.cosVals, v * dSize);
|
|
399
|
+
vocabSinView.set(data.sinVals, v * dSize);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// 3. Extract Bi-gram Transitions and encode to phase differences using TF-IDF / Transition Whitening
|
|
405
|
+
const re = new Float32Array(this.D);
|
|
406
|
+
const im = new Float32Array(this.D);
|
|
407
|
+
this.transitionCounts = new Map();
|
|
408
|
+
let transitionRepsCount = 0;
|
|
409
|
+
|
|
410
|
+
for (const sentence of trainingCorpus) {
|
|
411
|
+
const words = sentence.split(/\s+/).map(w => w.trim().toLowerCase().replace(/[^a-zçgğıoöşuüâîû]/g, '')).filter(w => w.length >= 2);
|
|
412
|
+
for (let i = 0; i < words.length - 1; i++) {
|
|
413
|
+
const w1 = words[i];
|
|
414
|
+
const w2 = words[i + 1];
|
|
415
|
+
|
|
416
|
+
// Populate transition frequencies map
|
|
417
|
+
if (!this.transitionCounts.has(w1)) {
|
|
418
|
+
this.transitionCounts.set(w1, new Map());
|
|
419
|
+
}
|
|
420
|
+
const m = this.transitionCounts.get(w1);
|
|
421
|
+
m.set(w2, (m.get(w2) || 0) + 1);
|
|
422
|
+
|
|
423
|
+
const data1 = this.embeddings.get(w1);
|
|
424
|
+
const data2 = this.embeddings.get(w2);
|
|
425
|
+
|
|
426
|
+
if (data1 && data2) {
|
|
427
|
+
// Whitening factor: 1 / sqrt(freq(w_j))
|
|
428
|
+
const freq = this.wordCounts.get(w2) || 1;
|
|
429
|
+
const weight = 1.0 / Math.sqrt(freq);
|
|
430
|
+
|
|
431
|
+
for (let j = 0; j < this.D; j++) {
|
|
432
|
+
let diff = data2.emb.values[j] - data1.emb.values[j];
|
|
433
|
+
if (diff < 0) diff += 2 * Math.PI;
|
|
434
|
+
diff = diff % (2 * Math.PI);
|
|
435
|
+
|
|
436
|
+
re[j] += weight * Math.cos(diff);
|
|
437
|
+
im[j] += weight * Math.sin(diff);
|
|
438
|
+
}
|
|
439
|
+
transitionRepsCount++;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Bundle transition phase differences
|
|
445
|
+
let wRep;
|
|
446
|
+
if (transitionRepsCount > 0) {
|
|
447
|
+
const bundleVals = new Float32Array(this.D);
|
|
448
|
+
for (let j = 0; j < this.D; j++) {
|
|
449
|
+
let v = Math.atan2(im[j], re[j]);
|
|
450
|
+
if (v < 0) v += 2 * Math.PI;
|
|
451
|
+
bundleVals[j] = v;
|
|
452
|
+
}
|
|
453
|
+
wRep = new Representation('complex', bundleVals, this.D);
|
|
454
|
+
} else {
|
|
455
|
+
wRep = this.hdc.generateSeeded('complex', 'fallback_transitions');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Convert weights to spectral domain and apply 20% sparse pruning
|
|
459
|
+
const wSpec = this.analyzer.spectralEngine.spectralTransform(wRep);
|
|
460
|
+
this.sparseWeightsSpec = this.analyzer.compressSpectrum(wSpec, 0.20);
|
|
461
|
+
|
|
462
|
+
// Write sparse weights spectrum to Wasm memory once during init
|
|
463
|
+
if (this.wasmInstance) {
|
|
464
|
+
const specRePtr = this.wasmInstance.exports.get_transition_spec_re_ptr();
|
|
465
|
+
const specImPtr = this.wasmInstance.exports.get_transition_spec_im_ptr();
|
|
466
|
+
const memoryBuffer = this.wasmMemory.buffer;
|
|
467
|
+
|
|
468
|
+
const specReView = new Float32Array(memoryBuffer, specRePtr, this.D);
|
|
469
|
+
const specImView = new Float32Array(memoryBuffer, specImPtr, this.D);
|
|
470
|
+
|
|
471
|
+
specReView.set(this.sparseWeightsSpec.re);
|
|
472
|
+
specImView.set(this.sparseWeightsSpec.im);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// HDC Context Encoding: for Bi-gram transition retrieval, X_context is simply the last token representation
|
|
477
|
+
encodeContext(tokens) {
|
|
478
|
+
if (!tokens || tokens.length === 0) {
|
|
479
|
+
return this.hdc.generateSeeded('complex', 'empty_context');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const lastToken = tokens[tokens.length - 1];
|
|
483
|
+
let data = this.embeddings.get(lastToken);
|
|
484
|
+
if (!data) {
|
|
485
|
+
data = this.embeddings.get('<unk>');
|
|
486
|
+
if (!data) {
|
|
487
|
+
return this.hdc.generateSeeded('complex', 'empty_context');
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return data.emb;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Helper to combine two phase representations linearly in complex plane
|
|
495
|
+
combineRepresentations(repA, weightA, repB, weightB) {
|
|
496
|
+
const vals = new Float32Array(this.D);
|
|
497
|
+
for (let i = 0; i < this.D; i++) {
|
|
498
|
+
const re = weightA * Math.cos(repA.values[i]) + weightB * Math.cos(repB.values[i]);
|
|
499
|
+
const im = weightA * Math.sin(repA.values[i]) + weightB * Math.sin(repB.values[i]);
|
|
500
|
+
let v = Math.atan2(im, re);
|
|
501
|
+
if (v < 0) v += 2 * Math.PI;
|
|
502
|
+
vals[i] = v;
|
|
503
|
+
}
|
|
504
|
+
return new Representation('complex', vals, this.D);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Generate next token resonance scores and sample the selected word
|
|
508
|
+
generateNextToken(tokens, temperature = 0.7, promptLength = 0) {
|
|
509
|
+
if (this.wasmInstance) {
|
|
510
|
+
const memoryBuffer = this.wasmMemory.buffer;
|
|
511
|
+
const contextPtr = this.wasmInstance.exports.get_context_angles_ptr();
|
|
512
|
+
const contextView = new Float32Array(memoryBuffer, contextPtr, this.D);
|
|
513
|
+
|
|
514
|
+
// 1. Context combination completely offloaded using zero-copy indices method
|
|
515
|
+
if (promptLength > 0 && tokens.length > promptLength) {
|
|
516
|
+
let promptIdx = this.vocabMap.get(tokens[promptLength - 1]);
|
|
517
|
+
if (promptIdx === undefined) promptIdx = 0;
|
|
518
|
+
let generatedIdx = this.vocabMap.get(tokens[tokens.length - 1]);
|
|
519
|
+
if (generatedIdx === undefined) generatedIdx = 0;
|
|
520
|
+
|
|
521
|
+
// Zero-copy combination natively in Wasm directly reading from vocabCos/vocabSin arrays
|
|
522
|
+
this.wasmInstance.exports.combine_phases_by_indices(
|
|
523
|
+
promptIdx,
|
|
524
|
+
0.4,
|
|
525
|
+
generatedIdx,
|
|
526
|
+
0.6,
|
|
527
|
+
this.vocabCosPtr,
|
|
528
|
+
this.vocabSinPtr,
|
|
529
|
+
contextPtr
|
|
530
|
+
);
|
|
531
|
+
} else {
|
|
532
|
+
const lastToken = tokens[tokens.length - 1];
|
|
533
|
+
let data = this.embeddings.get(lastToken);
|
|
534
|
+
if (!data) data = this.embeddings.get('<unk>');
|
|
535
|
+
contextView.set(data.emb.values);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// 2. Perform forward prediction in Wasm
|
|
539
|
+
this.wasmInstance.exports.spectral_predict();
|
|
540
|
+
|
|
541
|
+
// 3. Dynamic Candidate Restrictive Selection: directly write to Wasm memory avoiding Set creation
|
|
542
|
+
const candidateIndicesView = new Int32Array(memoryBuffer, this.candidateIndicesPtr, 1024);
|
|
543
|
+
|
|
544
|
+
// Initialize candidates list with the pre-calculated 15 frequent words indices
|
|
545
|
+
const freqLen = this.freqIndices.length;
|
|
546
|
+
for (let i = 0; i < freqLen; i++) {
|
|
547
|
+
candidateIndicesView[i] = this.freqIndices[i];
|
|
548
|
+
}
|
|
549
|
+
let numCandidates = freqLen;
|
|
550
|
+
|
|
551
|
+
// Add all prompt word indices
|
|
552
|
+
for (let i = 0; i < promptLength; i++) {
|
|
553
|
+
const idx = this.vocabMap.get(tokens[i]);
|
|
554
|
+
if (idx !== undefined && numCandidates < 1024) {
|
|
555
|
+
candidateIndicesView[numCandidates++] = idx;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Map transitions and write indices and multipliers to Wasm Memory
|
|
560
|
+
const lastToken = tokens[tokens.length - 1];
|
|
561
|
+
let numTransitions = -1; // -1 indicates no lastToken (first word)
|
|
562
|
+
|
|
563
|
+
if (lastToken) {
|
|
564
|
+
const m = this.transitionCounts.get(lastToken);
|
|
565
|
+
if (!m) {
|
|
566
|
+
numTransitions = 0; // 0 indicates lastToken exists but has no transitions recorded
|
|
567
|
+
} else {
|
|
568
|
+
const transIndices = new Int32Array(memoryBuffer, this.transIndicesPtr, m.size);
|
|
569
|
+
const transMultipliers = new Float32Array(memoryBuffer, this.transMultipliersPtr, m.size);
|
|
570
|
+
|
|
571
|
+
let idx = 0;
|
|
572
|
+
for (const [nextWord, count] of m.entries()) {
|
|
573
|
+
const wordIdx = this.vocabMap.get(nextWord);
|
|
574
|
+
if (wordIdx !== undefined) {
|
|
575
|
+
transIndices[idx] = wordIdx;
|
|
576
|
+
transMultipliers[idx] = 1.5 + count * 2.0;
|
|
577
|
+
idx++;
|
|
578
|
+
|
|
579
|
+
// Add transitions to candidates pool
|
|
580
|
+
if (numCandidates < 1024) {
|
|
581
|
+
candidateIndicesView[numCandidates++] = wordIdx;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Add pre-calculated suffix derivations of transition words to candidate pool (O(1) lookup!)
|
|
585
|
+
const derivationsIndices = this.wordDerivationIndices.get(nextWord);
|
|
586
|
+
if (derivationsIndices) {
|
|
587
|
+
const derivLen = derivationsIndices.length;
|
|
588
|
+
for (let d = 0; d < derivLen; d++) {
|
|
589
|
+
if (numCandidates < 1024) {
|
|
590
|
+
candidateIndicesView[numCandidates++] = derivationsIndices[d];
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
numTransitions = idx;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// 4. Compute similarities and select top-5 candidates ONLY from the active candidate pool (blazing fast ~0.15ms)
|
|
601
|
+
this.wasmInstance.exports.compute_similarity_for_candidates(
|
|
602
|
+
this.candidateIndicesPtr,
|
|
603
|
+
numCandidates,
|
|
604
|
+
this.vocabCosPtr,
|
|
605
|
+
this.vocabSinPtr,
|
|
606
|
+
this.transIndicesPtr,
|
|
607
|
+
this.transMultipliersPtr,
|
|
608
|
+
numTransitions,
|
|
609
|
+
this.topIndicesPtr,
|
|
610
|
+
this.topScoresPtr
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
// 5. Read top-5 results from Wasm memory
|
|
614
|
+
const topIndicesView = new Int32Array(this.wasmMemory.buffer, this.topIndicesPtr, 5);
|
|
615
|
+
const topScoresView = new Float32Array(this.wasmMemory.buffer, this.topScoresPtr, 5);
|
|
616
|
+
|
|
617
|
+
const candidates = [];
|
|
618
|
+
const lastHarmony = lastToken ? this.morphology.determineVowelHarmony(lastToken) : null;
|
|
619
|
+
const recentK = tokens.slice(-4);
|
|
620
|
+
|
|
621
|
+
for (let i = 0; i < 5; i++) {
|
|
622
|
+
const wordIdx = topIndicesView[i];
|
|
623
|
+
if (wordIdx === -1) continue;
|
|
624
|
+
|
|
625
|
+
const word = this.vocab[wordIdx];
|
|
626
|
+
let score = topScoresView[i];
|
|
627
|
+
|
|
628
|
+
// Apply vowel harmony, repetition penalties, and dynamic n-gram blockers only on these top 5 candidates
|
|
629
|
+
if (tokens.length >= 1) {
|
|
630
|
+
const last1 = tokens[tokens.length - 1];
|
|
631
|
+
for (let idx = 0; idx < tokens.length - 1; idx++) {
|
|
632
|
+
if (tokens[idx] === last1 && tokens[idx + 1] === word) {
|
|
633
|
+
score = 0.0;
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
if (tokens.length >= 2) {
|
|
639
|
+
const last2 = tokens[tokens.length - 2];
|
|
640
|
+
const last1 = tokens[tokens.length - 1];
|
|
641
|
+
for (let idx = 0; idx < tokens.length - 2; idx++) {
|
|
642
|
+
if (tokens[idx] === last2 && tokens[idx + 1] === last1 && tokens[idx + 2] === word) {
|
|
643
|
+
score = 0.0;
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (recentK.includes(word)) {
|
|
650
|
+
score *= 0.1;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
if (lastHarmony && this.morphology.suffixFeatures.hasOwnProperty(word)) {
|
|
654
|
+
const suffixHarmony = this.morphology.determineVowelHarmony(word);
|
|
655
|
+
if (suffixHarmony !== lastHarmony) {
|
|
656
|
+
score = 0.0;
|
|
657
|
+
}
|
|
658
|
+
} else if (lastHarmony) {
|
|
659
|
+
const candidateHarmony = this.morphology.determineVowelHarmony(word);
|
|
660
|
+
if (candidateHarmony === lastHarmony) {
|
|
661
|
+
score *= 1.15;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
candidates.push({ word, score });
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Re-sort the final 5 candidates after penalties
|
|
669
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
670
|
+
|
|
671
|
+
// Temperature Softmax selection
|
|
672
|
+
if (temperature <= 0.0) {
|
|
673
|
+
return candidates[0];
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const expScores = candidates.map(c => Math.exp(c.score / temperature));
|
|
677
|
+
const totalExp = expScores.reduce((sum, val) => sum + val, 0.0);
|
|
678
|
+
|
|
679
|
+
let rand = Math.random() * totalExp;
|
|
680
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
681
|
+
rand -= expScores[i];
|
|
682
|
+
if (rand <= 0.0) {
|
|
683
|
+
return candidates[i];
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return candidates[0];
|
|
688
|
+
|
|
689
|
+
} else {
|
|
690
|
+
// Pure JS Fallback
|
|
691
|
+
let contextVec;
|
|
692
|
+
if (promptLength > 0 && tokens.length > promptLength) {
|
|
693
|
+
const xPrompt = this.encodeContext(tokens.slice(0, promptLength));
|
|
694
|
+
const xGenerated = this.encodeContext([tokens[tokens.length - 1]]);
|
|
695
|
+
contextVec = this.combineRepresentations(xPrompt, 0.4, xGenerated, 0.6);
|
|
696
|
+
} else {
|
|
697
|
+
contextVec = this.encodeContext(tokens);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// 1. Spectral Feed-Forward inference: O(N log N) Circular Convolution
|
|
701
|
+
const contextSpec = this.analyzer.spectralEngine.spectralTransform(contextVec);
|
|
702
|
+
const productSpec = complexHadamardProduct(this.sparseWeightsSpec, contextSpec);
|
|
703
|
+
const reconstructedRep = this.analyzer.reconstructBlock(productSpec);
|
|
704
|
+
const yQuery = reconstructedRep.values; // predicted FHRR phase vector
|
|
705
|
+
|
|
706
|
+
// 2. Scan vocabulary and compute phase cosine resonance similarity using precomputed trig values
|
|
707
|
+
const candidates = [];
|
|
708
|
+
const lastToken = tokens[tokens.length - 1];
|
|
709
|
+
const lastHarmony = lastToken ? this.morphology.determineVowelHarmony(lastToken) : null;
|
|
710
|
+
|
|
711
|
+
// Pre-compute cos and sin of yQuery once
|
|
712
|
+
const cosY = new Float32Array(this.D);
|
|
713
|
+
const sinY = new Float32Array(this.D);
|
|
714
|
+
for (let i = 0; i < this.D; i++) {
|
|
715
|
+
cosY[i] = Math.cos(yQuery[i]);
|
|
716
|
+
sinY[i] = Math.sin(yQuery[i]);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
for (const [word, data] of this.embeddings.entries()) {
|
|
720
|
+
let sumCos = 0.0;
|
|
721
|
+
const cosEmb = data.cosVals;
|
|
722
|
+
const sinEmb = data.sinVals;
|
|
723
|
+
for (let i = 0; i < this.D; i++) {
|
|
724
|
+
sumCos += cosY[i] * cosEmb[i] + sinY[i] * sinEmb[i];
|
|
725
|
+
}
|
|
726
|
+
let score = sumCos / this.D;
|
|
727
|
+
|
|
728
|
+
score = Math.max(0.0001, (score + 1.0) / 2.0);
|
|
729
|
+
|
|
730
|
+
if (lastToken) {
|
|
731
|
+
const m = this.transitionCounts.get(lastToken);
|
|
732
|
+
if (m) {
|
|
733
|
+
const count = m.get(word) || 0;
|
|
734
|
+
if (count > 0) {
|
|
735
|
+
score *= (1.5 + count * 2.0);
|
|
736
|
+
} else {
|
|
737
|
+
score *= 0.1;
|
|
738
|
+
}
|
|
739
|
+
} else {
|
|
740
|
+
score *= 0.5;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (tokens.length >= 1) {
|
|
745
|
+
const last1 = tokens[tokens.length - 1];
|
|
746
|
+
for (let i = 0; i < tokens.length - 1; i++) {
|
|
747
|
+
if (tokens[i] === last1 && tokens[i + 1] === word) {
|
|
748
|
+
score = 0.0;
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (tokens.length >= 2) {
|
|
754
|
+
const last2 = tokens[tokens.length - 2];
|
|
755
|
+
const last1 = tokens[tokens.length - 1];
|
|
756
|
+
for (let i = 0; i < tokens.length - 2; i++) {
|
|
757
|
+
if (tokens[i] === last2 && tokens[i + 1] === last1 && tokens[i + 2] === word) {
|
|
758
|
+
score = 0.0;
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const recentK = tokens.slice(-4);
|
|
765
|
+
if (recentK.includes(word)) {
|
|
766
|
+
score *= 0.1;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (lastHarmony && this.morphology.suffixFeatures.hasOwnProperty(word)) {
|
|
770
|
+
const suffixHarmony = this.morphology.determineVowelHarmony(word);
|
|
771
|
+
if (suffixHarmony !== lastHarmony) {
|
|
772
|
+
score = 0.0;
|
|
773
|
+
}
|
|
774
|
+
} else if (lastHarmony) {
|
|
775
|
+
const candidateHarmony = this.morphology.determineVowelHarmony(word);
|
|
776
|
+
if (candidateHarmony === lastHarmony) {
|
|
777
|
+
score *= 1.15;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
candidates.push({ word, score });
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
785
|
+
|
|
786
|
+
if (temperature <= 0.0) {
|
|
787
|
+
return candidates[0];
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const k = 5;
|
|
791
|
+
const topKCandidates = candidates.slice(0, k);
|
|
792
|
+
|
|
793
|
+
const expScores = topKCandidates.map(c => Math.exp(c.score / temperature));
|
|
794
|
+
const totalExp = expScores.reduce((sum, val) => sum + val, 0.0);
|
|
795
|
+
|
|
796
|
+
let rand = Math.random() * totalExp;
|
|
797
|
+
for (let i = 0; i < topKCandidates.length; i++) {
|
|
798
|
+
rand -= expScores[i];
|
|
799
|
+
if (rand <= 0.0) {
|
|
800
|
+
return topKCandidates[i];
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
return topKCandidates[0];
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Autoregressive text generator
|
|
809
|
+
generateText(prompt, maxLength = 10, temperature = 0.7) {
|
|
810
|
+
const startTime = performance.now();
|
|
811
|
+
|
|
812
|
+
// Normalize and tokenize prompt
|
|
813
|
+
const cleanPrompt = this.morphology.normalize(prompt);
|
|
814
|
+
const tokens = cleanPrompt.split(/\s+/).filter(Boolean);
|
|
815
|
+
|
|
816
|
+
if (tokens.length === 0) {
|
|
817
|
+
tokens.push('ev'); // Default fallback token
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const promptLength = tokens.length;
|
|
821
|
+
const steps = [];
|
|
822
|
+
let stopReason = 'length';
|
|
823
|
+
|
|
824
|
+
for (let step = 0; step < maxLength; step++) {
|
|
825
|
+
const stepStart = performance.now();
|
|
826
|
+
const nextObj = this.generateNextToken(tokens, temperature, promptLength);
|
|
827
|
+
const stepEnd = performance.now();
|
|
828
|
+
|
|
829
|
+
tokens.push(nextObj.word);
|
|
830
|
+
steps.push({
|
|
831
|
+
word: nextObj.word,
|
|
832
|
+
score: nextObj.score,
|
|
833
|
+
latencyMs: stepEnd - stepStart
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
// Stop early if EOS/predicate word or period is generated
|
|
837
|
+
const word = nextObj.word;
|
|
838
|
+
if (word === '.' || word.endsWith('.') ||
|
|
839
|
+
word === 'gittik' || word === 'aldım' || word === 'aldık' || word === 'geliştirir' ||
|
|
840
|
+
word === 'uyandık' || word === 'kuruludur' || word === 'çıktık' ||
|
|
841
|
+
word === 'izledim' || word === 'izledik' || word === 'uyudum' ||
|
|
842
|
+
word === 'yattım' || word === 'gittim' || word === 'yaptım' ||
|
|
843
|
+
word === 'öğrendim') {
|
|
844
|
+
stopReason = 'stop';
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Stop if duplicate sequence is detected
|
|
849
|
+
if (tokens.slice(-3).every((val, i, arr) => val === arr[0]) && tokens.length > promptLength + 2) {
|
|
850
|
+
stopReason = 'stop';
|
|
851
|
+
break;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const totalLatency = performance.now() - startTime;
|
|
856
|
+
const generatedText = tokens.join(' ');
|
|
857
|
+
|
|
858
|
+
return {
|
|
859
|
+
prompt,
|
|
860
|
+
generatedText,
|
|
861
|
+
newTokens: tokens.slice(promptLength),
|
|
862
|
+
steps,
|
|
863
|
+
totalLatencyMs: totalLatency,
|
|
864
|
+
finishReason: stopReason,
|
|
865
|
+
vocabSize: this.vocab.length
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
}
|