esp32: 2.4x faster on the ESP32-S3 (quick-start 2.52 s -> 1.06 s on a Cardputer)
Browse filesMeasured on an M5Stack Cardputer: 67 tokens 2514 -> 1056 ms, 126 tokens 5687 -> 2281 ms, 12 tokens 528 -> 245 ms; same heap. Bit-identical: 4-token PIE kernel with the tile loop in assembly, attention loop restructuring, lrintf-free rounding, LayerNorm/quantisation on both cores, 64-byte-aligned embedded model. Within float rounding (outputs move <= 0.0035): fc2 in 256-neuron groups, Cephes exp in the attention softmax, heads read each matrix once (u = W^T q). Host conformance builds with -ffp-contract=fast like the device GCC: max|dp| 0.0163, no pick or span flips.
- README.md +2 -2
- esp32/README.md +1 -1
- esp32/host/conformance.mjs +3 -2
- esp32/tinydecide/src/dot_q4_pie.S +139 -0
- esp32/tinydecide/src/engine.cpp +145 -37
- esp32/tinydecide/src/model_data.S +5 -3
- esp32/tinydecide/src/tinydecide.h +3 -0
README.md
CHANGED
|
@@ -178,7 +178,7 @@ const char* msg = "Book a table for 4 at an Italian place near the station on Fr
|
|
| 178 |
td::answer(msg, qs, 2, a); // a[0].pick == 3, msg[a[1].start, a[1].end) == "7:30"
|
| 179 |
```
|
| 180 |
|
| 181 |
-
On the ESP32-S3, the PIE vector unit runs the 4-bit matmuls on both cores. On an M5Stack Cardputer the quick-start request takes
|
| 182 |
|
| 183 |
## Files
|
| 184 |
|
|
@@ -228,7 +228,7 @@ Calibration error (ECE) is 0.02 to 0.15 on the tiers with true labels.
|
|
| 228 |
- English only.
|
| 229 |
- Every engine reads the first 127 tokens of a message and sets `truncated` when it cuts.
|
| 230 |
- It answers best on short, everyday messages. Questions that need two-step reasoning, such as natural language inference, are close to chance.
|
| 231 |
-
- On a Cardputer the quick-start request takes
|
| 232 |
- The Decision Index numbers are self-scored with the kit. The board's maintainers have not verified them.
|
| 233 |
- The 13.8M build's 6.31 is not a clean out-of-sample number. It had two board runs, and one data change came after the first run. The 10.4M build's 5.77 came from a single run.
|
| 234 |
|
|
|
|
| 178 |
td::answer(msg, qs, 2, a); // a[0].pick == 3, msg[a[1].start, a[1].end) == "7:30"
|
| 179 |
```
|
| 180 |
|
| 181 |
+
On the ESP32-S3, the PIE vector unit runs the 4-bit matmuls on both cores. On an M5Stack Cardputer the quick-start request takes 1.1 s. It has four questions and 67 tokens, and the answers match the table above. That timing comes from the ESP-IDF project in `esp32/examples/basic`. The Arduino sketch in `esp32/tinydecide/examples/Basic` builds, but it has not run on a device yet. Each folder's README has the details.
|
| 182 |
|
| 183 |
## Files
|
| 184 |
|
|
|
|
| 228 |
- English only.
|
| 229 |
- Every engine reads the first 127 tokens of a message and sets `truncated` when it cuts.
|
| 230 |
- It answers best on short, everyday messages. Questions that need two-step reasoning, such as natural language inference, are close to chance.
|
| 231 |
+
- On a Cardputer the quick-start request takes 1.1 s on both cores. [Pocket Notes](https://github.com/therezor/pocket-notes) asks one choice question about a short note and takes about 1.8 s with its own, older kernels. A request needs about 2.2 KB of heap per token. The bare ESP32 example has 371 KB free, enough for about 150 tokens. An app with a display and Wi-Fi has less.
|
| 232 |
- The Decision Index numbers are self-scored with the kit. The board's maintainers have not verified them.
|
| 233 |
- The 13.8M build's 6.31 is not a clean out-of-sample number. It had two board runs, and one data change came after the first run. The 10.4M build's 5.77 came from a single run.
|
| 234 |
|
esp32/README.md
CHANGED
|
@@ -17,7 +17,7 @@ the conformance test below runs it.
|
|
| 17 |
| host build, 84 requests | same token ids, errors, picks and spans; largest probability difference about 0.02 |
|
| 18 |
| ESP-IDF 5.5 build for ESP32-S3 with the model embedded (pioarduino 55.03.312) | compiles; the app image is 6.6 MB |
|
| 19 |
| Arduino core 3.x build for ESP32-S3 with the model in a partition (same platform) | compiles; the app is 329 KB |
|
| 20 |
-
| `examples/basic` on an M5Stack Cardputer (ESP32-S3, 8 MB flash, no PSRAM) | the four answers match the reference (restaurants 0.98 vs 0.97, 0.22, 0.52, "7:30");
|
| 21 |
| the Arduino sketch on a device | not run |
|
| 22 |
|
| 23 |
The probabilities differ by up to about 0.02 because the PIE kernel needs activations rounded to
|
|
|
|
| 17 |
| host build, 84 requests | same token ids, errors, picks and spans; largest probability difference about 0.02 |
|
| 18 |
| ESP-IDF 5.5 build for ESP32-S3 with the model embedded (pioarduino 55.03.312) | compiles; the app image is 6.6 MB |
|
| 19 |
| Arduino core 3.x build for ESP32-S3 with the model in a partition (same platform) | compiles; the app is 329 KB |
|
| 20 |
+
| `examples/basic` on an M5Stack Cardputer (ESP32-S3, 8 MB flash, no PSRAM) | the four answers match the reference (restaurants 0.98 vs 0.97, 0.22, 0.52, "7:30"); 1.06 s per request; 371 KB heap free |
|
| 21 |
| the Arduino sketch on a device | not run |
|
| 22 |
|
| 23 |
The probabilities differ by up to about 0.02 because the PIE kernel needs activations rounded to
|
esp32/host/conformance.mjs
CHANGED
|
@@ -21,8 +21,9 @@ const exp = JSON.parse(readFileSync(conf + "expected.S768.json", "utf8"));
|
|
| 21 |
|
| 22 |
const tmp = mkdtempSync(tmpdir() + "/td_host_"), bin = tmp + "/td_host";
|
| 23 |
process.on("exit", () => rmSync(tmp, { recursive: true, force: true }));
|
| 24 |
-
//
|
| 25 |
-
|
|
|
|
| 26 |
here + "host/td_host.cpp", here + "tinydecide/src/engine.cpp", "-o", bin], { stdio: "inherit" });
|
| 27 |
|
| 28 |
const hex = (s) => (s === "" ? "-" : Buffer.from(s, "utf8").toString("hex"));
|
|
|
|
| 21 |
|
| 22 |
const tmp = mkdtempSync(tmpdir() + "/td_host_"), bin = tmp + "/td_host";
|
| 23 |
process.on("exit", () => rmSync(tmp, { recursive: true, force: true }));
|
| 24 |
+
// -ffp-contract=fast: fuse a*b+c as the device GCC does, so results do not depend on how the host
|
| 25 |
+
// compiler happens to contract each loop. TD_CXXFLAGS adds flags, e.g. TD_CXXFLAGS="-fsanitize=address,undefined -g"
|
| 26 |
+
execFileSync("c++", ["-O2", "-std=c++17", "-ffp-contract=fast", "-Wall", "-Wextra", "-DTD_TEST", ...(process.env.TD_CXXFLAGS || "").split(" ").filter(Boolean), "-I" + here + "tinydecide/src",
|
| 27 |
here + "host/td_host.cpp", here + "tinydecide/src/engine.cpp", "-o", bin], { stdio: "inherit" });
|
| 28 |
|
| 29 |
const hex = (s) => (s === "" ? "-" : Buffer.from(s, "utf8").toString("hex"));
|
esp32/tinydecide/src/dot_q4_pie.S
CHANGED
|
@@ -71,4 +71,143 @@ dot_q4q8_pie:
|
|
| 71 |
rfr a2, f0
|
| 72 |
retw.n
|
| 73 |
.size dot_q4q8_pie, . - dot_q4q8_pie
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
#endif // CONFIG_IDF_TARGET_ESP32S3
|
|
|
|
| 71 |
rfr a2, f0
|
| 72 |
retw.n
|
| 73 |
.size dot_q4q8_pie, . - dot_q4q8_pie
|
| 74 |
+
|
| 75 |
+
// The same dot product for tiles of four tokens: each weight block is unpacked once and applied to
|
| 76 |
+
// all four, and the loop over tiles runs here rather than in C.
|
| 77 |
+
//
|
| 78 |
+
// void dot4_q4q8_pie(const uint8_t* nib, // a2: 16B-aligned nibble plane
|
| 79 |
+
// const uint8_t* scales, // a3: bf16 weight scales
|
| 80 |
+
// const int8_t* xq, // a4: token 0's int8 activations; 16B-aligned
|
| 81 |
+
// const float* xs, // a5: token 0's block scales
|
| 82 |
+
// int nb, // a6: number of 32-weight blocks
|
| 83 |
+
// int xqs, // a7: bytes from one token's xq to the next;
|
| 84 |
+
// // its xs are xqs / 32 floats further on
|
| 85 |
+
// float* out, // stack: 4 * ntile sums, token order
|
| 86 |
+
// int ntile); // stack: tiles of four tokens, >= 1
|
| 87 |
+
//
|
| 88 |
+
// Each token's sum is accumulated exactly as dot_q4q8_pie does it, so the results are identical.
|
| 89 |
+
//
|
| 90 |
+
// Frame: a1+0 tiles left, +4 nib, +8 scales, +12 nb, +16 xs stride (bytes), +20 xq of the tile,
|
| 91 |
+
// +24 xs of the tile, +28 xqs.
|
| 92 |
+
|
| 93 |
+
.text
|
| 94 |
+
.align 4
|
| 95 |
+
.global dot4_q4q8_pie
|
| 96 |
+
.type dot4_q4q8_pie, @function
|
| 97 |
+
dot4_q4q8_pie:
|
| 98 |
+
entry a1, 48
|
| 99 |
+
l32i a15, a1, 48 // out (7th argument, on the caller's stack)
|
| 100 |
+
l32i a8, a1, 52 // ntile (8th)
|
| 101 |
+
s32i a8, a1, 0
|
| 102 |
+
s32i a2, a1, 4
|
| 103 |
+
s32i a3, a1, 8
|
| 104 |
+
s32i a6, a1, 12
|
| 105 |
+
srai a8, a7, 3 // xs stride in bytes: xqs / 32 floats
|
| 106 |
+
s32i a8, a1, 16
|
| 107 |
+
s32i a4, a1, 20
|
| 108 |
+
s32i a5, a1, 24
|
| 109 |
+
s32i a7, a1, 28
|
| 110 |
+
movi a8, .Lconst
|
| 111 |
+
ee.vldbc.8 q7, a8 // q7 = 0x0F splat
|
| 112 |
+
addi a8, a8, 1
|
| 113 |
+
ee.vldbc.8 q6, a8 // q6 = 8 splat
|
| 114 |
+
ssai 4
|
| 115 |
+
.Ltile:
|
| 116 |
+
l32i a4, a1, 20 // this tile's token pointers
|
| 117 |
+
l32i a7, a1, 28
|
| 118 |
+
add a9, a4, a7
|
| 119 |
+
add a10, a9, a7
|
| 120 |
+
add a11, a10, a7
|
| 121 |
+
l32i a5, a1, 24
|
| 122 |
+
l32i a8, a1, 16
|
| 123 |
+
add a12, a5, a8
|
| 124 |
+
add a13, a12, a8
|
| 125 |
+
add a14, a13, a8
|
| 126 |
+
l32i a2, a1, 4 // back to the start of the weight row
|
| 127 |
+
l32i a3, a1, 8
|
| 128 |
+
l32i a6, a1, 12
|
| 129 |
+
movi a8, 0
|
| 130 |
+
wfr f0, a8
|
| 131 |
+
wfr f1, a8
|
| 132 |
+
wfr f2, a8
|
| 133 |
+
wfr f3, a8
|
| 134 |
+
// Per block: unpack the weights once, then token k's MACs overlap token k-1's scalar tail.
|
| 135 |
+
// Token k's sum is still acc_k += (float)isum_k * (wscale * xs_k[b]), as in dot_q4q8_pie.
|
| 136 |
+
loopnez a6, .Lloop4_end
|
| 137 |
+
ee.vld.128.ip q0, a2, 16 // 16 packed nibble bytes
|
| 138 |
+
l16ui a8, a3, 0 // bf16 weight scale bits
|
| 139 |
+
lsip f5, a5, 4 // xs[b] of tokens 0..3
|
| 140 |
+
lsip f6, a12, 4
|
| 141 |
+
lsip f7, a13, 4
|
| 142 |
+
lsip f8, a14, 4
|
| 143 |
+
ee.vsr.32 q1, q0
|
| 144 |
+
ee.andq q0, q0, q7 // low nibbles -> q[0..15]
|
| 145 |
+
slli a8, a8, 16
|
| 146 |
+
ee.andq q1, q1, q7 // high nibbles -> q[16..31]
|
| 147 |
+
wfr f4, a8 // wscale
|
| 148 |
+
ee.vsubs.s8 q0, q0, q6
|
| 149 |
+
ee.vsubs.s8 q1, q1, q6
|
| 150 |
+
mul.s f5, f4, f5 // wscale * xscale, per token
|
| 151 |
+
mul.s f6, f4, f6
|
| 152 |
+
mul.s f7, f4, f7
|
| 153 |
+
mul.s f8, f4, f8
|
| 154 |
+
addi a3, a3, 2
|
| 155 |
+
// token 0
|
| 156 |
+
ee.vld.128.ip q2, a4, 16
|
| 157 |
+
ee.vld.128.ip q3, a4, 16
|
| 158 |
+
ee.zero.accx
|
| 159 |
+
ee.vmulas.s8.accx q0, q2
|
| 160 |
+
ee.vld.128.ip q4, a9, 16 // token 1's activations
|
| 161 |
+
ee.vmulas.s8.accx q1, q3
|
| 162 |
+
ee.vld.128.ip q5, a9, 16
|
| 163 |
+
rur.accx_0 a7
|
| 164 |
+
// token 1
|
| 165 |
+
ee.zero.accx
|
| 166 |
+
ee.vmulas.s8.accx q0, q4
|
| 167 |
+
ee.vld.128.ip q2, a10, 16 // token 2's
|
| 168 |
+
ee.vmulas.s8.accx q1, q5
|
| 169 |
+
ee.vld.128.ip q3, a10, 16
|
| 170 |
+
float.s f9, a7, 0
|
| 171 |
+
rur.accx_0 a8
|
| 172 |
+
// token 2
|
| 173 |
+
ee.zero.accx
|
| 174 |
+
ee.vmulas.s8.accx q0, q2
|
| 175 |
+
ee.vld.128.ip q4, a11, 16 // token 3's
|
| 176 |
+
ee.vmulas.s8.accx q1, q3
|
| 177 |
+
ee.vld.128.ip q5, a11, 16
|
| 178 |
+
madd.s f0, f9, f5
|
| 179 |
+
float.s f10, a8, 0
|
| 180 |
+
rur.accx_0 a7
|
| 181 |
+
// token 3
|
| 182 |
+
ee.zero.accx
|
| 183 |
+
ee.vmulas.s8.accx q0, q4
|
| 184 |
+
ee.vmulas.s8.accx q1, q5
|
| 185 |
+
madd.s f1, f10, f6
|
| 186 |
+
float.s f11, a7, 0
|
| 187 |
+
rur.accx_0 a8
|
| 188 |
+
madd.s f2, f11, f7
|
| 189 |
+
float.s f12, a8, 0
|
| 190 |
+
madd.s f3, f12, f8
|
| 191 |
+
.Lloop4_end:
|
| 192 |
+
ssi f0, a15, 0
|
| 193 |
+
ssi f1, a15, 4
|
| 194 |
+
ssi f2, a15, 8
|
| 195 |
+
ssi f3, a15, 12
|
| 196 |
+
addi a15, a15, 16
|
| 197 |
+
l32i a7, a1, 28 // next tile: four tokens on
|
| 198 |
+
slli a7, a7, 2
|
| 199 |
+
l32i a8, a1, 20
|
| 200 |
+
add a8, a8, a7
|
| 201 |
+
s32i a8, a1, 20
|
| 202 |
+
l32i a7, a1, 16
|
| 203 |
+
slli a7, a7, 2
|
| 204 |
+
l32i a8, a1, 24
|
| 205 |
+
add a8, a8, a7
|
| 206 |
+
s32i a8, a1, 24
|
| 207 |
+
l32i a8, a1, 0
|
| 208 |
+
addi a8, a8, -1
|
| 209 |
+
s32i a8, a1, 0
|
| 210 |
+
bnez a8, .Ltile
|
| 211 |
+
retw.n
|
| 212 |
+
.size dot4_q4q8_pie, . - dot4_q4q8_pie
|
| 213 |
#endif // CONFIG_IDF_TARGET_ESP32S3
|
esp32/tinydecide/src/engine.cpp
CHANGED
|
@@ -2,9 +2,11 @@
|
|
| 2 |
//
|
| 3 |
// Memory plan ("streamed kernel"): one scratch area per call, ~2.2 KB per token. Each layer keeps
|
| 4 |
// the fp32 residual x plus an int8 copy of the layer input; attention runs one head at a time with
|
| 5 |
-
// W_o accumulated straight into x, and the FFN runs in 64-neuron chunks
|
| 6 |
-
//
|
| 7 |
-
//
|
|
|
|
|
|
|
| 8 |
#include "tinydecide.h"
|
| 9 |
#include "td_meta.h"
|
| 10 |
#include "td_unicode.h"
|
|
@@ -276,14 +278,25 @@ static inline float dotq(const uint8_t* nib, const int8_t* xq, const uint8_t* sc
|
|
| 276 |
#endif
|
| 277 |
}
|
| 278 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
// Symmetric int8 per 32-element block, one fp32 scale each (llama.cpp Q8_0).
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
static void quant8(const float* x, int8_t* xq, float* xs, int n) {
|
|
|
|
|
|
|
|
|
|
| 281 |
for (int b = 0; b < n / 32; b++) {
|
| 282 |
float mx = 0.0f;
|
| 283 |
for (int i = 0; i < 32; i++) { float a = fabsf(x[i]); if (a > mx) mx = a; }
|
| 284 |
float s = mx / 127.0f, inv = s > 0 ? 1.0f / s : 0.0f;
|
| 285 |
xs[b] = s;
|
| 286 |
-
for (int i = 0; i < 32; i++) xq[i] = (int8_t)
|
| 287 |
x += 32; xq += 32;
|
| 288 |
}
|
| 289 |
}
|
|
@@ -308,7 +321,22 @@ static void mmRows(const MM& a, int ra, int rb) {
|
|
| 308 |
const uint8_t* sc = g_m + a.w->sc + ((size_t)r * nbRow + a.blk0) * 2;
|
| 309 |
const float b = a.bias ? a.bias[r] : 0.0f;
|
| 310 |
float* o = a.out + (r - a.r0);
|
| 311 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
float v = dotq(nib, a.xq + (size_t)t * a.xqs, sc, a.xs + (size_t)t * a.xss, a.nbk) + b;
|
| 313 |
if (a.acc) o[(size_t)t * a.os] += v; else o[(size_t)t * a.os] = v;
|
| 314 |
}
|
|
@@ -415,6 +443,38 @@ static void mvI8(const tdm::I8& w, const float* v, float* y) {
|
|
| 415 |
}
|
| 416 |
}
|
| 417 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
// ============================================================================
|
| 419 |
// Request layout (encodeRequest in tinydecide.js)
|
| 420 |
// ============================================================================
|
|
@@ -516,15 +576,28 @@ static bool allocScratch(Scratch& m, int T) {
|
|
| 516 |
return false;
|
| 517 |
}
|
| 518 |
|
| 519 |
-
// GELU over one FFN chunk for tokens [a, b), then int8
|
| 520 |
-
|
|
|
|
| 521 |
static void geluRange(void* ctx, int a, int b) {
|
| 522 |
const GeluCtx& g = *(const GeluCtx*)ctx;
|
| 523 |
const int DHd = tdm::DHEAD;
|
| 524 |
for (int t = a; t < b; t++) {
|
| 525 |
float* hrow = g.Hc + (size_t)t * DHd;
|
| 526 |
for (int c = 0; c < DHd; c++) hrow[c] = gelu(hrow[c]);
|
| 527 |
-
quant8(hrow, g.hq + (size_t)t *
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
}
|
| 529 |
}
|
| 530 |
|
|
@@ -551,8 +624,24 @@ static void attnRange(void* ctx, int a, int b) {
|
|
| 551 |
const float* q = c.Qh + (size_t)i * DHd;
|
| 552 |
float mx = -1e30f;
|
| 553 |
int n = 0;
|
| 554 |
-
for (const auto& rg : ranges)
|
| 555 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 556 |
const float* kk = c.Kh + (size_t)j * DHd;
|
| 557 |
float s = 0;
|
| 558 |
for (int d = 0; d < DHd; d++) s += q[d] * kk[d];
|
|
@@ -560,17 +649,25 @@ static void attnRange(void* ctx, int a, int b) {
|
|
| 560 |
sc[n++] = s;
|
| 561 |
if (s > mx) mx = s;
|
| 562 |
}
|
|
|
|
| 563 |
float z = 0;
|
| 564 |
-
for (int j = 0; j < n; j++) { sc[j] =
|
| 565 |
const float iz = 1.0f / z;
|
| 566 |
-
for (int
|
| 567 |
-
|
| 568 |
-
for (
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
for (int
|
| 573 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 574 |
quant8(out, c.hq + (size_t)i * DHd, c.hs + (size_t)i * (DHd / 32), DHd);
|
| 575 |
}
|
| 576 |
}
|
|
@@ -615,7 +712,10 @@ static void encode(const Req& r, const Scratch& m) {
|
|
| 615 |
const float scale = 1.0f / sqrtf((float)DHd);
|
| 616 |
for (int l = 0; l < tdm::LAYERS; l++) {
|
| 617 |
const tdm::Block& B = tdm::BLOCKS[l];
|
| 618 |
-
|
|
|
|
|
|
|
|
|
|
| 619 |
|
| 620 |
// ---- attention, one head at a time; W_o slice accumulated into the residual
|
| 621 |
for (int h = 0; h < tdm::HEADS; h++) {
|
|
@@ -627,18 +727,26 @@ static void encode(const Req& r, const Scratch& m) {
|
|
| 627 |
parallel(attnRange, &ac, T);
|
| 628 |
mm({&B.o, r0 / 32, DHd / 32, hq, DHd, hs, DHd / 32, T, x, D, h == 0 ? F(B.ob) : nullptr, true, 0}, D);
|
| 629 |
}
|
| 630 |
-
|
|
|
|
| 631 |
|
| 632 |
-
// ---- FFN in 64-neuron chunks
|
| 633 |
-
|
|
|
|
|
|
|
| 634 |
float* Hc = Qh;
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 640 |
}
|
| 641 |
-
|
|
|
|
| 642 |
}
|
| 643 |
}
|
| 644 |
|
|
@@ -688,16 +796,16 @@ static float protoTerm(const Protos& p, const float* qvec, const float* c, int i
|
|
| 688 |
static void headChoice(const Req& r, const float* x, int k, const Question& q, Answer& out) {
|
| 689 |
const int sel = q.type == SCORE ? 1 : 0, n = q.n_options, DH = tdm::DH;
|
| 690 |
const float Tt = tdm::TEMP[sel ? 2 : 0], s = expf(F(tdm::H_SCALE)[sel]);
|
| 691 |
-
float h[tdm::D], qa[tdm::DH],
|
| 692 |
headRow(x, r.ans[k], h);
|
| 693 |
mvI8(tdm::H_A[sel], h, qa);
|
| 694 |
mvI8(tdm::H_P, h, out.qvec);
|
|
|
|
| 695 |
float cbuf[QDIM];
|
| 696 |
const float* c = q.protos ? centreOf(*q.protos, n, cbuf) : nullptr;
|
| 697 |
for (int i = 0; i < n; i++) {
|
| 698 |
headRow(x, r.opt[k][i], h);
|
| 699 |
-
|
| 700 |
-
out.z0[i] = s * dotf(qa, ov, DH) / sqrtf((float)DH) / Tt;
|
| 701 |
L[i] = out.z0[i] + (q.bias ? q.bias[i] : 0.0f);
|
| 702 |
if (q.protos) L[i] += protoTerm(*q.protos, out.qvec, c, i) / Tt;
|
| 703 |
}
|
|
@@ -742,18 +850,18 @@ static void headNoul(const Req& r, const float* x, int k, const Question& q, Ans
|
|
| 742 |
static void headSpan(const Req& r, const float* x, int k, const char* state, Answer& out) {
|
| 743 |
const int S = r.S - 1, DH = tdm::DH; // state text tokens at rows 1..S
|
| 744 |
const float Tt = tdm::TEMP[3], isq = 1.0f / sqrtf((float)DH);
|
| 745 |
-
float h[tdm::D], hAns[tdm::D], qS[tdm::DH],
|
| 746 |
headRow(x, r.ans[k], hAns);
|
| 747 |
mvI8(tdm::H_P, hAns, out.qvec);
|
| 748 |
mvI8(tdm::H_SQ, hAns, qS);
|
|
|
|
| 749 |
// start logits: null, then each state token; log-softmax at temperature Tt
|
| 750 |
const float zNull = dotf(F(tdm::H_SNULL_W), hAns, tdm::D) + F(tdm::H_SNULL_B)[0];
|
| 751 |
static float zs[tdm::TS_MAX];
|
| 752 |
float mx = zNull;
|
| 753 |
for (int t = 0; t < S; t++) {
|
| 754 |
headRow(x, 1 + t, h);
|
| 755 |
-
|
| 756 |
-
zs[t] = dotf(qS, kk, DH) * isq;
|
| 757 |
if (zs[t] > mx) mx = zs[t];
|
| 758 |
}
|
| 759 |
float Z = expf((zNull - mx) / Tt);
|
|
@@ -774,12 +882,12 @@ static void headSpan(const Req& r, const float* x, int k, const char* state, Ans
|
|
| 774 |
headRow(x, 1 + best, h);
|
| 775 |
mvI8(tdm::H_ES, h, es);
|
| 776 |
for (int i = 0; i < DH; i++) qe[i] += es[i];
|
|
|
|
| 777 |
const int e1 = best + tdm::SPAN_MAX < S ? best + tdm::SPAN_MAX : S;
|
| 778 |
float en[tdm::SPAN_MAX], emx = -1e30f;
|
| 779 |
for (int t = best; t < e1; t++) {
|
| 780 |
headRow(x, 1 + t, h);
|
| 781 |
-
|
| 782 |
-
en[t - best] = dotf(qe, kk, DH) * isq;
|
| 783 |
if (en[t - best] > emx) emx = en[t - best];
|
| 784 |
}
|
| 785 |
float EZ = 0;
|
|
|
|
| 2 |
//
|
| 3 |
// Memory plan ("streamed kernel"): one scratch area per call, ~2.2 KB per token. Each layer keeps
|
| 4 |
// the fp32 residual x plus an int8 copy of the layer input; attention runs one head at a time with
|
| 5 |
+
// W_o accumulated straight into x, and the FFN runs fc in 64-neuron chunks into 256-neuron int8
|
| 6 |
+
// groups (in the then idle K and V buffers), each group's fc2 slice accumulated into x. Every
|
| 7 |
+
// weight row is read once per layer and applied to all tokens while it is hot in cache, four
|
| 8 |
+
// tokens per unpacked weight block on the ESP32-S3; rows are split across both cores on dual-core
|
| 9 |
+
// chips.
|
| 10 |
#include "tinydecide.h"
|
| 11 |
#include "td_meta.h"
|
| 12 |
#include "td_unicode.h"
|
|
|
|
| 278 |
#endif
|
| 279 |
}
|
| 280 |
|
| 281 |
+
// lrintf for |v| < 2^22 under round-to-nearest-even, without the libm call: adding 1.5 * 2^23
|
| 282 |
+
// leaves no fraction bits, so the FPU rounds v to an integer exactly as lrintf does.
|
| 283 |
+
static inline int roundEven(float v) { return (int)((v + 12582912.0f) - 12582912.0f); }
|
| 284 |
+
|
| 285 |
// Symmetric int8 per 32-element block, one fp32 scale each (llama.cpp Q8_0).
|
| 286 |
+
// No a*b+c fusion here: x * inv must be rounded to float before roundEven, as lrintf would see it.
|
| 287 |
+
#if defined(__GNUC__) && !defined(__clang__)
|
| 288 |
+
__attribute__((optimize("fp-contract=off")))
|
| 289 |
+
#endif
|
| 290 |
static void quant8(const float* x, int8_t* xq, float* xs, int n) {
|
| 291 |
+
#if defined(__clang__)
|
| 292 |
+
#pragma clang fp contract(off)
|
| 293 |
+
#endif
|
| 294 |
for (int b = 0; b < n / 32; b++) {
|
| 295 |
float mx = 0.0f;
|
| 296 |
for (int i = 0; i < 32; i++) { float a = fabsf(x[i]); if (a > mx) mx = a; }
|
| 297 |
float s = mx / 127.0f, inv = s > 0 ? 1.0f / s : 0.0f;
|
| 298 |
xs[b] = s;
|
| 299 |
+
for (int i = 0; i < 32; i++) xq[i] = (int8_t)roundEven(x[i] * inv);
|
| 300 |
x += 32; xq += 32;
|
| 301 |
}
|
| 302 |
}
|
|
|
|
| 321 |
const uint8_t* sc = g_m + a.w->sc + ((size_t)r * nbRow + a.blk0) * 2;
|
| 322 |
const float b = a.bias ? a.bias[r] : 0.0f;
|
| 323 |
float* o = a.out + (r - a.r0);
|
| 324 |
+
int t = 0;
|
| 325 |
+
#if defined(TD_PIE)
|
| 326 |
+
// tiles of four tokens, up to eight tiles per call: each weight block is unpacked once per
|
| 327 |
+
// tile (xss == xqs / 32)
|
| 328 |
+
float vt[32];
|
| 329 |
+
while (t + 4 <= a.T) {
|
| 330 |
+
const int nt = (a.T - t) / 4 < 8 ? (a.T - t) / 4 : 8;
|
| 331 |
+
dot4_q4q8_pie(nib, sc, a.xq + (size_t)t * a.xqs, a.xs + (size_t)t * a.xss, a.nbk, a.xqs, vt, nt);
|
| 332 |
+
for (int j = 0; j < 4 * nt; j++) {
|
| 333 |
+
const float v = vt[j] + b;
|
| 334 |
+
if (a.acc) o[(size_t)(t + j) * a.os] += v; else o[(size_t)(t + j) * a.os] = v;
|
| 335 |
+
}
|
| 336 |
+
t += 4 * nt;
|
| 337 |
+
}
|
| 338 |
+
#endif
|
| 339 |
+
for (; t < a.T; t++) {
|
| 340 |
float v = dotq(nib, a.xq + (size_t)t * a.xqs, sc, a.xs + (size_t)t * a.xss, a.nbk) + b;
|
| 341 |
if (a.acc) o[(size_t)t * a.os] += v; else o[(size_t)t * a.os] = v;
|
| 342 |
}
|
|
|
|
| 443 |
}
|
| 444 |
}
|
| 445 |
|
| 446 |
+
// u = W^T v for an int8 head matrix: q . (W h) == u . h, so a head that scores many tokens against
|
| 447 |
+
// one query reads W once instead of once per token. u has w.cols entries.
|
| 448 |
+
static void mtvI8(const tdm::I8& w, const float* v, float* u) {
|
| 449 |
+
const int8_t* q = (const int8_t*)(g_m + w.q);
|
| 450 |
+
const float* s = F(w.sc);
|
| 451 |
+
for (int c = 0; c < w.cols; c++) u[c] = 0;
|
| 452 |
+
for (int r = 0; r < w.rows; r++) {
|
| 453 |
+
const float k = v[r] * s[r];
|
| 454 |
+
const int8_t* row = q + (size_t)r * w.cols;
|
| 455 |
+
for (int c = 0; c < w.cols; c++) u[c] += k * (float)row[c];
|
| 456 |
+
}
|
| 457 |
+
}
|
| 458 |
+
|
| 459 |
+
// exp(x) for the attention softmax, x <= 0: Cephes expf (within about 1 ulp of expf) at a tenth of
|
| 460 |
+
// newlib's cost. Returns 0 below -87, where expf is denormal.
|
| 461 |
+
static inline float expNeg(float x) {
|
| 462 |
+
if (x < -87.0f) return 0.0f;
|
| 463 |
+
const float n = (x * 1.44269504089f + 12582912.0f) - 12582912.0f; // round(x / ln 2)
|
| 464 |
+
const float r = (x - n * 0.693359375f) - n * -2.12194440e-4f;
|
| 465 |
+
float p = 1.9875691500e-4f;
|
| 466 |
+
p = p * r + 1.3981999507e-3f;
|
| 467 |
+
p = p * r + 8.3334519073e-3f;
|
| 468 |
+
p = p * r + 4.1665795894e-2f;
|
| 469 |
+
p = p * r + 1.6666665459e-1f;
|
| 470 |
+
p = p * r + 5.0000001201e-1f;
|
| 471 |
+
p = p * r * r + r + 1.0f;
|
| 472 |
+
const uint32_t bits = (uint32_t)((int)n + 127) << 23;
|
| 473 |
+
float sc;
|
| 474 |
+
memcpy(&sc, &bits, 4);
|
| 475 |
+
return p * sc;
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
// ============================================================================
|
| 479 |
// Request layout (encodeRequest in tinydecide.js)
|
| 480 |
// ============================================================================
|
|
|
|
| 576 |
return false;
|
| 577 |
}
|
| 578 |
|
| 579 |
+
// GELU over one 64-neuron FFN chunk for tokens [a, b), then int8 into columns of hq (row stride
|
| 580 |
+
// qs bytes, scales qs / 32 floats) for fc2.
|
| 581 |
+
struct GeluCtx { float* Hc; int8_t* hq; float* hs; int qs; };
|
| 582 |
static void geluRange(void* ctx, int a, int b) {
|
| 583 |
const GeluCtx& g = *(const GeluCtx*)ctx;
|
| 584 |
const int DHd = tdm::DHEAD;
|
| 585 |
for (int t = a; t < b; t++) {
|
| 586 |
float* hrow = g.Hc + (size_t)t * DHd;
|
| 587 |
for (int c = 0; c < DHd; c++) hrow[c] = gelu(hrow[c]);
|
| 588 |
+
quant8(hrow, g.hq + (size_t)t * g.qs, g.hs + (size_t)t * (g.qs / 32), DHd);
|
| 589 |
+
}
|
| 590 |
+
}
|
| 591 |
+
|
| 592 |
+
// Optional LayerNorm (w != nullptr), then int8 of x for the next matmuls, for tokens [a, b).
|
| 593 |
+
struct NormCtx { float* x; int8_t* xq; float* xs; const float* w; const float* b; };
|
| 594 |
+
static void normRange(void* ctx, int a, int b) {
|
| 595 |
+
const NormCtx& n = *(const NormCtx*)ctx;
|
| 596 |
+
const int D = tdm::D;
|
| 597 |
+
for (int t = a; t < b; t++) {
|
| 598 |
+
float* row = n.x + (size_t)t * D;
|
| 599 |
+
if (n.w) layernorm(row, D, n.w, n.b, tdm::LN_EPS);
|
| 600 |
+
quant8(row, n.xq + (size_t)t * D, n.xs + (size_t)t * (D / 32), D);
|
| 601 |
}
|
| 602 |
}
|
| 603 |
|
|
|
|
| 624 |
const float* q = c.Qh + (size_t)i * DHd;
|
| 625 |
float mx = -1e30f;
|
| 626 |
int n = 0;
|
| 627 |
+
for (const auto& rg : ranges) {
|
| 628 |
+
int j = rg[0];
|
| 629 |
+
// four keys at a time: four independent sums, each in the order of the one-key loop
|
| 630 |
+
for (; j + 4 <= rg[1]; j += 4) {
|
| 631 |
+
const float* k0 = c.Kh + (size_t)j * DHd;
|
| 632 |
+
const float *k1 = k0 + DHd, *k2 = k1 + DHd, *k3 = k2 + DHd;
|
| 633 |
+
float s0 = 0, s1 = 0, s2 = 0, s3 = 0;
|
| 634 |
+
for (int d = 0; d < DHd; d++) {
|
| 635 |
+
const float qd = q[d];
|
| 636 |
+
s0 += qd * k0[d]; s1 += qd * k1[d]; s2 += qd * k2[d]; s3 += qd * k3[d];
|
| 637 |
+
}
|
| 638 |
+
for (float s : {s0, s1, s2, s3}) {
|
| 639 |
+
s *= c.scale;
|
| 640 |
+
sc[n++] = s;
|
| 641 |
+
if (s > mx) mx = s;
|
| 642 |
+
}
|
| 643 |
+
}
|
| 644 |
+
for (; j < rg[1]; j++) {
|
| 645 |
const float* kk = c.Kh + (size_t)j * DHd;
|
| 646 |
float s = 0;
|
| 647 |
for (int d = 0; d < DHd; d++) s += q[d] * kk[d];
|
|
|
|
| 649 |
sc[n++] = s;
|
| 650 |
if (s > mx) mx = s;
|
| 651 |
}
|
| 652 |
+
}
|
| 653 |
float z = 0;
|
| 654 |
+
for (int j = 0; j < n; j++) { sc[j] = expNeg(sc[j] - mx); z += sc[j]; }
|
| 655 |
const float iz = 1.0f / z;
|
| 656 |
+
for (int j = 0; j < n; j++) sc[j] *= iz;
|
| 657 |
+
// out = sum_j p_j v_j, eight dimensions at a time so the sums stay in registers
|
| 658 |
+
for (int d0 = 0; d0 < DHd; d0 += 8) {
|
| 659 |
+
float o0 = 0, o1 = 0, o2 = 0, o3 = 0, o4 = 0, o5 = 0, o6 = 0, o7 = 0;
|
| 660 |
+
n = 0;
|
| 661 |
+
for (const auto& rg : ranges)
|
| 662 |
+
for (int j = rg[0]; j < rg[1]; j++) {
|
| 663 |
+
const float p = sc[n++];
|
| 664 |
+
const float* v = c.Vh + (size_t)j * DHd + d0;
|
| 665 |
+
o0 += p * v[0]; o1 += p * v[1]; o2 += p * v[2]; o3 += p * v[3];
|
| 666 |
+
o4 += p * v[4]; o5 += p * v[5]; o6 += p * v[6]; o7 += p * v[7];
|
| 667 |
+
}
|
| 668 |
+
out[d0] = o0; out[d0 + 1] = o1; out[d0 + 2] = o2; out[d0 + 3] = o3;
|
| 669 |
+
out[d0 + 4] = o4; out[d0 + 5] = o5; out[d0 + 6] = o6; out[d0 + 7] = o7;
|
| 670 |
+
}
|
| 671 |
quant8(out, c.hq + (size_t)i * DHd, c.hs + (size_t)i * (DHd / 32), DHd);
|
| 672 |
}
|
| 673 |
}
|
|
|
|
| 712 |
const float scale = 1.0f / sqrtf((float)DHd);
|
| 713 |
for (int l = 0; l < tdm::LAYERS; l++) {
|
| 714 |
const tdm::Block& B = tdm::BLOCKS[l];
|
| 715 |
+
if (l == 0) {
|
| 716 |
+
NormCtx nc{x, xq, xs, nullptr, nullptr};
|
| 717 |
+
parallel(normRange, &nc, T);
|
| 718 |
+
}
|
| 719 |
|
| 720 |
// ---- attention, one head at a time; W_o slice accumulated into the residual
|
| 721 |
for (int h = 0; h < tdm::HEADS; h++) {
|
|
|
|
| 727 |
parallel(attnRange, &ac, T);
|
| 728 |
mm({&B.o, r0 / 32, DHd / 32, hq, DHd, hs, DHd / 32, T, x, D, h == 0 ? F(B.ob) : nullptr, true, 0}, D);
|
| 729 |
}
|
| 730 |
+
NormCtx n1{x, xq, xs, F(B.ln1w), F(B.ln1b)};
|
| 731 |
+
parallel(normRange, &n1, T);
|
| 732 |
|
| 733 |
+
// ---- FFN: fc in 64-neuron chunks through GELU into int8 groups of G neurons, held in the
|
| 734 |
+
// K and V buffers (free until the next layer); each group's fc2 slice is accumulated into x.
|
| 735 |
+
constexpr int G = 256;
|
| 736 |
+
static_assert(tdm::FFN % G == 0 && G <= tdm::DHEAD * 4, "an FFN group must fit the K buffer");
|
| 737 |
float* Hc = Qh;
|
| 738 |
+
int8_t* gq = (int8_t*)Kh; // [T, G] int8
|
| 739 |
+
float* gs = Vh; // [T, G / 32]
|
| 740 |
+
for (int g0 = 0; g0 < tdm::FFN; g0 += G) {
|
| 741 |
+
for (int c0 = g0; c0 < g0 + G; c0 += DHd) {
|
| 742 |
+
mm({&B.fc, 0, D / 32, xq, D, xs, D / 32, T, Hc, DHd, F(B.fcb), false, c0}, c0 + DHd);
|
| 743 |
+
GeluCtx gc{Hc, gq + (c0 - g0), gs + (c0 - g0) / 32, G};
|
| 744 |
+
parallel(geluRange, &gc, T);
|
| 745 |
+
}
|
| 746 |
+
mm({&B.fc2, g0 / 32, G / 32, gq, G, gs, G / 32, T, x, D, g0 == 0 ? F(B.fc2b) : nullptr, true, 0}, D);
|
| 747 |
}
|
| 748 |
+
NormCtx n2{x, xq, xs, F(B.ln2w), F(B.ln2b)}; // the int8 copy is the next layer's input
|
| 749 |
+
parallel(normRange, &n2, T);
|
| 750 |
}
|
| 751 |
}
|
| 752 |
|
|
|
|
| 796 |
static void headChoice(const Req& r, const float* x, int k, const Question& q, Answer& out) {
|
| 797 |
const int sel = q.type == SCORE ? 1 : 0, n = q.n_options, DH = tdm::DH;
|
| 798 |
const float Tt = tdm::TEMP[sel ? 2 : 0], s = expf(F(tdm::H_SCALE)[sel]);
|
| 799 |
+
float h[tdm::D], qa[tdm::DH], u[tdm::D], L[MAX_OPTIONS];
|
| 800 |
headRow(x, r.ans[k], h);
|
| 801 |
mvI8(tdm::H_A[sel], h, qa);
|
| 802 |
mvI8(tdm::H_P, h, out.qvec);
|
| 803 |
+
mtvI8(tdm::H_O[sel], qa, u); // qa . (H_O h) == u . h
|
| 804 |
float cbuf[QDIM];
|
| 805 |
const float* c = q.protos ? centreOf(*q.protos, n, cbuf) : nullptr;
|
| 806 |
for (int i = 0; i < n; i++) {
|
| 807 |
headRow(x, r.opt[k][i], h);
|
| 808 |
+
out.z0[i] = s * dotf(u, h, tdm::D) / sqrtf((float)DH) / Tt;
|
|
|
|
| 809 |
L[i] = out.z0[i] + (q.bias ? q.bias[i] : 0.0f);
|
| 810 |
if (q.protos) L[i] += protoTerm(*q.protos, out.qvec, c, i) / Tt;
|
| 811 |
}
|
|
|
|
| 850 |
static void headSpan(const Req& r, const float* x, int k, const char* state, Answer& out) {
|
| 851 |
const int S = r.S - 1, DH = tdm::DH; // state text tokens at rows 1..S
|
| 852 |
const float Tt = tdm::TEMP[3], isq = 1.0f / sqrtf((float)DH);
|
| 853 |
+
float h[tdm::D], hAns[tdm::D], qS[tdm::DH], qe[tdm::DH], u[tdm::D];
|
| 854 |
headRow(x, r.ans[k], hAns);
|
| 855 |
mvI8(tdm::H_P, hAns, out.qvec);
|
| 856 |
mvI8(tdm::H_SQ, hAns, qS);
|
| 857 |
+
mtvI8(tdm::H_SK, qS, u); // qS . (H_SK h) == u . h
|
| 858 |
// start logits: null, then each state token; log-softmax at temperature Tt
|
| 859 |
const float zNull = dotf(F(tdm::H_SNULL_W), hAns, tdm::D) + F(tdm::H_SNULL_B)[0];
|
| 860 |
static float zs[tdm::TS_MAX];
|
| 861 |
float mx = zNull;
|
| 862 |
for (int t = 0; t < S; t++) {
|
| 863 |
headRow(x, 1 + t, h);
|
| 864 |
+
zs[t] = dotf(u, h, tdm::D) * isq;
|
|
|
|
| 865 |
if (zs[t] > mx) mx = zs[t];
|
| 866 |
}
|
| 867 |
float Z = expf((zNull - mx) / Tt);
|
|
|
|
| 882 |
headRow(x, 1 + best, h);
|
| 883 |
mvI8(tdm::H_ES, h, es);
|
| 884 |
for (int i = 0; i < DH; i++) qe[i] += es[i];
|
| 885 |
+
mtvI8(tdm::H_EK, qe, u); // qe . (H_EK h) == u . h
|
| 886 |
const int e1 = best + tdm::SPAN_MAX < S ? best + tdm::SPAN_MAX : S;
|
| 887 |
float en[tdm::SPAN_MAX], emx = -1e30f;
|
| 888 |
for (int t = best; t < e1; t++) {
|
| 889 |
headRow(x, 1 + t, h);
|
| 890 |
+
en[t - best] = dotf(u, h, tdm::D) * isq;
|
|
|
|
| 891 |
if (en[t - best] > emx) emx = en[t - best];
|
| 892 |
}
|
| 893 |
float EZ = 0;
|
esp32/tinydecide/src/model_data.S
CHANGED
|
@@ -1,10 +1,12 @@
|
|
| 1 |
// Embeds model.bin and vocab.bin in flash (.rodata, MMU-mapped) when the ESP-IDF component is built
|
| 2 |
// with CONFIG_TINYDECIDE_EMBED_MODEL (CMakeLists.txt defines TINYDECIDE_EMBED and the include path).
|
| 3 |
-
// The PIE kernel loads 128-bit words, so the model
|
| 4 |
-
// inside it are multiples of 16 (checked by tools/pack.mjs).
|
|
|
|
|
|
|
| 5 |
#if defined(TINYDECIDE_EMBED)
|
| 6 |
.section .rodata.td_model, "a"
|
| 7 |
-
.balign
|
| 8 |
.global td_model_bin
|
| 9 |
td_model_bin:
|
| 10 |
.incbin "model.bin"
|
|
|
|
| 1 |
// Embeds model.bin and vocab.bin in flash (.rodata, MMU-mapped) when the ESP-IDF component is built
|
| 2 |
// with CONFIG_TINYDECIDE_EMBED_MODEL (CMakeLists.txt defines TINYDECIDE_EMBED and the include path).
|
| 3 |
+
// The PIE kernel loads 128-bit words, so the model must start on a 16-byte boundary; all Q4 offsets
|
| 4 |
+
// inside it are multiples of 16 (checked by tools/pack.mjs). It starts on a 64-byte boundary so the
|
| 5 |
+
// weight rows, at 64-byte offsets in model.bin, line up with the flash cache lines: unaligned, each
|
| 6 |
+
// row touches one more line and the matmuls run up to ~10% slower.
|
| 7 |
#if defined(TINYDECIDE_EMBED)
|
| 8 |
.section .rodata.td_model, "a"
|
| 9 |
+
.balign 64
|
| 10 |
.global td_model_bin
|
| 11 |
td_model_bin:
|
| 12 |
.incbin "model.bin"
|
esp32/tinydecide/src/tinydecide.h
CHANGED
|
@@ -117,4 +117,7 @@ int requestTokens(const Question* qs, int nq, int state_tokens);
|
|
| 117 |
// Q4_0 row-planar dot product against Q8_0 activations (dot_q4_pie.S, ESP32-S3 only).
|
| 118 |
extern "C" float dot_q4q8_pie(const uint8_t* nib, const int8_t* xq, const uint8_t* scales,
|
| 119 |
const float* xs, int nb);
|
|
|
|
|
|
|
|
|
|
| 120 |
#endif
|
|
|
|
| 117 |
// Q4_0 row-planar dot product against Q8_0 activations (dot_q4_pie.S, ESP32-S3 only).
|
| 118 |
extern "C" float dot_q4q8_pie(const uint8_t* nib, const int8_t* xq, const uint8_t* scales,
|
| 119 |
const float* xs, int nb);
|
| 120 |
+
// The same for ntile * 4 tokens xqs bytes apart (their scales xqs / 32 floats apart); out[4 * ntile].
|
| 121 |
+
extern "C" void dot4_q4q8_pie(const uint8_t* nib, const uint8_t* scales, const int8_t* xq, const float* xs,
|
| 122 |
+
int nb, int xqs, float* out, int ntile);
|
| 123 |
#endif
|