main.bend
bend-ml-tensor@0.1.0.0 · 497 lines · 0 definitions
1# bend-ml-tensor: tensores com a shape no TIPO. Erro de shape é erro de tipo.
2#
3# import bend-ml-tensor@0.1.0.0/main.bend as T
4#
5# Vec<n> é um vetor de n números F32; Mat<r, c> é uma matriz r x c (lista de r
6# linhas com c números). As dimensões são parâmetros de tipo apagados: não custam
7# nada em tempo de execução, mas o checker as confere em toda operação.
8
9import Base
10import bend-ml-nat-lemmas@0.1.0.0/main.bend as NL
11
12type Vec<-n: Nat> is Data:
13 Vec{xs: List<&2, F32>}
14
15type Mat<-r: Nat, -c: Nat> is Data:
16 Mat{rows: List<&2, List<&2, F32>>}
17
18# ---------------------------------------------------------------------
19# listas de F32 (internas; as operações públicas fixam as dimensões)
20# ---------------------------------------------------------------------
21
22def zip_with.add(xs: List<&2, F32>, ys: List<&2, F32>) -> List<&2, F32>:
23 match xs ys:
24 case Con{+h, t} Con{+g, u}:
25 (h + g : F32) <> zip_with.add(t, u)
26 case _ _:
27 Nil{}
28
29def zip_with.sub(xs: List<&2, F32>, ys: List<&2, F32>) -> List<&2, F32>:
30 match xs ys:
31 case Con{+h, t} Con{+g, u}:
32 (h - g : F32) <> zip_with.sub(t, u)
33 case _ _:
34 Nil{}
35
36def zip_with.mul(xs: List<&2, F32>, ys: List<&2, F32>) -> List<&2, F32>:
37 match xs ys:
38 case Con{+h, t} Con{+g, u}:
39 (h * g : F32) <> zip_with.mul(t, u)
40 case _ _:
41 Nil{}
42
43def scale.l(+k: F32, xs: List<&2, F32>) -> List<&2, F32>:
44 match xs:
45 case Nil{}:
46 Nil{}
47 case Con{+h, t}:
48 (k * h : F32) <> scale.l(k, t)
49
50def dot.go(xs: List<&2, F32>, ys: List<&2, F32>, acc: F32) -> F32:
51 match xs ys:
52 case Con{+h, t} Con{+g, u}:
53 dot.go(t, u, (acc + (h * g : F32) : F32))
54 case _ _:
55 acc
56
57def sum.go(xs: List<&2, F32>, acc: F32) -> F32:
58 match xs:
59 case Nil{}:
60 acc
61 case Con{+h, t}:
62 sum.go(t, (acc + h : F32))
63
64def relu.l(xs: List<&2, F32>) -> List<&2, F32>:
65 match xs:
66 case Nil{}:
67 Nil{}
68 case Con{+h, t}:
69 F32.max(h, 0.0) <> relu.l(t)
70
71def fill.l(n: Nat, +v: F32) -> List<&2, F32>:
72 match n:
73 case 0n:
74 Nil{}
75 case 1n+p:
76 v <> fill.l(p, v)
77
78# ---------------------------------------------------------------------
79# Vec
80# ---------------------------------------------------------------------
81
82def Vec.zeros(n: Nat) -> Vec<n>:
83 Vec{fill.l(n, 0.0)}
84
85def Vec.fill(n: Nat, +v: F32) -> Vec<n>:
86 Vec{fill.l(n, v)}
87
88def Vec.add(-n: Nat, a: Vec<n>, b: Vec<n>) -> Vec<n>:
89 match a b:
90 case Vec{xs} Vec{ys}:
91 Vec{zip_with.add(xs, ys)}
92
93def Vec.sub(-n: Nat, a: Vec<n>, b: Vec<n>) -> Vec<n>:
94 match a b:
95 case Vec{xs} Vec{ys}:
96 Vec{zip_with.sub(xs, ys)}
97
98def Vec.mul(-n: Nat, a: Vec<n>, b: Vec<n>) -> Vec<n>:
99 match a b:
100 case Vec{xs} Vec{ys}:
101 Vec{zip_with.mul(xs, ys)}
102
103def Vec.scale(-n: Nat, +k: F32, a: Vec<n>) -> Vec<n>:
104 match a:
105 case Vec{xs}:
106 Vec{scale.l(k, xs)}
107
108def Vec.dot(-n: Nat, a: Vec<n>, b: Vec<n>) -> F32:
109 match a b:
110 case Vec{xs} Vec{ys}:
111 dot.go(xs, ys, 0.0)
112
113def Vec.sum(-n: Nat, a: Vec<n>) -> F32:
114 match a:
115 case Vec{xs}:
116 sum.go(xs, 0.0)
117
118def Vec.relu(-n: Nat, a: Vec<n>) -> Vec<n>:
119 match a:
120 case Vec{xs}:
121 Vec{relu.l(xs)}
122
123# ---------------------------------------------------------------------
124# listas de linhas (internas)
125# ---------------------------------------------------------------------
126
127def heads(rows: List<&2, List<&2, F32>>) -> List<&2, F32>:
128 match rows:
129 case Con{Con{+h, t}, rest}:
130 h <> heads(rest)
131 case _:
132 Nil{}
133
134def tails(rows: List<&2, List<&2, F32>>) -> List<&2, List<&2, F32>>:
135 match rows:
136 case Con{Con{h, t}, rest}:
137 t <> tails(rest)
138 case _:
139 Nil{}
140
141# transposta de uma matriz com `c` colunas (c é quem encolhe, vem primeiro)
142def transpose.go(c: Nat, +rows: List<&2, List<&2, F32>>) -> List<&2, List<&2, F32>>:
143 match c:
144 case 0n:
145 Nil{}
146 case 1n+p:
147 heads(rows) <> transpose.go(p, tails(rows))
148
149def rows.zip_add(xs: List<&2, List<&2, F32>>, ys: List<&2, List<&2, F32>>) -> List<&2, List<&2, F32>>:
150 match xs ys:
151 case Con{a, t} Con{b, u}:
152 zip_with.add(a, b) <> rows.zip_add(t, u)
153 case _ _:
154 Nil{}
155
156def rows.zip_sub(xs: List<&2, List<&2, F32>>, ys: List<&2, List<&2, F32>>) -> List<&2, List<&2, F32>>:
157 match xs ys:
158 case Con{a, t} Con{b, u}:
159 zip_with.sub(a, b) <> rows.zip_sub(t, u)
160 case _ _:
161 Nil{}
162
163def rows.zip_mul(xs: List<&2, List<&2, F32>>, ys: List<&2, List<&2, F32>>) -> List<&2, List<&2, F32>>:
164 match xs ys:
165 case Con{a, t} Con{b, u}:
166 zip_with.mul(a, b) <> rows.zip_mul(t, u)
167 case _ _:
168 Nil{}
169
170def rows.scale(+k: F32, xs: List<&2, List<&2, F32>>) -> List<&2, List<&2, F32>>:
171 match xs:
172 case Nil{}:
173 Nil{}
174 case Con{r, t}:
175 scale.l(k, r) <> rows.scale(k, t)
176
177def rows.relu(xs: List<&2, List<&2, F32>>) -> List<&2, List<&2, F32>>:
178 match xs:
179 case Nil{}:
180 Nil{}
181 case Con{r, t}:
182 relu.l(r) <> rows.relu(t)
183
184# soma por colunas
185def rows.col_sums(c: Nat, +rows: List<&2, List<&2, F32>>) -> List<&2, F32>:
186 match c:
187 case 0n:
188 Nil{}
189 case 1n+p:
190 sum.go(heads(rows), 0.0) <> rows.col_sums(p, tails(rows))
191
192def rows.add_row(xs: List<&2, List<&2, F32>>, +b: List<&2, F32>) -> List<&2, List<&2, F32>>:
193 match xs:
194 case Nil{}:
195 Nil{}
196 case Con{r, t}:
197 zip_with.add(r, b) <> rows.add_row(t, b)
198
199def matmul.row(+ra: List<&2, F32>, bt: List<&2, List<&2, F32>>) -> List<&2, F32>:
200 match bt:
201 case Nil{}:
202 Nil{}
203 case Con{col, t}:
204 dot.go(ra, col, 0.0) <> matmul.row(ra, t)
205
206def matmul.rows(a: List<&2, List<&2, F32>>, +bt: List<&2, List<&2, F32>>) -> List<&2, List<&2, F32>>:
207 match a:
208 case Nil{}:
209 Nil{}
210 case Con{r, t}:
211 matmul.row(r, bt) <> matmul.rows(t, bt)
212
213# junta todas as linhas numa lista só
214def flatten.l(rows: List<&2, List<&2, F32>>) -> List<&2, F32>:
215 match rows:
216 case Nil{}:
217 Nil{}
218 case Con{r, t}:
219 List.append(&2, F32, r, flatten.l(t))
220
221# quebra uma lista em r linhas de c números
222def chunk.l(r: Nat, +c: Nat, +xs: List<&2, F32>) -> List<&2, List<&2, F32>>:
223 match r:
224 case 0n:
225 Nil{}
226 case 1n+p:
227 List.take(&2, F32, xs, c) <> chunk.l(p, c, List.drop(&2, F32, xs, c))
228
229# ---------------------------------------------------------------------
230# Mat
231# ---------------------------------------------------------------------
232
233def Mat.zeros(+r: Nat, +c: Nat) -> Mat<r, c>:
234 Mat{chunk.l(r, c, fill.l(Nat.mul(r, c), 0.0))}
235
236def Mat.fill(+r: Nat, +c: Nat, +v: F32) -> Mat<r, c>:
237 Mat{chunk.l(r, c, fill.l(Nat.mul(r, c), v))}
238
239def Mat.add(-r: Nat, -c: Nat, a: Mat<r, c>, b: Mat<r, c>) -> Mat<r, c>:
240 match a b:
241 case Mat{x} Mat{y}:
242 Mat{rows.zip_add(x, y)}
243
244def Mat.sub(-r: Nat, -c: Nat, a: Mat<r, c>, b: Mat<r, c>) -> Mat<r, c>:
245 match a b:
246 case Mat{x} Mat{y}:
247 Mat{rows.zip_sub(x, y)}
248
249# produto elemento a elemento (Hadamard)
250def Mat.mul(-r: Nat, -c: Nat, a: Mat<r, c>, b: Mat<r, c>) -> Mat<r, c>:
251 match a b:
252 case Mat{x} Mat{y}:
253 Mat{rows.zip_mul(x, y)}
254
255def Mat.scale(-r: Nat, -c: Nat, +k: F32, a: Mat<r, c>) -> Mat<r, c>:
256 match a:
257 case Mat{x}:
258 Mat{rows.scale(k, x)}
259
260def Mat.relu(-r: Nat, -c: Nat, a: Mat<r, c>) -> Mat<r, c>:
261 match a:
262 case Mat{x}:
263 Mat{rows.relu(x)}
264
265def Mat.transpose(-r: Nat, +c: Nat, a: Mat<r, c>) -> Mat<c, r>:
266 match a:
267 case Mat{x}:
268 Mat{transpose.go(c, x)}
269
270# (n x k) · (k x m) = (n x m): as dimensões internas têm que ser a mesma k
271def Mat.matmul(-n: Nat, +k: Nat, +m: Nat, a: Mat<n, k>, b: Mat<k, m>) -> Mat<n, m>:
272 match a b:
273 case Mat{x} Mat{y}:
274 Mat{matmul.rows(x, transpose.go(m, y))}
275
276# soma bias (um vetor de m números) a cada linha de uma matriz n x m
277def Mat.add_row(-n: Nat, -m: Nat, a: Mat<n, m>, b: Vec<m>) -> Mat<n, m>:
278 match a b:
279 case Mat{x} Vec{v}:
280 Mat{rows.add_row(x, v)}
281
282# soma de cada coluna: Mat<n, m> -> Vec<m>
283def Mat.col_sums(-n: Nat, +m: Nat, a: Mat<n, m>) -> Vec<m>:
284 match a:
285 case Mat{x}:
286 Vec{rows.col_sums(m, x)}
287
288# matriz · vetor: (n x k) · k = n
289def Mat.matvec(-n: Nat, +k: Nat, a: Mat<n, k>, v: Vec<k>) -> Vec<n>:
290 match a v:
291 case Mat{x} Vec{ys}:
292 Vec{heads(matmul.rows(x, ys <> Nil{}))}
293
294# Reshape: só compila com a PROVA de que o número de elementos não muda.
295def Mat.reshape(-r1: Nat, -c1: Nat, r2: Nat, +c2: Nat, -p: {Nat.mul(r1, c1) == Nat.mul(r2, c2) : Nat}, a: Mat<r1, c1>) -> Mat<r2, c2>:
296 match a:
297 case Mat{x}:
298 Mat{chunk.l(r2, c2, flatten.l(x))}
299
300# ---------------------------------------------------------------------
301# funções por linha: softmax, GELU, layernorm
302# ---------------------------------------------------------------------
303
304def max.go(xs: List<&2, F32>, cur: F32) -> F32:
305 match xs:
306 case Nil{}:
307 cur
308 case Con{+h, t}:
309 max.go(t, F32.max(cur, h))
310
311def max.l(xs: List<&2, F32>) -> F32:
312 match xs:
313 case Nil{}:
314 0.0
315 case Con{+h, t}:
316 max.go(t, h)
317
318def exp_shift(xs: List<&2, F32>, +m: F32) -> List<&2, F32>:
319 match xs:
320 case Nil{}:
321 Nil{}
322 case Con{+h, t}:
323 F32.exp((h - m : F32)) <> exp_shift(t, m)
324
325def div.l(xs: List<&2, F32>, +s: F32) -> List<&2, F32>:
326 match xs:
327 case Nil{}:
328 Nil{}
329 case Con{+h, t}:
330 (h / s : F32) <> div.l(t, s)
331
332# softmax estável: subtrai o máximo antes de exponenciar
333def softmax.fin(+es: List<&2, F32>) -> List<&2, F32>:
334 div.l(es, sum.go(es, 0.0))
335
336def softmax.l(+xs: List<&2, F32>) -> List<&2, F32>:
337 softmax.fin(exp_shift(xs, max.l(xs)))
338
339def rows.softmax(xs: List<&2, List<&2, F32>>) -> List<&2, List<&2, F32>>:
340 match xs:
341 case Nil{}:
342 Nil{}
343 case Con{r, t}:
344 softmax.l(r) <> rows.softmax(t)
345
346# GELU com a aproximação por tanh (a mesma do GPT-2)
347def gelu.f(+x: F32) -> F32:
348 (0.5 * (x * (1.0 + F32.tanh((0.7978846 * (x + (0.044715 * (x * (x * x : F32) : F32) : F32) : F32) : F32)) : F32) : F32) : F32)
349
350def gelu.l(xs: List<&2, F32>) -> List<&2, F32>:
351 match xs:
352 case Nil{}:
353 Nil{}
354 case Con{h, t}:
355 gelu.f(h) <> gelu.l(t)
356
357def rows.gelu(xs: List<&2, List<&2, F32>>) -> List<&2, List<&2, F32>>:
358 match xs:
359 case Nil{}:
360 Nil{}
361 case Con{r, t}:
362 gelu.l(r) <> rows.gelu(t)
363
364def sub_const(xs: List<&2, F32>, +k: F32) -> List<&2, F32>:
365 match xs:
366 case Nil{}:
367 Nil{}
368 case Con{+h, t}:
369 (h - k : F32) <> sub_const(t, k)
370
371def sq_sum(xs: List<&2, F32>, acc: F32) -> F32:
372 match xs:
373 case Nil{}:
374 acc
375 case Con{+h, t}:
376 sq_sum(t, (acc + (h * h : F32) : F32))
377
378# (x - média) / sqrt(variância + eps), depois * gamma + beta
379def layernorm.fin(+cs: List<&2, F32>, +d: F32, +eps: F32, +gamma: List<&2, F32>, +beta: List<&2, F32>) -> List<&2, F32>:
380 zip_with.add(zip_with.mul(scale.l((1.0 / F32.sqrt(((sq_sum(cs, 0.0) / d : F32) + eps : F32)) : F32), cs), gamma), beta)
381
382def layernorm.l(+xs: List<&2, F32>, +d: F32, +eps: F32, +gamma: List<&2, F32>, +beta: List<&2, F32>) -> List<&2, F32>:
383 layernorm.fin(sub_const(xs, (sum.go(xs, 0.0) / d : F32)), d, eps, gamma, beta)
384
385def rows.layernorm(xs: List<&2, List<&2, F32>>, +d: F32, +eps: F32, +gamma: List<&2, F32>, +beta: List<&2, F32>) -> List<&2, List<&2, F32>>:
386 match xs:
387 case Nil{}:
388 Nil{}
389 case Con{r, t}:
390 layernorm.l(r, d, eps, gamma, beta) <> rows.layernorm(t, d, eps, gamma, beta)
391
392def Mat.softmax(-r: Nat, -c: Nat, a: Mat<r, c>) -> Mat<r, c>:
393 match a:
394 case Mat{x}:
395 Mat{rows.softmax(x)}
396
397def Mat.gelu(-r: Nat, -c: Nat, a: Mat<r, c>) -> Mat<r, c>:
398 match a:
399 case Mat{x}:
400 Mat{rows.gelu(x)}
401
402# normaliza cada linha (d colunas) e aplica gamma e beta, vetores de d números
403def Mat.layernorm(-n: Nat, d: Nat, eps: F32, g: Vec<d>, bt: Vec<d>, a: Mat<n, d>) -> Mat<n, d>:
404 match g bt a:
405 case Vec{gamma} Vec{beta} Mat{x}:
406 Mat{rows.layernorm(x, F32.from_nat(d), eps, gamma, beta)}
407
408def Vec.softmax(-n: Nat, a: Vec<n>) -> Vec<n>:
409 match a:
410 case Vec{xs}:
411 Vec{softmax.l(xs)}
412
413# ---------------------------------------------------------------------
414# construção e leitura com verificação de tamanho
415# ---------------------------------------------------------------------
416
417def len.eq(xs: List<&2, F32>, n: Nat) -> Bool:
418 match xs n:
419 case Nil{} 0n:
420 True{}
421 case Con{h, t} 1n+p:
422 len.eq(t, p)
423 case _ _:
424 False{}
425
426def rows.ok(rs: List<&2, List<&2, F32>>, +c: Nat, r: Nat) -> Bool:
427 match rs r:
428 case Nil{} 0n:
429 True{}
430 case Con{row, t} 1n+p:
431 Bool.and(len.eq(row, c), rows.ok(t, c, p))
432 case _ _:
433 False{}
434
435def Vec.of2(-n: Nat, ok: Bool, xs: List<&2, F32>) -> Maybe<&2, Vec<n>>:
436 match ok:
437 case True{}:
438 Some{Vec{xs}}
439 case False{}:
440 None{}
441
442# Vec<n> a partir de uma lista, só se ela tem exatamente n números
443def Vec.of(n: Nat, +xs: List<&2, F32>) -> Maybe<&2, Vec<n>>:
444 Vec.of2(n, len.eq(xs, n), xs)
445
446def Mat.of2(-r: Nat, -c: Nat, ok: Bool, rs: List<&2, List<&2, F32>>) -> Maybe<&2, Mat<r, c>>:
447 match ok:
448 case True{}:
449 Some{Mat{rs}}
450 case False{}:
451 None{}
452
453# Mat<r, c> a partir de linhas, só se são exatamente r linhas de c números
454def Mat.of(+r: Nat, +c: Nat, +rs: List<&2, List<&2, F32>>) -> Maybe<&2, Mat<r, c>>:
455 Mat.of2(r, c, rows.ok(rs, c, r), rs)
456
457def Vec.to_list(-n: Nat, a: Vec<n>) -> List<&2, F32>:
458 match a:
459 case Vec{xs}:
460 xs
461
462def Mat.to_rows(-r: Nat, -c: Nat, a: Mat<r, c>) -> List<&2, List<&2, F32>>:
463 match a:
464 case Mat{x}:
465 x
466
467# ---------------------------------------------------------------------
468# LAWS: quando um reshape é possível, e uma prova reaproveitada do nat-lemmas
469# ---------------------------------------------------------------------
470
471# LEI (reshape_swap): r x c e c x r têm o mesmo número de elementos
472# (a multiplicação é comutativa), então esse reshape sempre existe.
473law reshape_swap:
474 for +r: Nat
475 for +c: Nat
476 {Nat.mul(r, c) == Nat.mul(c, r) : Nat}
477
478def reshape_swap(r, c):
479 NL.mul_comm(r, c)
480
481# LEI (reshape_flat): r x c tem o mesmo número de elementos que 1 x (r*c)
482# (multiplicar por 1 não muda nada), então achatar uma matriz sempre existe.
483law reshape_flat:
484 for +r: Nat
485 for +c: Nat
486 {Nat.mul(r, c) == Nat.mul(1n, Nat.mul(r, c)) : Nat}
487
488def reshape_flat(r, c):
489 Equal.sym(Nat, Nat.mul(1n, Nat.mul(r, c)), Nat.mul(r, c), NL.mul_one_l(Nat.mul(r, c)))
490
491# Mat<r, c> -> Mat<1, r*c>: a prova vem da lei acima
492def Mat.flatten(+r: Nat, +c: Nat, a: Mat<r, c>) -> Mat<1n, Nat.mul(r, c)>:
493 Mat.reshape(r, c, 1n, Nat.mul(r, c), reshape_flat(r, c), a)
494
495# Mat<r, c> -> Mat<c, r> pelos mesmos dados (não é a transposta!): só reinterpreta
496def Mat.reshape_swap(+r: Nat, +c: Nat, a: Mat<r, c>) -> Mat<c, r>:
497 Mat.reshape(r, c, c, r, reshape_swap(r, c), a)