1 # bend_tensors: dense linear algebra with shapes in the types. 2 # 3 # A Vec(n) holds 4 * 2^n floats and a Mat(r, c) 4 * 2^r rows of 4 * 2^c 4 # floats, both as balanced trees of 4x4 register blocks. The tree is the 5 # fork tree: every op below a node is a parallel let, and the matrix 6 # product recurses on the tree, cache-oblivious, down to a 64-multiply 7 # kernel. A shape mismatch is a checker error, not a runtime one. 8 # 9 # Nothing here matches on a computed value, recurses mutually, or opens 10 # an Array: those are the rules the compiler makes fast code from. Read 11 # AGENTS.md before editing. This file is assembled from gen/ by 12 # gen/assemble.py; edit the parts, not this file. bend_tensors.bend is 13 # the release (CPU BLAS); bend_tensors_gpu.bend adds the GPU paths. 14 15 import Base 16 17 # Base's F32.max and F32.show take + parameters, which a template slot 18 # does not accept; these take plain ones. 19 def F32.max.of(a: F32, b: F32) -> F32: 20 F32.max(a, b) 21 22 def F32.show.of(x: F32) -> String: 23 F32.show(x) 24 25 # Blocks 26 # ====== 27 # 28 # A Blk is a 4x4 block of F32 in row-major order, a Vec4 four F32. Both 29 # are Data records of scalars: they ride in registers, and the trees 30 # built from them are shared through + binders with no copy. A block 31 # product is 64 fused multiply-adds with no memory traffic: the kernel 32 # every larger op bottoms out in. 33 34 type Blk is Data: 35 Blk{a00: F32, a01: F32, a02: F32, a03: F32, 36 a10: F32, a11: F32, a12: F32, a13: F32, 37 a20: F32, a21: F32, a22: F32, a23: F32, 38 a30: F32, a31: F32, a32: F32, a33: F32} 39 40 type Vec4 is Data: 41 Vec4{x0: F32, x1: F32, x2: F32, x3: F32} 42 43 # Vec4 44 # ---- 45 46 def Vec4.full(+x: F32) -> Vec4: 47 Vec4{x, x, x, x} 48 49 def Vec4.zeros() -> Vec4: 50 Vec4{0.0, 0.0, 0.0, 0.0} 51 52 # x, x + 1, x + 2, x + 3 53 def Vec4.arange(+x: F32) -> Vec4: 54 Vec4{x, (x + 1.0 : F32), (x + 2.0 : F32), (x + 3.0 : F32)} 55 56 def Vec4.map(~f: F32 -> F32, v: Vec4) -> Vec4: 57 Vec4{x0, x1, x2, x3} = v 58 Vec4{f(x0), f(x1), f(x2), f(x3)} 59 60 def Vec4.zip(~f: F32 -> F32 -> F32, a: Vec4, b: Vec4) -> Vec4: 61 Vec4{x0, x1, x2, x3} = a 62 Vec4{y0, y1, y2, y3} = b 63 Vec4{f(x0, y0), f(x1, y1), f(x2, y2), f(x3, y3)} 64 65 def Vec4.add(a: Vec4, b: Vec4) -> Vec4: 66 Vec4{x0, x1, x2, x3} = a 67 Vec4{y0, y1, y2, y3} = b 68 Vec4{(x0 + y0 : F32), (x1 + y1 : F32), (x2 + y2 : F32), (x3 + y3 : F32)} 69 70 def Vec4.sub(a: Vec4, b: Vec4) -> Vec4: 71 Vec4{x0, x1, x2, x3} = a 72 Vec4{y0, y1, y2, y3} = b 73 Vec4{(x0 - y0 : F32), (x1 - y1 : F32), (x2 - y2 : F32), (x3 - y3 : F32)} 74 75 def Vec4.mul(a: Vec4, b: Vec4) -> Vec4: 76 Vec4{x0, x1, x2, x3} = a 77 Vec4{y0, y1, y2, y3} = b 78 Vec4{(x0 * y0 : F32), (x1 * y1 : F32), (x2 * y2 : F32), (x3 * y3 : F32)} 79 80 def Vec4.scale(+k: F32, v: Vec4) -> Vec4: 81 Vec4{x0, x1, x2, x3} = v 82 Vec4{(k * x0 : F32), (k * x1 : F32), (k * x2 : F32), (k * x3 : F32)} 83 84 # the sum, as (x0 + x1) + (x2 + x3): every reduction in this file 85 # pairs its terms the same way, so laws can equate them 86 def Vec4.sum(v: Vec4) -> F32: 87 Vec4{x0, x1, x2, x3} = v 88 ((x0 + x1) + (x2 + x3) : F32) 89 90 def Vec4.dot(a: Vec4, b: Vec4) -> F32: 91 Vec4{x0, x1, x2, x3} = a 92 Vec4{y0, y1, y2, y3} = b 93 ((x0 * y0 + x1 * y1) + (x2 * y2 + x3 * y3) : F32) 94 95 def Vec4.max(v: Vec4) -> F32: 96 Vec4{x0, x1, x2, x3} = v 97 F32.max(F32.max(x0, x1), F32.max(x2, x3)) 98 99 def Vec4.min(v: Vec4) -> F32: 100 Vec4{x0, x1, x2, x3} = v 101 F32.min(F32.min(x0, x1), F32.min(x2, x3)) 102 103 # the index (0-3) and value of the largest element; the first wins ties 104 def Vec4.argmax.pick(c: Bool, +i: U32, +x: F32, +j: U32, +y: F32) -> U32 & F32: 105 match c: 106 case True{}: 107 (i, x) 108 case False{}: 109 (j, y) 110 111 def Vec4.argmax.two(ix: U32 & F32, jy: U32 & F32) -> U32 & F32: 112 (i, x) = ix 113 (j, y) = jy 114 +x2 = x 115 +y2 = y 116 Vec4.argmax.pick((x2 >= y2 : F32), i, x2, j, y2) 117 118 def Vec4.argmax(v: Vec4) -> U32 & F32: 119 Vec4{x0, x1, x2, x3} = v 120 Vec4.argmax.two(Vec4.argmax.two((0, x0), (1, x1)), Vec4.argmax.two((2, x2), (3, x3))) 121 122 def Vec4.to_list(v: Vec4, acc: List<&2, F32>) -> List<&2, F32>: 123 Vec4{x0, x1, x2, x3} = v 124 x0 <> x1 <> x2 <> x3 <> acc 125 126 # the first four of xs, padded with 0.0, and the rest 127 def Vec4.from_list(xs: List<&2, F32>) -> Vec4: 128 match xs: 129 case x0 <> x1 <> x2 <> x3 <> rest: 130 Vec4{x0, x1, x2, x3} 131 case x0 <> x1 <> x2 <> []: 132 Vec4{x0, x1, x2, 0.0} 133 case x0 <> x1 <> []: 134 Vec4{x0, x1, 0.0, 0.0} 135 case x0 <> []: 136 Vec4{x0, 0.0, 0.0, 0.0} 137 case []: 138 Vec4{0.0, 0.0, 0.0, 0.0} 139 140 def Vec4.show(v: Vec4) -> String: 141 Vec4{x0, x1, x2, x3} = v 142 F32.show(x0) ++ " " ++ F32.show(x1) ++ " " ++ F32.show(x2) ++ " " ++ F32.show(x3) 143 144 # Blk 145 # --- 146 147 def Blk.full(+x: F32) -> Blk: 148 Blk{x, x, x, x, 149 x, x, x, x, 150 x, x, x, x, 151 x, x, x, x} 152 153 def Blk.zeros() -> Blk: 154 Blk.full(0.0) 155 156 def Blk.eye() -> Blk: 157 Blk{1.0, 0.0, 0.0, 0.0, 158 0.0, 1.0, 0.0, 0.0, 159 0.0, 0.0, 1.0, 0.0, 160 0.0, 0.0, 0.0, 1.0} 161 162 def Blk.map(~f: F32 -> F32, m: Blk) -> Blk: 163 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = m 164 Blk{f(a00), f(a01), f(a02), f(a03), 165 f(a10), f(a11), f(a12), f(a13), 166 f(a20), f(a21), f(a22), f(a23), 167 f(a30), f(a31), f(a32), f(a33)} 168 169 def Blk.zip(~f: F32 -> F32 -> F32, m: Blk, n: Blk) -> Blk: 170 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = m 171 Blk{b00, b01, b02, b03, b10, b11, b12, b13, b20, b21, b22, b23, b30, b31, b32, b33} = n 172 Blk{f(a00, b00), f(a01, b01), f(a02, b02), f(a03, b03), 173 f(a10, b10), f(a11, b11), f(a12, b12), f(a13, b13), 174 f(a20, b20), f(a21, b21), f(a22, b22), f(a23, b23), 175 f(a30, b30), f(a31, b31), f(a32, b32), f(a33, b33)} 176 177 def Blk.add(m: Blk, n: Blk) -> Blk: 178 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = m 179 Blk{b00, b01, b02, b03, b10, b11, b12, b13, b20, b21, b22, b23, b30, b31, b32, b33} = n 180 Blk{(a00 + b00 : F32), (a01 + b01 : F32), (a02 + b02 : F32), (a03 + b03 : F32), 181 (a10 + b10 : F32), (a11 + b11 : F32), (a12 + b12 : F32), (a13 + b13 : F32), 182 (a20 + b20 : F32), (a21 + b21 : F32), (a22 + b22 : F32), (a23 + b23 : F32), 183 (a30 + b30 : F32), (a31 + b31 : F32), (a32 + b32 : F32), (a33 + b33 : F32)} 184 185 def Blk.sub(m: Blk, n: Blk) -> Blk: 186 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = m 187 Blk{b00, b01, b02, b03, b10, b11, b12, b13, b20, b21, b22, b23, b30, b31, b32, b33} = n 188 Blk{(a00 - b00 : F32), (a01 - b01 : F32), (a02 - b02 : F32), (a03 - b03 : F32), 189 (a10 - b10 : F32), (a11 - b11 : F32), (a12 - b12 : F32), (a13 - b13 : F32), 190 (a20 - b20 : F32), (a21 - b21 : F32), (a22 - b22 : F32), (a23 - b23 : F32), 191 (a30 - b30 : F32), (a31 - b31 : F32), (a32 - b32 : F32), (a33 - b33 : F32)} 192 193 def Blk.mul(m: Blk, n: Blk) -> Blk: 194 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = m 195 Blk{b00, b01, b02, b03, b10, b11, b12, b13, b20, b21, b22, b23, b30, b31, b32, b33} = n 196 Blk{(a00 * b00 : F32), (a01 * b01 : F32), (a02 * b02 : F32), (a03 * b03 : F32), 197 (a10 * b10 : F32), (a11 * b11 : F32), (a12 * b12 : F32), (a13 * b13 : F32), 198 (a20 * b20 : F32), (a21 * b21 : F32), (a22 * b22 : F32), (a23 * b23 : F32), 199 (a30 * b30 : F32), (a31 * b31 : F32), (a32 * b32 : F32), (a33 * b33 : F32)} 200 201 def Blk.scale(+k: F32, m: Blk) -> Blk: 202 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = m 203 Blk{(k * a00 : F32), (k * a01 : F32), (k * a02 : F32), (k * a03 : F32), 204 (k * a10 : F32), (k * a11 : F32), (k * a12 : F32), (k * a13 : F32), 205 (k * a20 : F32), (k * a21 : F32), (k * a22 : F32), (k * a23 : F32), 206 (k * a30 : F32), (k * a31 : F32), (k * a32 : F32), (k * a33 : F32)} 207 208 def Blk.transpose(m: Blk) -> Blk: 209 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = m 210 Blk{a00, a10, a20, a30, 211 a01, a11, a21, a31, 212 a02, a12, a22, a32, 213 a03, a13, a23, a33} 214 215 # m * n: 64 multiply-adds, each cell as (a0 b0 + a1 b1) + (a2 b2 + a3 b3) 216 def Blk.matmul(m: Blk, n: Blk) -> Blk: 217 Blk{+a00, +a01, +a02, +a03, +a10, +a11, +a12, +a13, +a20, +a21, +a22, +a23, +a30, +a31, +a32, +a33} = m 218 Blk{+b00, +b01, +b02, +b03, +b10, +b11, +b12, +b13, +b20, +b21, +b22, +b23, +b30, +b31, +b32, +b33} = n 219 Blk{((a00 * b00 + a01 * b10) + (a02 * b20 + a03 * b30) : F32), ((a00 * b01 + a01 * b11) + (a02 * b21 + a03 * b31) : F32), ((a00 * b02 + a01 * b12) + (a02 * b22 + a03 * b32) : F32), ((a00 * b03 + a01 * b13) + (a02 * b23 + a03 * b33) : F32), 220 ((a10 * b00 + a11 * b10) + (a12 * b20 + a13 * b30) : F32), ((a10 * b01 + a11 * b11) + (a12 * b21 + a13 * b31) : F32), ((a10 * b02 + a11 * b12) + (a12 * b22 + a13 * b32) : F32), ((a10 * b03 + a11 * b13) + (a12 * b23 + a13 * b33) : F32), 221 ((a20 * b00 + a21 * b10) + (a22 * b20 + a23 * b30) : F32), ((a20 * b01 + a21 * b11) + (a22 * b21 + a23 * b31) : F32), ((a20 * b02 + a21 * b12) + (a22 * b22 + a23 * b32) : F32), ((a20 * b03 + a21 * b13) + (a22 * b23 + a23 * b33) : F32), 222 ((a30 * b00 + a31 * b10) + (a32 * b20 + a33 * b30) : F32), ((a30 * b01 + a31 * b11) + (a32 * b21 + a33 * b31) : F32), ((a30 * b02 + a31 * b12) + (a32 * b22 + a33 * b32) : F32), ((a30 * b03 + a31 * b13) + (a32 * b23 + a33 * b33) : F32)} 223 224 # m * v: each row dotted with v 225 def Blk.matvec(m: Blk, v: Vec4) -> Vec4: 226 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = m 227 Vec4{+y0, +y1, +y2, +y3} = v 228 Vec4{((a00 * y0 + a01 * y1) + (a02 * y2 + a03 * y3) : F32), ((a10 * y0 + a11 * y1) + (a12 * y2 + a13 * y3) : F32), ((a20 * y0 + a21 * y1) + (a22 * y2 + a23 * y3) : F32), ((a30 * y0 + a31 * y1) + (a32 * y2 + a33 * y3) : F32)} 229 230 # row i's sum, as (a_i0 + a_i1) + (a_i2 + a_i3) 231 def Blk.sum_rows(m: Blk) -> Vec4: 232 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = m 233 Vec4{((a00 + a01) + (a02 + a03) : F32), ((a10 + a11) + (a12 + a13) : F32), ((a20 + a21) + (a22 + a23) : F32), ((a30 + a31) + (a32 + a33) : F32)} 234 235 # the sum of every cell, as the sum of the row sums 236 def Blk.sum(m: Blk) -> F32: 237 Vec4.sum(Blk.sum_rows(m)) 238 239 # column j's sum, as (a_0j + a_1j) + (a_2j + a_3j): the row sums of the transpose 240 def Blk.sum_cols(m: Blk) -> Vec4: 241 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = m 242 Vec4{((a00 + a10) + (a20 + a30) : F32), ((a01 + a11) + (a21 + a31) : F32), ((a02 + a12) + (a22 + a32) : F32), ((a03 + a13) + (a23 + a33) : F32)} 243 244 def Blk.max_rows(m: Blk) -> Vec4: 245 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = m 246 Vec4{F32.max(F32.max(a00, a01), F32.max(a02, a03)), F32.max(F32.max(a10, a11), F32.max(a12, a13)), F32.max(F32.max(a20, a21), F32.max(a22, a23)), F32.max(F32.max(a30, a31), F32.max(a32, a33))} 247 248 # v added to every row 249 def Blk.add_row(m: Blk, v: Vec4) -> Blk: 250 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = m 251 Vec4{+y0, +y1, +y2, +y3} = v 252 Blk{(a00 + y0 : F32), (a01 + y1 : F32), (a02 + y2 : F32), (a03 + y3 : F32), 253 (a10 + y0 : F32), (a11 + y1 : F32), (a12 + y2 : F32), (a13 + y3 : F32), 254 (a20 + y0 : F32), (a21 + y1 : F32), (a22 + y2 : F32), (a23 + y3 : F32), 255 (a30 + y0 : F32), (a31 + y1 : F32), (a32 + y2 : F32), (a33 + y3 : F32)} 256 257 # v subtracted from every row 258 def Blk.sub_row(m: Blk, v: Vec4) -> Blk: 259 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = m 260 Vec4{+y0, +y1, +y2, +y3} = v 261 Blk{(a00 - y0 : F32), (a01 - y1 : F32), (a02 - y2 : F32), (a03 - y3 : F32), 262 (a10 - y0 : F32), (a11 - y1 : F32), (a12 - y2 : F32), (a13 - y3 : F32), 263 (a20 - y0 : F32), (a21 - y1 : F32), (a22 - y2 : F32), (a23 - y3 : F32), 264 (a30 - y0 : F32), (a31 - y1 : F32), (a32 - y2 : F32), (a33 - y3 : F32)} 265 266 # row i subtracted by v_i (the column vector v broadcast along rows) 267 def Blk.sub_col(m: Blk, v: Vec4) -> Blk: 268 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = m 269 Vec4{+y0, +y1, +y2, +y3} = v 270 Blk{(a00 - y0 : F32), (a01 - y0 : F32), (a02 - y0 : F32), (a03 - y0 : F32), 271 (a10 - y1 : F32), (a11 - y1 : F32), (a12 - y1 : F32), (a13 - y1 : F32), 272 (a20 - y2 : F32), (a21 - y2 : F32), (a22 - y2 : F32), (a23 - y2 : F32), 273 (a30 - y3 : F32), (a31 - y3 : F32), (a32 - y3 : F32), (a33 - y3 : F32)} 274 275 # row i scaled by v_i 276 def Blk.scale_rows(m: Blk, v: Vec4) -> Blk: 277 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = m 278 Vec4{+y0, +y1, +y2, +y3} = v 279 Blk{(y0 * a00 : F32), (y0 * a01 : F32), (y0 * a02 : F32), (y0 * a03 : F32), 280 (y1 * a10 : F32), (y1 * a11 : F32), (y1 * a12 : F32), (y1 * a13 : F32), 281 (y2 * a20 : F32), (y2 * a21 : F32), (y2 * a22 : F32), (y2 * a23 : F32), 282 (y3 * a30 : F32), (y3 * a31 : F32), (y3 * a32 : F32), (y3 * a33 : F32)} 283 284 # the 16 cells in row-major order, before acc 285 def Blk.to_list(m: Blk, acc: List<&2, F32>) -> List<&2, F32>: 286 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = m 287 a00 <> a01 <> a02 <> a03 <> a10 <> a11 <> a12 <> a13 <> a20 <> a21 <> a22 <> a23 <> a30 <> a31 <> a32 <> a33 <> acc 288 289 # a block from its four rows 290 def Blk.from_vec4s(v0: Vec4, v1: Vec4, v2: Vec4, v3: Vec4) -> Blk: 291 Vec4{a00, a01, a02, a03} = v0 292 Vec4{a10, a11, a12, a13} = v1 293 Vec4{a20, a21, a22, a23} = v2 294 Vec4{a30, a31, a32, a33} = v3 295 Blk{a00, a01, a02, a03, 296 a10, a11, a12, a13, 297 a20, a21, a22, a23, 298 a30, a31, a32, a33} 299 300 # a block from four rows of four; a short row pads with 0.0 301 def Blk.from_rows(r0: List<&2, F32>, r1: List<&2, F32>, r2: List<&2, F32>, r3: List<&2, F32>) -> Blk: 302 Blk.from_vec4s(Vec4.from_list(r0), Vec4.from_list(r1), Vec4.from_list(r2), Vec4.from_list(r3)) 303 304 def Blk.row(m: Blk, i: Nat) -> Vec4: 305 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = m 306 match i: 307 case 0n: 308 Vec4{a00, a01, a02, a03} 309 case 1n: 310 Vec4{a10, a11, a12, a13} 311 case 2n: 312 Vec4{a20, a21, a22, a23} 313 case _: 314 Vec4{a30, a31, a32, a33} 315 316 # Masks 317 # ----- 318 # 319 # k counts the valid elements (or rows, or columns) of a block; the 320 # rest are padding, set to 0.0 or left out of a reduction. A k of 0 321 # only reaches a reduction whose result the caller discards. 322 323 # elements at index >= k set to 0.0 324 def Vec4.mask(k: Nat, v: Vec4) -> Vec4: 325 match k v: 326 case 0n Vec4{x0, x1, x2, x3}: 327 Vec4{0.0, 0.0, 0.0, 0.0} 328 case 1n Vec4{x0, x1, x2, x3}: 329 Vec4{x0, 0.0, 0.0, 0.0} 330 case 2n Vec4{x0, x1, x2, x3}: 331 Vec4{x0, x1, 0.0, 0.0} 332 case 3n Vec4{x0, x1, x2, x3}: 333 Vec4{x0, x1, x2, 0.0} 334 case _ v2: 335 v2 336 337 # the largest of the first k elements 338 def Vec4.max_upto(k: Nat, v: Vec4) -> F32: 339 match k v: 340 case 1n Vec4{x0, x1, x2, x3}: 341 x0 342 case 2n Vec4{x0, x1, x2, x3}: 343 F32.max(x0, x1) 344 case 3n Vec4{x0, x1, x2, x3}: 345 F32.max(F32.max(x0, x1), x2) 346 case _ v2: 347 Vec4.max(v2) 348 349 # the smallest of the first k elements 350 def Vec4.min_upto(k: Nat, v: Vec4) -> F32: 351 match k v: 352 case 1n Vec4{x0, x1, x2, x3}: 353 x0 354 case 2n Vec4{x0, x1, x2, x3}: 355 F32.min(x0, x1) 356 case 3n Vec4{x0, x1, x2, x3}: 357 F32.min(F32.min(x0, x1), x2) 358 case _ v2: 359 Vec4.min(v2) 360 361 # the index and value of the largest of the first k elements 362 def Vec4.argmax_upto(k: Nat, v: Vec4) -> U32 & F32: 363 match k v: 364 case 1n Vec4{x0, x1, x2, x3}: 365 (0, x0) 366 case 2n Vec4{x0, x1, x2, x3}: 367 Vec4.argmax.two((0, x0), (1, x1)) 368 case 3n Vec4{x0, x1, x2, x3}: 369 Vec4.argmax.two(Vec4.argmax.two((0, x0), (1, x1)), (2, x2)) 370 case _ v2: 371 Vec4.argmax(v2) 372 373 # rows at index >= k set to 0.0 374 def Blk.mask_rows(k: Nat, m: Blk) -> Blk: 375 match k m: 376 case 0n Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33}: 377 Blk{0.0, 0.0, 0.0, 0.0, 378 0.0, 0.0, 0.0, 0.0, 379 0.0, 0.0, 0.0, 0.0, 380 0.0, 0.0, 0.0, 0.0} 381 case 1n Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33}: 382 Blk{a00, a01, a02, a03, 383 0.0, 0.0, 0.0, 0.0, 384 0.0, 0.0, 0.0, 0.0, 385 0.0, 0.0, 0.0, 0.0} 386 case 2n Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33}: 387 Blk{a00, a01, a02, a03, 388 a10, a11, a12, a13, 389 0.0, 0.0, 0.0, 0.0, 390 0.0, 0.0, 0.0, 0.0} 391 case 3n Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33}: 392 Blk{a00, a01, a02, a03, 393 a10, a11, a12, a13, 394 a20, a21, a22, a23, 395 0.0, 0.0, 0.0, 0.0} 396 case _ m2: 397 m2 398 399 # columns at index >= k set to 0.0 400 def Blk.mask_cols(k: Nat, m: Blk) -> Blk: 401 match k m: 402 case 0n Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33}: 403 Blk{0.0, 0.0, 0.0, 0.0, 404 0.0, 0.0, 0.0, 0.0, 405 0.0, 0.0, 0.0, 0.0, 406 0.0, 0.0, 0.0, 0.0} 407 case 1n Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33}: 408 Blk{a00, 0.0, 0.0, 0.0, 409 a10, 0.0, 0.0, 0.0, 410 a20, 0.0, 0.0, 0.0, 411 a30, 0.0, 0.0, 0.0} 412 case 2n Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33}: 413 Blk{a00, a01, 0.0, 0.0, 414 a10, a11, 0.0, 0.0, 415 a20, a21, 0.0, 0.0, 416 a30, a31, 0.0, 0.0} 417 case 3n Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33}: 418 Blk{a00, a01, a02, 0.0, 419 a10, a11, a12, 0.0, 420 a20, a21, a22, 0.0, 421 a30, a31, a32, 0.0} 422 case _ m2: 423 m2 424 425 # rows at index >= ki and columns at index >= kj set to 0.0 426 def Blk.mask(ki: Nat, kj: Nat, m: Blk) -> Blk: 427 Blk.mask_cols(kj, Blk.mask_rows(ki, m)) 428 429 # each row's largest among its first k columns 430 def Blk.max_rows_upto(k: Nat, m: Blk) -> Vec4: 431 match k m: 432 case 1n Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33}: 433 Vec4{a00, a10, a20, a30} 434 case 2n Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33}: 435 Vec4{F32.max(a00, a01), F32.max(a10, a11), F32.max(a20, a21), F32.max(a30, a31)} 436 case 3n Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33}: 437 Vec4{F32.max(F32.max(a00, a01), a02), F32.max(F32.max(a10, a11), a12), F32.max(F32.max(a20, a21), a22), F32.max(F32.max(a30, a31), a32)} 438 case _ m2: 439 Blk.max_rows(m2) 440 441 # Vectors 442 # ======= 443 # 444 # VecT(n) is a balanced binary tree of depth n over Vec4 leaves: 4 * 2^n 445 # floats. The depth is a live Nat, so every op matches on it first and 446 # then opens the constructor the depth implies, as Base does for Word. 447 # Every recursion below a node is a parallel let: the tree is the fork 448 # tree, balanced by construction. 449 450 law VecT: 451 for n: Nat 452 Data 453 454 type VecT.Leaf is Data: 455 VecL{v: Vec4} 456 457 type VecT.Node<-p: Nat> is Data: 458 VecN{l: VecT(p), r: VecT(p)} 459 460 def VecT(n): 461 match n: 462 case 0n: 463 VecT.Leaf 464 case 1n+p: 465 VecT.Node
466 467 # the element count of a VecT(n): 4 * 2^n 468 def VecT.size(+n: Nat) -> Nat: 469 match n: 470 case 0n: 471 4n 472 case 1n+p: 473 +h = VecT.size(p) 474 Nat.add(h, h) 475 476 def VecT.full(+n: Nat, +x: F32) -> VecT(n): 477 match n: 478 case 0n: 479 VecL{Vec4.full(x)} 480 case 1n+p: 481 l r = VecT.full(p, x) VecT.full(p, x) 482 VecN{l, r} 483 484 def VecT.zeros(+n: Nat) -> VecT(n): 485 VecT.full(n, 0.0) 486 487 def VecT.ones(+n: Nat) -> VecT(n): 488 VecT.full(n, 1.0) 489 490 # x, x + 1, x + 2, .. 491 def VecT.arange.from(+n: Nat, +x: F32) -> VecT(n): 492 match n: 493 case 0n: 494 VecL{Vec4.arange(x)} 495 case 1n+p: 496 +h = F32.from_nat(VecT.size(p)) 497 l r = VecT.arange.from(p, x) VecT.arange.from(p, (x + h : F32)) 498 VecN{l, r} 499 500 # 0, 1, 2, .. 501 def VecT.arange(+n: Nat) -> VecT(n): 502 VecT.arange.from(n, 0.0) 503 504 def VecT.map(~f: F32 -> F32, +n: Nat, v: VecT(n)) -> VecT(n): 505 match n: 506 case 0n: 507 VecL{x} = v 508 VecL{Vec4.map(~f, x)} 509 case 1n+p: 510 VecN{l, r} = v 511 a b = VecT.map(~f, p, l) VecT.map(~f, p, r) 512 VecN{a, b} 513 514 def VecT.zip(~f: F32 -> F32 -> F32, +n: Nat, a: VecT(n), b: VecT(n)) -> VecT(n): 515 match n: 516 case 0n: 517 VecL{x} = a 518 VecL{y} = b 519 VecL{Vec4.zip(~f, x, y)} 520 case 1n+p: 521 VecN{al, ar} = a 522 VecN{bl, br} = b 523 l r = VecT.zip(~f, p, al, bl) VecT.zip(~f, p, ar, br) 524 VecN{l, r} 525 526 def VecT.add(+n: Nat, a: VecT(n), b: VecT(n)) -> VecT(n): 527 VecT.zip(~F32.add, n, a, b) 528 529 def VecT.sub(+n: Nat, a: VecT(n), b: VecT(n)) -> VecT(n): 530 VecT.zip(~F32.sub, n, a, b) 531 532 # elementwise product 533 def VecT.mul(+n: Nat, a: VecT(n), b: VecT(n)) -> VecT(n): 534 VecT.zip(~F32.mul, n, a, b) 535 536 def VecT.exp(+n: Nat, v: VecT(n)) -> VecT(n): 537 VecT.map(~F32.exp, n, v) 538 539 def VecT.scale(+n: Nat, +k: F32, v: VecT(n)) -> VecT(n): 540 match n: 541 case 0n: 542 VecL{x} = v 543 VecL{Vec4.scale(k, x)} 544 case 1n+p: 545 VecN{l, r} = v 546 a b = VecT.scale(p, k, l) VecT.scale(p, k, r) 547 VecN{a, b} 548 549 # k added to every element 550 def VecT.shift(+n: Nat, +k: F32, v: VecT(n)) -> VecT(n): 551 match n: 552 case 0n: 553 VecL{x} = v 554 Vec4{x0, x1, x2, x3} = x 555 VecL{Vec4{(x0 + k : F32), (x1 + k : F32), (x2 + k : F32), (x3 + k : F32)}} 556 case 1n+p: 557 VecN{l, r} = v 558 a b = VecT.shift(p, k, l) VecT.shift(p, k, r) 559 VecN{a, b} 560 561 # the sum, left half plus right half 562 def VecT.sum(+n: Nat, v: VecT(n)) -> F32: 563 match n: 564 case 0n: 565 VecL{x} = v 566 Vec4.sum(x) 567 case 1n+p: 568 VecN{l, r} = v 569 a b = VecT.sum(p, l) VecT.sum(p, r) 570 (a + b : F32) 571 572 def VecT.dot(+n: Nat, a: VecT(n), b: VecT(n)) -> F32: 573 match n: 574 case 0n: 575 VecL{x} = a 576 VecL{y} = b 577 Vec4.dot(x, y) 578 case 1n+p: 579 VecN{al, ar} = a 580 VecN{bl, br} = b 581 l r = VecT.dot(p, al, bl) VecT.dot(p, ar, br) 582 (l + r : F32) 583 584 def VecT.max(+n: Nat, v: VecT(n)) -> F32: 585 match n: 586 case 0n: 587 VecL{x} = v 588 Vec4.max(x) 589 case 1n+p: 590 VecN{l, r} = v 591 a b = VecT.max(p, l) VecT.max(p, r) 592 F32.max(a, b) 593 594 def VecT.min(+n: Nat, v: VecT(n)) -> F32: 595 match n: 596 case 0n: 597 VecL{x} = v 598 Vec4.min(x) 599 case 1n+p: 600 VecN{l, r} = v 601 a b = VecT.min(p, l) VecT.min(p, r) 602 F32.min(a, b) 603 604 # the two halves' winners, the right one's index offset by the half's size 605 def VecT.argmax.two(+off: U32, ix: U32 & F32, jy: U32 & F32) -> U32 & F32: 606 (i, x) = ix 607 (j, y) = jy 608 Vec4.argmax.two((i, x), ((j + off : U32), y)) 609 610 # the index and value of the largest element; the first wins ties 611 def VecT.argmax(+n: Nat, v: VecT(n)) -> U32 & F32: 612 match n: 613 case 0n: 614 VecL{x} = v 615 Vec4.argmax(x) 616 case 1n+p: 617 VecN{l, r} = v 618 a b = VecT.argmax(p, l) VecT.argmax(p, r) 619 VecT.argmax.two(U32.from_nat(VecT.size(p)), a, b) 620 621 # the elements in order, before acc 622 # the tree is read borrowed (+v, matched in place), never consumed: a 623 # reader that consumes a shared tree makes the compiler copy it, and 624 # bend 2.0.16 miscompiles that copy when the depth is a literal (the 625 # bias of a model: memory faults in the loop; issues/shared_vec_loop_*) 626 def VecT.to_list.go(+n: Nat, +v: VecT(n), acc: List<&2, F32>) -> List<&2, F32>: 627 match n v: 628 case 0n VecL{x}: 629 Vec4.to_list(x, acc) 630 case 1n+p VecN{l, r}: 631 VecT.to_list.go(p, l, VecT.to_list.go(p, r, acc)) 632 633 def VecT.to_list(+n: Nat, +v: VecT(n)) -> List<&2, F32>: 634 VecT.to_list.go(n, v, []) 635 636 # the first 4 * 2^n elements of xs, padded with 0.0 637 def VecT.from_list(+n: Nat, +xs: List<&2, F32>) -> VecT(n): 638 match n: 639 case 0n: 640 VecL{Vec4.from_list(xs)} 641 case 1n+p: 642 +h = VecT.size(p) 643 l r = VecT.from_list(p, List.take(&2, F32, xs, h)) VecT.from_list(p, List.drop(&2, F32, xs, h)) 644 VecN{l, r} 645 646 def VecT.softmax.fin(+n: Nat, +e: VecT(n)) -> VecT(n): 647 VecT.scale(n, F32.div(1.0, VecT.sum(n, e)), e) 648 649 # exp(v - max v) / sum 650 def VecT.softmax(+n: Nat, +v: VecT(n)) -> VecT(n): 651 VecT.softmax.fin(n, VecT.exp(n, VecT.shift(n, (0.0 - VecT.max(n, v) : F32), v))) 652 653 def VecT.show(+n: Nat, v: VecT(n)) -> String: 654 List.show(~&2, ~F32, ~F32.show.of, VecT.to_list(n, v)) 655 656 # Matrices 657 # ======== 658 # 659 # MatT(r, c) has 4 * 2^r rows and 4 * 2^c columns of F32, as a tree of Blk 660 # leaves. While both depths are positive a node is a quadrant split 661 # (MatQ); when only rows remain it splits top and bottom (MatR); when 662 # only columns remain, left and right (MatC). The block product recurses 663 # on that tree, so it is cache-oblivious: every level halves the working 664 # set, down to a 4x4 kernel in registers. Operands are + and shared, never 665 # copied. The shape is the type: MatT.matmul only accepts MatT(r, k) and 666 # MatT(k, c), and answers MatT(r, c). 667 668 law MatT: 669 for r: Nat 670 for c: Nat 671 Data 672 673 type MatT.Leaf is Data: 674 MatB{b: Blk} 675 676 type MatT.Rows<-p: Nat, -c: Nat> is Data: 677 MatR{top: MatT(p, c), bot: MatT(p, c)} 678 679 type MatT.Cols<-r: Nat, -q: Nat> is Data: 680 MatC{left: MatT(r, q), right: MatT(r, q)} 681 682 type MatT.Quad<-p: Nat, -q: Nat> is Data: 683 MatQ{tl: MatT(p, q), tr: MatT(p, q), bl: MatT(p, q), br: MatT(p, q)} 684 685 def MatT(r, c): 686 match r c: 687 case 0n 0n: 688 MatT.Leaf 689 case 1n+p 0n: 690 MatT.Rows
691 case 0n 1n+q: 692 MatT.Cols<0n, q> 693 case 1n+p 1n+q: 694 MatT.Quad
695
696 def MatT.full(+r: Nat, +c: Nat, +x: F32) -> MatT(r, c):
697 match r c:
698 case 0n 0n:
699 MatB{Blk.full(x)}
700 case 1n+p 0n:
701 t b = MatT.full(p, 0n, x) MatT.full(p, 0n, x)
702 MatR{t, b}
703 case 0n 1n+q:
704 l r2 = MatT.full(0n, q, x) MatT.full(0n, q, x)
705 MatC{l, r2}
706 case 1n+p 1n+q:
707 a b c2 d = MatT.full(p, q, x) MatT.full(p, q, x) MatT.full(p, q, x) MatT.full(p, q, x)
708 MatQ{a, b, c2, d}
709
710 def MatT.zeros(+r: Nat, +c: Nat) -> MatT(r, c):
711 MatT.full(r, c, 0.0)
712
713 def MatT.ones(+r: Nat, +c: Nat) -> MatT(r, c):
714 MatT.full(r, c, 1.0)
715
716 # the identity of MatT(n, n)
717 def MatT.eye(+n: Nat) -> MatT(n, n):
718 match n:
719 case 0n:
720 MatB{Blk.eye()}
721 case 1n+p:
722 a b c d = MatT.eye(p) MatT.zeros(p, p) MatT.zeros(p, p) MatT.eye(p)
723 MatQ{a, b, c, d}
724
725 def MatT.map(~f: F32 -> F32, +r: Nat, +c: Nat, m: MatT(r, c)) -> MatT(r, c):
726 match r c:
727 case 0n 0n:
728 MatB{b} = m
729 MatB{Blk.map(~f, b)}
730 case 1n+p 0n:
731 MatR{t, b} = m
732 x y = MatT.map(~f, p, 0n, t) MatT.map(~f, p, 0n, b)
733 MatR{x, y}
734 case 0n 1n+q:
735 MatC{l, r2} = m
736 x y = MatT.map(~f, 0n, q, l) MatT.map(~f, 0n, q, r2)
737 MatC{x, y}
738 case 1n+p 1n+q:
739 MatQ{a, b, c2, d} = m
740 w x y z = MatT.map(~f, p, q, a) MatT.map(~f, p, q, b) MatT.map(~f, p, q, c2) MatT.map(~f, p, q, d)
741 MatQ{w, x, y, z}
742
743 def MatT.zip(~f: F32 -> F32 -> F32, +r: Nat, +c: Nat, m: MatT(r, c), n: MatT(r, c)) -> MatT(r, c):
744 match r c:
745 case 0n 0n:
746 MatB{x} = m
747 MatB{y} = n
748 MatB{Blk.zip(~f, x, y)}
749 case 1n+p 0n:
750 MatR{mt, mb} = m
751 MatR{nt, nb} = n
752 x y = MatT.zip(~f, p, 0n, mt, nt) MatT.zip(~f, p, 0n, mb, nb)
753 MatR{x, y}
754 case 0n 1n+q:
755 MatC{ml, mr} = m
756 MatC{nl, nr} = n
757 x y = MatT.zip(~f, 0n, q, ml, nl) MatT.zip(~f, 0n, q, mr, nr)
758 MatC{x, y}
759 case 1n+p 1n+q:
760 MatQ{ma, mb, mc, md} = m
761 MatQ{na, nb, nc, nd} = n
762 w x y z = MatT.zip(~f, p, q, ma, na) MatT.zip(~f, p, q, mb, nb) MatT.zip(~f, p, q, mc, nc) MatT.zip(~f, p, q, md, nd)
763 MatQ{w, x, y, z}
764
765 def MatT.add(+r: Nat, +c: Nat, m: MatT(r, c), n: MatT(r, c)) -> MatT(r, c):
766 MatT.zip(~F32.add, r, c, m, n)
767
768 def MatT.sub(+r: Nat, +c: Nat, m: MatT(r, c), n: MatT(r, c)) -> MatT(r, c):
769 MatT.zip(~F32.sub, r, c, m, n)
770
771 # elementwise (Hadamard) product; the matrix product is MatT.matmul
772 def MatT.mul(+r: Nat, +c: Nat, m: MatT(r, c), n: MatT(r, c)) -> MatT(r, c):
773 MatT.zip(~F32.mul, r, c, m, n)
774
775 def MatT.exp(+r: Nat, +c: Nat, m: MatT(r, c)) -> MatT(r, c):
776 MatT.map(~F32.exp, r, c, m)
777
778 def MatT.scale(+r: Nat, +c: Nat, +k: F32, m: MatT(r, c)) -> MatT(r, c):
779 match r c:
780 case 0n 0n:
781 MatB{b} = m
782 MatB{Blk.scale(k, b)}
783 case 1n+p 0n:
784 MatR{t, b} = m
785 x y = MatT.scale(p, 0n, k, t) MatT.scale(p, 0n, k, b)
786 MatR{x, y}
787 case 0n 1n+q:
788 MatC{l, r2} = m
789 x y = MatT.scale(0n, q, k, l) MatT.scale(0n, q, k, r2)
790 MatC{x, y}
791 case 1n+p 1n+q:
792 MatQ{a, b, c2, d} = m
793 w x y z = MatT.scale(p, q, k, a) MatT.scale(p, q, k, b) MatT.scale(p, q, k, c2) MatT.scale(p, q, k, d)
794 MatQ{w, x, y, z}
795
796 def MatT.transpose(+r: Nat, +c: Nat, m: MatT(r, c)) -> MatT(c, r):
797 match r c:
798 case 0n 0n:
799 MatB{b} = m
800 MatB{Blk.transpose(b)}
801 case 1n+p 0n:
802 MatR{t, b} = m
803 x y = MatT.transpose(p, 0n, t) MatT.transpose(p, 0n, b)
804 MatC{x, y}
805 case 0n 1n+q:
806 MatC{l, r2} = m
807 x y = MatT.transpose(0n, q, l) MatT.transpose(0n, q, r2)
808 MatR{x, y}
809 case 1n+p 1n+q:
810 MatQ{a, b, c2, d} = m
811 w x y z = MatT.transpose(p, q, a) MatT.transpose(p, q, c2) MatT.transpose(p, q, b) MatT.transpose(p, q, d)
812 MatQ{w, x, y, z}
813
814 # the four quadrant sums of a product node, in one balanced fork
815 def MatT.add4(+p: Nat, +q: Nat, a0: MatT(p, q), b0: MatT(p, q), a1: MatT(p, q), b1: MatT(p, q),
816 a2: MatT(p, q), b2: MatT(p, q), a3: MatT(p, q), b3: MatT(p, q)) -> MatT(1n+p, 1n+q):
817 w x y z = MatT.add(p, q, a0, b0) MatT.add(p, q, a1, b1) MatT.add(p, q, a2, b2) MatT.add(p, q, a3, b3)
818 MatQ{w, x, y, z}
819
820 def MatT.add2r(+p: Nat, a0: MatT(p, 0n), b0: MatT(p, 0n), a1: MatT(p, 0n), b1: MatT(p, 0n)) -> MatT(1n+p, 0n):
821 x y = MatT.add(p, 0n, a0, b0) MatT.add(p, 0n, a1, b1)
822 MatR{x, y}
823
824 def MatT.add2c(+q: Nat, a0: MatT(0n, q), b0: MatT(0n, q), a1: MatT(0n, q), b1: MatT(0n, q)) -> MatT(0n, 1n+q):
825 x y = MatT.add(0n, q, a0, b0) MatT.add(0n, q, a1, b1)
826 MatC{x, y}
827
828 # a * b for square operands of one depth: the same recursion with one
829 # depth riding instead of three, which measures 3x faster per node
830 def MatT.matmul.sq(+d: Nat, +a: MatT(d, d), +b: MatT(d, d)) -> MatT(d, d):
831 match d:
832 case 0n:
833 MatB{x} = a
834 MatB{y} = b
835 MatB{Blk.matmul(x, y)}
836 case 1n+p:
837 MatQ{a0, a1, a2, a3} = a
838 MatQ{b0, b1, b2, b3} = b
839 p0 q0 p1 q1 p2 q2 p3 q3 = MatT.matmul.sq(p, a0, b0) MatT.matmul.sq(p, a1, b2) MatT.matmul.sq(p, a0, b1) MatT.matmul.sq(p, a1, b3) MatT.matmul.sq(p, a2, b0) MatT.matmul.sq(p, a3, b2) MatT.matmul.sq(p, a2, b1) MatT.matmul.sq(p, a3, b3)
840 MatT.add4(p, p, p0, q0, p1, q1, p2, q2, p3, q3)
841
842 # a * b for any shapes: MatT(r, k) times MatT(k, c) is MatT(r, c). A node
843 # forks its whole burst of products at once (eight at a quadrant), then
844 # joins them with a fork of adds. Both operands are shared through +,
845 # never copied. Three depths ride every call, which is what the square
846 # path below avoids.
847 def MatT.matmul.any(+r: Nat, +k: Nat, +c: Nat, +a: MatT(r, k), +b: MatT(k, c)) -> MatT(r, c):
848 match r k c:
849 case 0n 0n 0n:
850 # the leaf goes through the square path on purpose: compiled
851 # without MatT.matmul.sq reachable, this recursion fails at runtime
852 # from depth 2 on (a bend 2.0.16 bug, see README); with it, it runs
853 MatT.matmul.sq(0n, a, b)
854 case 1n+p 0n 0n:
855 MatR{at, ab} = a
856 x y = MatT.matmul.any(p, 0n, 0n, at, b) MatT.matmul.any(p, 0n, 0n, ab, b)
857 MatR{x, y}
858 case 0n 0n 1n+s:
859 MatC{bl, br} = b
860 x y = MatT.matmul.any(0n, 0n, s, a, bl) MatT.matmul.any(0n, 0n, s, a, br)
861 MatC{x, y}
862 case 0n 1n+q 0n:
863 MatC{al, ar} = a
864 MatR{bt, bb} = b
865 x y = MatT.matmul.any(0n, q, 0n, al, bt) MatT.matmul.any(0n, q, 0n, ar, bb)
866 MatT.add(0n, 0n, x, y)
867 case 1n+p 1n+q 0n:
868 MatQ{a0, a1, a2, a3} = a
869 MatR{bt, bb} = b
870 p0 q0 p1 q1 = MatT.matmul.any(p, q, 0n, a0, bt) MatT.matmul.any(p, q, 0n, a1, bb) MatT.matmul.any(p, q, 0n, a2, bt) MatT.matmul.any(p, q, 0n, a3, bb)
871 MatT.add2r(p, p0, q0, p1, q1)
872 case 0n 1n+q 1n+s:
873 MatC{al, ar} = a
874 MatQ{b0, b1, b2, b3} = b
875 p0 q0 p1 q1 = MatT.matmul.any(0n, q, s, al, b0) MatT.matmul.any(0n, q, s, ar, b2) MatT.matmul.any(0n, q, s, al, b1) MatT.matmul.any(0n, q, s, ar, b3)
876 MatT.add2c(s, p0, q0, p1, q1)
877 case 1n+p 0n 1n+s:
878 MatR{at, ab} = a
879 MatC{bl, br} = b
880 w x y z = MatT.matmul.any(p, 0n, s, at, bl) MatT.matmul.any(p, 0n, s, at, br) MatT.matmul.any(p, 0n, s, ab, bl) MatT.matmul.any(p, 0n, s, ab, br)
881 MatQ{w, x, y, z}
882 case 1n+p 1n+q 1n+s:
883 MatQ{a0, a1, a2, a3} = a
884 MatQ{b0, b1, b2, b3} = b
885 p0 q0 p1 q1 p2 q2 p3 q3 = MatT.matmul.any(p, q, s, a0, b0) MatT.matmul.any(p, q, s, a1, b2) MatT.matmul.any(p, q, s, a0, b1) MatT.matmul.any(p, q, s, a1, b3) MatT.matmul.any(p, q, s, a2, b0) MatT.matmul.any(p, q, s, a3, b2) MatT.matmul.any(p, q, s, a2, b1) MatT.matmul.any(p, q, s, a3, b3)
886 MatT.add4(p, s, p0, q0, p1, q1, p2, q2, p3, q3)
887
888 # a * b: MatT(r, k) times MatT(k, c) is MatT(r, c). For two square
889 # operands of one depth, MatT.matmul.sq is the same recursion with one
890 # depth riding instead of three, and 3x faster per node; a runtime
891 # dispatch to it is not possible without a cast, and any def that returns
892 # or rebuilds a MatT parameter makes the compiler reference-count every
893 # MatT node in the program (see AGENTS.md), so the choice is the caller's.
894 def MatT.matmul(+r: Nat, +k: Nat, +c: Nat, +a: MatT(r, k), +b: MatT(k, c)) -> MatT(r, c):
895 MatT.matmul.any(r, k, c, a, b)
896
897 def VecT.add2(+p: Nat, a0: VecT(p), b0: VecT(p), a1: VecT(p), b1: VecT(p)) -> VecT(1n+p):
898 x y = VecT.add(p, a0, b0) VecT.add(p, a1, b1)
899 VecN{x, y}
900
901 # m * v: MatT(r, c) times VecT(c) is VecT(r)
902 def MatT.matvec(+r: Nat, +c: Nat, +m: MatT(r, c), +v: VecT(c)) -> VecT(r):
903 match r c:
904 case 0n 0n:
905 MatB{b} = m
906 VecL{x} = v
907 VecL{Blk.matvec(b, x)}
908 case 1n+p 0n:
909 MatR{t, b} = m
910 x y = MatT.matvec(p, 0n, t, v) MatT.matvec(p, 0n, b, v)
911 VecN{x, y}
912 case 0n 1n+q:
913 MatC{l, r2} = m
914 VecN{vl, vr} = v
915 x y = MatT.matvec(0n, q, l, vl) MatT.matvec(0n, q, r2, vr)
916 VecT.add(0n, x, y)
917 case 1n+p 1n+q:
918 MatQ{a, b, c2, d} = m
919 VecN{vl, vr} = v
920 p0 q0 p1 q1 = MatT.matvec(p, q, a, vl) MatT.matvec(p, q, b, vr) MatT.matvec(p, q, c2, vl) MatT.matvec(p, q, d, vr)
921 VecT.add2(p, p0, q0, p1, q1)
922
923 # the sum of every cell
924 def MatT.sum(+r: Nat, +c: Nat, m: MatT(r, c)) -> F32:
925 match r c:
926 case 0n 0n:
927 MatB{b} = m
928 Blk.sum(b)
929 case 1n+p 0n:
930 MatR{t, b} = m
931 x y = MatT.sum(p, 0n, t) MatT.sum(p, 0n, b)
932 (x + y : F32)
933 case 0n 1n+q:
934 MatC{l, r2} = m
935 x y = MatT.sum(0n, q, l) MatT.sum(0n, q, r2)
936 (x + y : F32)
937 case 1n+p 1n+q:
938 MatQ{a, b, c2, d} = m
939 w x y z = MatT.sum(p, q, a) MatT.sum(p, q, b) MatT.sum(p, q, c2) MatT.sum(p, q, d)
940 ((w + x) + (y + z) : F32)
941
942 # each row's sum: VecT(r)
943 def MatT.sum_rows(+r: Nat, +c: Nat, m: MatT(r, c)) -> VecT(r):
944 match r c:
945 case 0n 0n:
946 MatB{b} = m
947 VecL{Blk.sum_rows(b)}
948 case 1n+p 0n:
949 MatR{t, b} = m
950 x y = MatT.sum_rows(p, 0n, t) MatT.sum_rows(p, 0n, b)
951 VecN{x, y}
952 case 0n 1n+q:
953 MatC{l, r2} = m
954 x y = MatT.sum_rows(0n, q, l) MatT.sum_rows(0n, q, r2)
955 VecT.add(0n, x, y)
956 case 1n+p 1n+q:
957 MatQ{a, b, c2, d} = m
958 p0 q0 p1 q1 = MatT.sum_rows(p, q, a) MatT.sum_rows(p, q, b) MatT.sum_rows(p, q, c2) MatT.sum_rows(p, q, d)
959 VecT.add2(p, p0, q0, p1, q1)
960
961 # each column's sum: VecT(c)
962 def MatT.sum_cols(+r: Nat, +c: Nat, m: MatT(r, c)) -> VecT(c):
963 match r c:
964 case 0n 0n:
965 MatB{b} = m
966 VecL{Blk.sum_cols(b)}
967 case 1n+p 0n:
968 MatR{t, b} = m
969 x y = MatT.sum_cols(p, 0n, t) MatT.sum_cols(p, 0n, b)
970 VecT.add(0n, x, y)
971 case 0n 1n+q:
972 MatC{l, r2} = m
973 x y = MatT.sum_cols(0n, q, l) MatT.sum_cols(0n, q, r2)
974 VecN{x, y}
975 case 1n+p 1n+q:
976 MatQ{a, b, c2, d} = m
977 p0 q0 p1 q1 = MatT.sum_cols(p, q, a) MatT.sum_cols(p, q, c2) MatT.sum_cols(p, q, b) MatT.sum_cols(p, q, d)
978 VecT.add2(q, p0, q0, p1, q1)
979
980 def VecT.max2(+p: Nat, a0: VecT(p), b0: VecT(p), a1: VecT(p), b1: VecT(p)) -> VecT(1n+p):
981 x y = VecT.zip(~F32.max.of, p, a0, b0) VecT.zip(~F32.max.of, p, a1, b1)
982 VecN{x, y}
983
984 # each row's largest element: VecT(r)
985 def MatT.max_rows(+r: Nat, +c: Nat, m: MatT(r, c)) -> VecT(r):
986 match r c:
987 case 0n 0n:
988 MatB{b} = m
989 VecL{Blk.max_rows(b)}
990 case 1n+p 0n:
991 MatR{t, b} = m
992 x y = MatT.max_rows(p, 0n, t) MatT.max_rows(p, 0n, b)
993 VecN{x, y}
994 case 0n 1n+q:
995 MatC{l, r2} = m
996 x y = MatT.max_rows(0n, q, l) MatT.max_rows(0n, q, r2)
997 VecT.zip(~F32.max.of, 0n, x, y)
998 case 1n+p 1n+q:
999 MatQ{a, b, c2, d} = m
1000 p0 q0 p1 q1 = MatT.max_rows(p, q, a) MatT.max_rows(p, q, b) MatT.max_rows(p, q, c2) MatT.max_rows(p, q, d)
1001 VecT.max2(p, p0, q0, p1, q1)
1002
1003 # v added to every row: v is a VecT(c)
1004 def MatT.add_row(+r: Nat, +c: Nat, m: MatT(r, c), +v: VecT(c)) -> MatT(r, c):
1005 match r c:
1006 case 0n 0n:
1007 MatB{b} = m
1008 VecL{x} = v
1009 MatB{Blk.add_row(b, x)}
1010 case 1n+p 0n:
1011 MatR{t, b} = m
1012 x y = MatT.add_row(p, 0n, t, v) MatT.add_row(p, 0n, b, v)
1013 MatR{x, y}
1014 case 0n 1n+q:
1015 MatC{l, r2} = m
1016 VecN{vl, vr} = v
1017 x y = MatT.add_row(0n, q, l, vl) MatT.add_row(0n, q, r2, vr)
1018 MatC{x, y}
1019 case 1n+p 1n+q:
1020 MatQ{a, b, c2, d} = m
1021 VecN{vl, vr} = v
1022 w x y z = MatT.add_row(p, q, a, vl) MatT.add_row(p, q, b, vr) MatT.add_row(p, q, c2, vl) MatT.add_row(p, q, d, vr)
1023 MatQ{w, x, y, z}
1024
1025 # row i minus v_i: v is a VecT(r), one scalar per row
1026 def MatT.sub_col(+r: Nat, +c: Nat, m: MatT(r, c), +v: VecT(r)) -> MatT(r, c):
1027 match r c:
1028 case 0n 0n:
1029 MatB{b} = m
1030 VecL{x} = v
1031 MatB{Blk.sub_col(b, x)}
1032 case 1n+p 0n:
1033 MatR{t, b} = m
1034 VecN{vt, vb} = v
1035 x y = MatT.sub_col(p, 0n, t, vt) MatT.sub_col(p, 0n, b, vb)
1036 MatR{x, y}
1037 case 0n 1n+q:
1038 MatC{l, r2} = m
1039 x y = MatT.sub_col(0n, q, l, v) MatT.sub_col(0n, q, r2, v)
1040 MatC{x, y}
1041 case 1n+p 1n+q:
1042 MatQ{a, b, c2, d} = m
1043 VecN{vt, vb} = v
1044 w x y z = MatT.sub_col(p, q, a, vt) MatT.sub_col(p, q, b, vt) MatT.sub_col(p, q, c2, vb) MatT.sub_col(p, q, d, vb)
1045 MatQ{w, x, y, z}
1046
1047 # row i times v_i: v is a VecT(r), one scalar per row
1048 def MatT.scale_rows(+r: Nat, +c: Nat, m: MatT(r, c), +v: VecT(r)) -> MatT(r, c):
1049 match r c:
1050 case 0n 0n:
1051 MatB{b} = m
1052 VecL{x} = v
1053 MatB{Blk.scale_rows(b, x)}
1054 case 1n+p 0n:
1055 MatR{t, b} = m
1056 VecN{vt, vb} = v
1057 x y = MatT.scale_rows(p, 0n, t, vt) MatT.scale_rows(p, 0n, b, vb)
1058 MatR{x, y}
1059 case 0n 1n+q:
1060 MatC{l, r2} = m
1061 x y = MatT.scale_rows(0n, q, l, v) MatT.scale_rows(0n, q, r2, v)
1062 MatC{x, y}
1063 case 1n+p 1n+q:
1064 MatQ{a, b, c2, d} = m
1065 VecN{vt, vb} = v
1066 w x y z = MatT.scale_rows(p, q, a, vt) MatT.scale_rows(p, q, b, vt) MatT.scale_rows(p, q, c2, vb) MatT.scale_rows(p, q, d, vb)
1067 MatQ{w, x, y, z}
1068
1069 def MatT.softmax_rows.fin(+r: Nat, +c: Nat, +e: MatT(r, c)) -> MatT(r, c):
1070 MatT.scale_rows(r, c, e, VecT.map(~(s => F32.div(1.0, s)), r, MatT.sum_rows(r, c, e)))
1071
1072 # softmax along each row: exp(row - max row) / sum
1073 def MatT.softmax_rows(+r: Nat, +c: Nat, +m: MatT(r, c)) -> MatT(r, c):
1074 MatT.softmax_rows.fin(r, c, MatT.exp(r, c, MatT.sub_col(r, c, m, MatT.max_rows(r, c, m))))
1075
1076 # Conversions
1077 # -----------
1078
1079 # the first w of every row
1080 def MatT.rows_take(+w: Nat, rows: List<&2, List<&2, F32>>) -> List<&2, List<&2, F32>>:
1081 match rows:
1082 case []:
1083 []
1084 case row <> rest:
1085 List.take(&2, F32, row, w) <> MatT.rows_take(w, rest)
1086
1087 # every row past its first w
1088 def MatT.rows_drop(+w: Nat, rows: List<&2, List<&2, F32>>) -> List<&2, List<&2, F32>>:
1089 match rows:
1090 case []:
1091 []
1092 case row <> rest:
1093 List.drop(&2, F32, row, w) <> MatT.rows_drop(w, rest)
1094
1095 # the rows of xs side by side with the rows of ys
1096 def MatT.rows_cat(xs: List<&2, List<&2, F32>>, ys: List<&2, List<&2, F32>>) -> List<&2, List<&2, F32>>:
1097 match xs ys:
1098 case x <> xt y <> yt:
1099 List.append(&2, F32, x, y) <> MatT.rows_cat(xt, yt)
1100 case _ _:
1101 []
1102
1103 # a leaf from up to four rows; missing rows and cells pad with 0.0
1104 def MatT.from_rows.leaf(rows: List<&2, List<&2, F32>>) -> Blk:
1105 match rows:
1106 case r0 <> r1 <> r2 <> r3 <> rest:
1107 Blk.from_rows(r0, r1, r2, r3)
1108 case r0 <> r1 <> r2 <> []:
1109 Blk.from_rows(r0, r1, r2, [])
1110 case r0 <> r1 <> []:
1111 Blk.from_rows(r0, r1, [], [])
1112 case r0 <> []:
1113 Blk.from_rows(r0, [], [], [])
1114 case []:
1115 Blk.zeros()
1116
1117 # a MatT(r, c) from its rows, row-major; short or missing rows pad with 0.0
1118 def MatT.from_rows(+r: Nat, +c: Nat, +rows: List<&2, List<&2, F32>>) -> MatT(r, c):
1119 match r c:
1120 case 0n 0n:
1121 MatB{MatT.from_rows.leaf(rows)}
1122 case 1n+p 0n:
1123 +h = VecT.size(p)
1124 t b = MatT.from_rows(p, 0n, List.take(&2, List<&2, F32>, rows, h)) MatT.from_rows(p, 0n, List.drop(&2, List<&2, F32>, rows, h))
1125 MatR{t, b}
1126 case 0n 1n+q:
1127 +w = VecT.size(q)
1128 l r2 = MatT.from_rows(0n, q, MatT.rows_take(w, rows)) MatT.from_rows(0n, q, MatT.rows_drop(w, rows))
1129 MatC{l, r2}
1130 case 1n+p 1n+q:
1131 +h = VecT.size(p)
1132 +w = VecT.size(q)
1133 +top = List.take(&2, List<&2, F32>, rows, h)
1134 +bot = List.drop(&2, List<&2, F32>, rows, h)
1135 a b c2 d = MatT.from_rows(p, q, MatT.rows_take(w, top)) MatT.from_rows(p, q, MatT.rows_drop(w, top)) MatT.from_rows(p, q, MatT.rows_take(w, bot)) MatT.from_rows(p, q, MatT.rows_drop(w, bot))
1136 MatQ{a, b, c2, d}
1137
1138 # the rows of a MatT(r, c), row-major
1139 def MatT.to_rows(+r: Nat, +c: Nat, m: MatT(r, c)) -> List<&2, List<&2, F32>>:
1140 match r c:
1141 case 0n 0n:
1142 MatB{b} = m
1143 Blk{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33} = b
1144 [[a00, a01, a02, a03], [a10, a11, a12, a13], [a20, a21, a22, a23], [a30, a31, a32, a33]]
1145 case 1n+p 0n:
1146 MatR{t, b} = m
1147 List.append(&2, List<&2, F32>, MatT.to_rows(p, 0n, t), MatT.to_rows(p, 0n, b))
1148 case 0n 1n+q:
1149 MatC{l, r2} = m
1150 MatT.rows_cat(MatT.to_rows(0n, q, l), MatT.to_rows(0n, q, r2))
1151 case 1n+p 1n+q:
1152 MatQ{a, b, c2, d} = m
1153 List.append(&2, List<&2, F32>, MatT.rows_cat(MatT.to_rows(p, q, a), MatT.to_rows(p, q, b)), MatT.rows_cat(MatT.to_rows(p, q, c2), MatT.to_rows(p, q, d)))
1154
1155 # xs cut into n rows of w
1156 def MatT.chunks(+n: Nat, +w: Nat, +xs: List<&2, F32>) -> List<&2, List<&2, F32>>:
1157 match n:
1158 case 0n:
1159 []
1160 case 1n+p:
1161 List.take(&2, F32, xs, w) <> MatT.chunks(p, w, List.drop(&2, F32, xs, w))
1162
1163 # a MatT(r, c) from 4 * 2^r times 4 * 2^c floats in row-major order
1164 def MatT.from_list(+r: Nat, +c: Nat, xs: List<&2, F32>) -> MatT(r, c):
1165 +r2 = r
1166 MatT.from_rows(r2, c, MatT.chunks(VecT.size(r2), VecT.size(c), xs))
1167
1168 # the cells in row-major order
1169 def MatT.to_list(+r: Nat, +c: Nat, m: MatT(r, c)) -> List<&2, F32>:
1170 List.concat(&2, F32, MatT.to_rows(r, c, m))
1171
1172 def MatT.show.row(row: List<&2, F32>) -> String:
1173 List.show(~&2, ~F32, ~F32.show.of, row)
1174
1175 def MatT.show(+r: Nat, +c: Nat, m: MatT(r, c)) -> String:
1176 List.show(~&2, ~List<&2, F32>, ~MatT.show.row, MatT.to_rows(r, c, m))
1177
1178 # Sizes
1179 # =====
1180 #
1181 # The public types. A Vec