js小型llm计划

准备照着重打一遍代码,不过是用tensorflow.js

持续更新。

粗略理解

首先先粗略的理解一下文本生成的原理吧,虽然非常幼稚…

注意力:对应两个token之间的关系
文本生成:根据上个token预测下一个token
k,q,v:k与q相对应,v作为输入,输出v2,v2又作为下一个输入…
所以理论上,一层注意力也是能用来生成的,只要参数够大…
希望没有错的太离谱。

一个js transformer实现

在github上发现了一个有趣的项目
transformer-tfjs
直接在浏览器里训练模型,太牛逼了,准备照着做…

归一化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
tf.util.shuffle(data)

const epsilon = tf.scalar(1e-4) // 创建一个很小的标量epsilon
const inputs = data.map((d) => d.horsepower)
const labels = data.map((d) => d.mpg)

const inputTensor = tf.tensor2d(inputs, [inputs.length, 1])
const labelTensor = tf.tensor2d(labels, [labels.length, 1])

const sqrt_pow_mean = inputTensor
.square()
.mean(1)
.sqrt()
.expandDims(1)
.add(epsilon)
const label_pow_mean = inputTensor
.square()
.mean(1)
.sqrt()
.expandDims(1)
.add(epsilon)

最简单的,RMS_Norm归一化。

但我不是很懂mean()这个函数,沿着第1维和第2维好像都能出现结果。。。
(划掉)

而且出现了未完全归一化的结果…

呃…大体上是完成了吧。(还是用简单点的ba…)

data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
 //import * as assert from 'assert'
import { getEncoding, encodingForModel } from 'js-tiktoken'
import * as fs from 'fs'
import * as nj from 'numjs'
//import * as os from 'os'
import * as path from 'path'
//import * as tf from '@tensorflow/tfjs'
//import { fileURLToPath } from 'url'

//const __filename = fileURLToPath(import.meta.url)
//const __dirname = path.dirname(__filename)
const enc = getEncoding('gpt2')
//assert.strictEqual(enc.decode(enc.encode('hello world')), 'hello world')

fs.readFile('./night.txt', 'utf8', (err: Error | null, content: string) => {
if (err) {
console.error('Error reading file:', err)
return
}
console.log(content.length)
const n = content.length
const train_data = content.slice(0, Math.floor(n * 0.8)) //切片,前80%
const val_data = content.slice(Math.floor(n * 0.8)) //后20%
let train_ids = enc.encode(train_data) //编码
let val_ids = enc.encode(val_data)
console.log('train tokens:', train_ids.length)
console.log('val tokens:', val_ids.length)

var train_ids_list = nj.array(train_ids, 'uint16')
var val_ids_list = nj.array(val_ids, 'uint16')

const trainArray = train_ids_list.tolist() // 转换为普通数组
const trainBuffer = Buffer.from(trainArray) // 转换为 Buffer 对象
const valArray = train_ids_list.tolist()
const valBuffer = Buffer.from(valArray)

fs.writeFileSync(path.join('./data', 'train.bin'), trainBuffer)

fs.writeFileSync(path.join('./data', 'val.bin'), valBuffer)
})

用numjs和js-tiktoken实现编码,模仿prepare.py保存为二进制。