ember.cpp 0.1.0
C++ inference engine for Llama-family and paperkiln models (CPU, CUDA, WebAssembly)
Loading...
Searching...
No Matches
graph_ir_recurrence.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cctype>
5#include <cmath>
6#include <cstdlib>
7#include <map>
8#include <set>
9#include <stdexcept>
10
11using nlohmann::json;
12
13namespace ember_ir {
14namespace rec {
15namespace {
16
17[[noreturn]] void fail(int rule, const std::string& msg) {
18 throw std::runtime_error("IR_SPEC 6.10 (rule " + std::to_string(rule) + "): " + msg);
19}
20
21const std::set<std::string> kFunctions = {"exp", "log", "sqrt", "tanh", "erf", "abs", "sigmoid", "relu",
22 "max", "min", "where"};
23const std::set<std::string> kRowReductions = {"rowsum", "rowmean", "rowmax"};
24
25bool is_identifier(const std::string& s) {
26 if (s.empty() || !(std::isalpha(static_cast<unsigned char>(s[0])) || s[0] == '_')) return false;
27 for (char c : s)
28 if (!(std::isalnum(static_cast<unsigned char>(c)) || c == '_')) return false;
29 return true;
30}
31
32bool is_reserved(const std::string& s) {
33 return kFunctions.count(s) || kRowReductions.count(s) || s == "pi" || s == "sum" || s == "per_layer";
34}
35
36// ---------------------------------------------------------------- tokens
37
38enum class Tok { Ident, Number, LParen, RParen, LBracket, RBracket, Comma, Plus, Minus, Star, Slash, Caret, Eq, End };
39
40struct Token {
41 Tok kind = Tok::End;
42 std::string text;
43 size_t column = 0; // 1-based
44 bool integer = false; // a Number of digits only
45};
46
47std::vector<Token> lex(const std::string& s, const std::string& where) {
48 std::vector<Token> out;
49 size_t i = 0;
50 while (i < s.size()) {
51 const char c = s[i];
52 if (c == ' ' || c == '\t') {
53 ++i;
54 continue;
55 }
56 Token t;
57 t.column = i + 1;
58 if (std::isalpha(static_cast<unsigned char>(c)) || c == '_') {
59 size_t j = i;
60 while (j < s.size() && (std::isalnum(static_cast<unsigned char>(s[j])) || s[j] == '_')) ++j;
61 t.kind = Tok::Ident;
62 t.text = s.substr(i, j - i);
63 i = j;
64 } else if (std::isdigit(static_cast<unsigned char>(c)) || c == '.') {
65 size_t j = i, mant = 0;
66 while (j < s.size() && std::isdigit(static_cast<unsigned char>(s[j]))) ++j, ++mant;
67 bool integer = true;
68 if (j < s.size() && s[j] == '.') {
69 integer = false;
70 ++j;
71 while (j < s.size() && std::isdigit(static_cast<unsigned char>(s[j]))) ++j, ++mant;
72 }
73 if (mant == 0) fail(31, where + " does not parse: a malformed number at column " + std::to_string(t.column));
74 if (j < s.size() && (s[j] == 'e' || s[j] == 'E')) {
75 integer = false;
76 size_t k = j + 1;
77 if (k < s.size() && (s[k] == '+' || s[k] == '-')) ++k;
78 const size_t d0 = k;
79 while (k < s.size() && std::isdigit(static_cast<unsigned char>(s[k]))) ++k;
80 if (k == d0)
81 fail(31, where + " does not parse: a malformed number (an exponent without digits) at column " +
82 std::to_string(t.column));
83 j = k;
84 }
85 t.kind = Tok::Number;
86 t.text = s.substr(i, j - i);
87 t.integer = integer;
88 i = j;
89 } else {
90 static const std::map<char, Tok> kPunct = {{'(', Tok::LParen}, {')', Tok::RParen}, {'[', Tok::LBracket},
91 {']', Tok::RBracket}, {',', Tok::Comma}, {'+', Tok::Plus},
92 {'-', Tok::Minus}, {'*', Tok::Star}, {'/', Tok::Slash},
93 {'^', Tok::Caret}, {'=', Tok::Eq}};
94 const auto it = kPunct.find(c);
95 if (it == kPunct.end())
96 fail(31, where + " does not parse: unexpected character '" + std::string(1, c) + "' at column " +
97 std::to_string(t.column));
98 t.kind = it->second;
99 t.text = std::string(1, c);
100 ++i;
101 }
102 out.push_back(std::move(t));
103 }
104 Token end;
105 end.kind = Tok::End;
106 end.column = s.size() + 1;
107 out.push_back(end);
108 return out;
109}
110
111// ---------------------------------------------------------------- the compiled form
112
113enum class K {
114 Num, Idx, Read, Neg, Add, Sub, Mul, Div, Pow,
115 Exp, Log, Sqrt, Tanh, Erf, Abs, Sigmoid, Relu, Max, Min, Where, Sum
116};
117
118struct Ix {
119 int index = -1; // -1: the constant off
120 long off = 0;
121};
122
123struct E {
124 K k = K::Num;
125 float v = 0.0f;
126 int id = -1; // Idx, Sum: the index; Read: the tensor
127 std::vector<Ix> ix;
128 std::vector<E> a;
129};
130
131enum class TKind { Input, Weight, State, Let, Out };
132
133struct Tensor {
134 std::string name;
135 TKind kind = TKind::Input;
136 std::vector<int> sizes;
137 std::vector<size_t> strides;
138 size_t numel = 1;
139 int pos = 0; // within its kind's list
140};
141
142struct Assign {
143 int tensor = -1;
144 std::vector<int> lhs; // index ids
146 std::string where;
147 size_t body_tok = 0; // the first token of the body (compile time)
148 std::vector<Token> toks;
149};
150
151} // namespace
152
153struct Program {
154 std::vector<std::string> index_names;
155 std::vector<int> index_sizes;
156 std::map<std::string, int> index_of;
157 std::vector<Tensor> tensors;
158 std::map<std::string, int> tensor_of;
159 std::vector<int> inputs, weights, states, lets;
160 int out = -1;
161 std::vector<Assign> init, let, update;
162 Assign readout;
163 std::vector<int> init_of_state; // per state: index into init, or -1
164 std::vector<Recurrence::StateTensor> state_info;
165 std::vector<Recurrence::WeightDecl> weight_info;
166 std::vector<size_t> in_widths;
167 size_t out_width = 0;
168 bool exact = true;
169 // evaluation scratch
170 mutable std::vector<int> iv;
171 mutable std::vector<const float*> data;
172 mutable std::vector<std::vector<float>> let_buf, new_state;
173
174 float eval(const E& e) const;
175 void assign(const Assign& a, float* out) const;
176};
177
178namespace {
179
180// Which tensors a body may read (section 6.10.2), by section.
181struct ReadRules {
182 bool inputs = false, weights = false, state = false;
183 int lets = 0; // lets 0 .. lets - 1
184};
185
186class BodyParser {
187 public:
188 BodyParser(Program& p, const std::vector<Token>& toks, size_t pos, const std::string& where,
189 const ReadRules& rules, const std::vector<int>& scope)
190 : p_(p), t_(toks), i_(pos), where_(where), rules_(rules), in_scope_(p.index_names.size(), false) {
191 for (int s : scope) in_scope_[s] = true;
192 }
193
194 E parse() {
195 E e = expr(0);
196 if (peek().kind != Tok::End) syntax("unexpected '" + peek().text + "'");
197 return e;
198 }
199
200 private:
201 const Token& peek() const { return t_[i_]; }
202 const Token& next() { return t_[i_++]; }
203 [[noreturn]] void syntax(const std::string& what) const {
204 fail(31, where_ + " does not parse: " + what + " at column " + std::to_string(peek().column));
205 }
206 void expect(Tok k, const char* what) {
207 if (peek().kind != k) syntax(std::string("expected ") + what);
208 ++i_;
209 }
210 void enter(int depth) const {
211 if (depth > 256) syntax("nesting deeper than the limit");
212 }
213
214 E expr(int depth) {
215 enter(depth);
216 E left = term(depth + 1);
217 while (peek().kind == Tok::Plus || peek().kind == Tok::Minus) {
218 const K k = next().kind == Tok::Plus ? K::Add : K::Sub;
219 E right = term(depth + 1);
220 E n;
221 n.k = k;
222 n.a.push_back(std::move(left));
223 n.a.push_back(std::move(right));
224 left = std::move(n);
225 }
226 return left;
227 }
228 E term(int depth) {
229 enter(depth);
230 E left = unary(depth + 1);
231 while (peek().kind == Tok::Star || peek().kind == Tok::Slash) {
232 const K k = next().kind == Tok::Star ? K::Mul : K::Div;
233 E right = unary(depth + 1);
234 E n;
235 n.k = k;
236 n.a.push_back(std::move(left));
237 n.a.push_back(std::move(right));
238 left = std::move(n);
239 }
240 return left;
241 }
242 E unary(int depth) {
243 enter(depth);
244 if (peek().kind == Tok::Minus) {
245 ++i_;
246 E n;
247 n.k = K::Neg;
248 n.a.push_back(unary(depth + 1));
249 return n;
250 }
251 return power(depth + 1);
252 }
253 E power(int depth) {
254 enter(depth);
255 E base = primary(depth + 1);
256 if (peek().kind == Tok::Caret) {
257 ++i_;
258 p_.exact = false;
259 E n;
260 n.k = K::Pow;
261 n.a.push_back(std::move(base));
262 n.a.push_back(unary(depth + 1));
263 return n;
264 }
265 return base;
266 }
267
268 int declared_index(const Token& t) const {
269 const auto it = p_.index_of.find(t.text);
270 if (it == p_.index_of.end())
271 fail(32, where_ + ": index '" + t.text + "' at column " + std::to_string(t.column) + " is not declared");
272 return it->second;
273 }
274
275 Ix ix() {
276 Ix r;
277 const Token& t = peek();
278 if (t.kind == Tok::Number) {
279 if (!t.integer) syntax("an index offset or position must be an integer");
280 ++i_;
281 r.off = std::strtol(t.text.c_str(), nullptr, 10);
282 return r;
283 }
284 if (t.kind != Tok::Ident) syntax("expected an index");
285 ++i_;
286 r.index = declared_index(t);
287 if (!in_scope_[r.index])
288 fail(32, where_ + ": index '" + t.text + "' at column " + std::to_string(t.column) + " is not in scope");
289 if (peek().kind == Tok::Plus || peek().kind == Tok::Minus) {
290 const bool minus = next().kind == Tok::Minus;
291 const Token& n = peek();
292 if (n.kind != Tok::Number || !n.integer) syntax("expected an integer offset");
293 ++i_;
294 const long v = std::strtol(n.text.c_str(), nullptr, 10);
295 r.off = minus ? -v : v;
296 }
297 return r;
298 }
299
300 E primary(int depth) {
301 enter(depth);
302 const Token& t = peek();
303 if (t.kind == Tok::Number) {
304 ++i_;
305 E n;
306 n.k = K::Num;
307 n.v = std::strtof(t.text.c_str(), nullptr);
308 if (!std::isfinite(n.v)) syntax("the number " + t.text + " is not finite in f32");
309 return n;
310 }
311 if (t.kind == Tok::LParen) {
312 ++i_;
313 E e = expr(depth + 1);
314 expect(Tok::RParen, "')'");
315 return e;
316 }
317 if (t.kind != Tok::Ident) syntax("expected an operand");
318 const std::string name = t.text;
319 const size_t col = t.column;
320 ++i_;
321 if (name == "pi") {
322 E n;
323 n.k = K::Num;
324 n.v = static_cast<float>(3.14159265358979323846);
325 return n;
326 }
327 if (kRowReductions.count(name))
328 fail(31, where_ + " uses " + name + " at column " + std::to_string(col) +
329 " (the row reductions are not part of a recurrence body; sum replaces them)");
330 if (name == "sum") {
331 expect(Tok::LParen, "'(' after sum");
332 const Token& it = peek();
333 if (it.kind != Tok::Ident) syntax("expected sum's index");
334 ++i_;
335 const int id = declared_index(it);
336 if (in_scope_[id])
337 fail(32, where_ + ": sum over index '" + it.text + "' at column " + std::to_string(it.column) +
338 ", which is already in scope");
339 expect(Tok::Comma, "',' after sum's index");
340 in_scope_[id] = true;
341 E arg = expr(depth + 1);
342 in_scope_[id] = false;
343 expect(Tok::RParen, "')'");
344 E n;
345 n.k = K::Sum;
346 n.id = id;
347 n.a.push_back(std::move(arg));
348 return n;
349 }
350 if (peek().kind == Tok::LParen) {
351 static const std::map<std::string, std::pair<K, size_t>> kFn = {
352 {"exp", {K::Exp, 1}}, {"log", {K::Log, 1}}, {"sqrt", {K::Sqrt, 1}}, {"tanh", {K::Tanh, 1}},
353 {"erf", {K::Erf, 1}}, {"abs", {K::Abs, 1}}, {"sigmoid", {K::Sigmoid, 1}},
354 {"relu", {K::Relu, 1}}, {"max", {K::Max, 2}}, {"min", {K::Min, 2}}, {"where", {K::Where, 3}}};
355 const auto f = kFn.find(name);
356 if (f == kFn.end()) fail(31, where_ + ": unknown function '" + name + "' at column " + std::to_string(col));
357 ++i_;
358 E n;
359 n.k = f->second.first;
360 if (n.k == K::Exp || n.k == K::Log || n.k == K::Tanh || n.k == K::Erf || n.k == K::Sigmoid) p_.exact = false;
361 n.a.push_back(expr(depth + 1));
362 while (peek().kind == Tok::Comma) {
363 ++i_;
364 n.a.push_back(expr(depth + 1));
365 }
366 expect(Tok::RParen, "')'");
367 if (n.a.size() != f->second.second)
368 fail(31, where_ + ": " + name + " at column " + std::to_string(col) + " takes " +
369 std::to_string(f->second.second) + " argument(s), not " + std::to_string(n.a.size()));
370 return n;
371 }
372 if (kFunctions.count(name)) syntax("expected '(' after " + name);
373 if (peek().kind == Tok::LBracket) {
374 ++i_;
375 const auto it = p_.tensor_of.find(name);
376 if (it == p_.tensor_of.end())
377 fail(32, where_ + " reads '" + name + "' at column " + std::to_string(col) + ", which is not declared");
378 const Tensor& tn = p_.tensors[it->second];
379 const bool ok = (tn.kind == TKind::Input && rules_.inputs) || (tn.kind == TKind::Weight && rules_.weights) ||
380 (tn.kind == TKind::State && rules_.state) || (tn.kind == TKind::Let && tn.pos < rules_.lets);
381 if (!ok)
382 fail(32, where_ + " reads '" + name + "' at column " + std::to_string(col) + ", which its section cannot read");
383 E n;
384 n.k = K::Read;
385 n.id = it->second;
386 n.ix.push_back(ix());
387 while (peek().kind == Tok::Comma) {
388 ++i_;
389 n.ix.push_back(ix());
390 }
391 expect(Tok::RBracket, "']'");
392 if (n.ix.size() != tn.sizes.size())
393 fail(32, where_ + " reads '" + name + "' at column " + std::to_string(col) + " with " +
394 std::to_string(n.ix.size()) + " index(es); it has " + std::to_string(tn.sizes.size()) +
395 " dimension(s)");
396 return n;
397 }
398 // A bare name: an index's value.
399 const auto ii = p_.index_of.find(name);
400 if (ii != p_.index_of.end()) {
401 if (!in_scope_[ii->second])
402 fail(32, where_ + ": index '" + name + "' at column " + std::to_string(col) + " is not in scope");
403 E n;
404 n.k = K::Idx;
405 n.id = ii->second;
406 return n;
407 }
408 if (p_.tensor_of.count(name))
409 fail(32, where_ + " reads '" + name + "' at column " + std::to_string(col) +
410 " without indices; it has " + std::to_string(p_.tensors[p_.tensor_of.at(name)].sizes.size()) +
411 " dimension(s)");
412 fail(32, where_ + ": '" + name + "' at column " + std::to_string(col) + " is not declared");
413 }
414
415 Program& p_;
416 const std::vector<Token>& t_;
417 size_t i_;
418 std::string where_;
419 ReadRules rules_;
420 std::vector<bool> in_scope_;
421};
422
423// name '[' index (',' index)* ']' at toks[pos]; returns the name and the index ids; pos moves past it.
424std::pair<std::string, std::vector<int>> head(const Program& p, const std::vector<Token>& toks, size_t& pos,
425 const std::string& where, bool distinct) {
426 auto syntax = [&](const std::string& what) {
427 fail(31, where + " does not parse: " + what + " at column " + std::to_string(toks[pos].column));
428 };
429 if (toks[pos].kind != Tok::Ident) syntax("expected a name");
430 const std::string name = toks[pos++].text;
431 if (toks[pos].kind != Tok::LBracket) syntax("expected '[' after " + name);
432 ++pos;
433 std::vector<int> ids;
434 for (;;) {
435 if (toks[pos].kind != Tok::Ident) syntax("expected an index name");
436 const Token& t = toks[pos++];
437 const auto it = p.index_of.find(t.text);
438 if (it == p.index_of.end())
439 fail(32, where + ": index '" + t.text + "' at column " + std::to_string(t.column) + " is not declared");
440 if (distinct && std::find(ids.begin(), ids.end(), it->second) != ids.end())
441 fail(32, where + ": the assignment repeats index '" + t.text + "' at column " + std::to_string(t.column));
442 ids.push_back(it->second);
443 if (toks[pos].kind == Tok::Comma) {
444 ++pos;
445 continue;
446 }
447 if (toks[pos].kind != Tok::RBracket) syntax("expected ',' or ']'");
448 ++pos;
449 break;
450 }
451 return {name, ids};
452}
453
454std::vector<std::string> strings(const json& node, const char* key, bool required, bool nonempty) {
455 std::vector<std::string> out;
456 if (!node.contains(key) || node.at(key).is_null()) {
457 if (required) fail(31, std::string("'") + key + "' is missing");
458 return out;
459 }
460 const json& v = node.at(key);
461 if (!v.is_array()) fail(31, std::string("'") + key + "' is not a list of strings");
462 for (const auto& x : v) {
463 if (!x.is_string()) fail(31, std::string("'") + key + "' holds a value that is not a string");
464 out.push_back(x.get<std::string>());
465 }
466 if (nonempty && out.empty()) fail(31, std::string("'") + key + "' is empty");
467 return out;
468}
469
470} // namespace
471
472float Program::eval(const E& e) const {
473 switch (e.k) {
474 case K::Num: return e.v;
475 case K::Idx: return static_cast<float>(iv[e.id]);
476 case K::Read: {
477 const Tensor& t = tensors[e.id];
478 size_t flat = 0;
479 for (size_t d = 0; d < e.ix.size(); ++d) {
480 const long c = (e.ix[d].index >= 0 ? iv[e.ix[d].index] : 0) + e.ix[d].off;
481 if (c < 0 || c >= t.sizes[d]) return 0.0f; // outside the shape: +0
482 flat += static_cast<size_t>(c) * t.strides[d];
483 }
484 return data[e.id][flat];
485 }
486 case K::Neg: return -eval(e.a[0]);
487 case K::Add: { const float x = eval(e.a[0]); const float y = eval(e.a[1]); return x + y; }
488 case K::Sub: { const float x = eval(e.a[0]); const float y = eval(e.a[1]); return x - y; }
489 case K::Mul: { const float x = eval(e.a[0]); const float y = eval(e.a[1]); return x * y; }
490 case K::Div: { const float x = eval(e.a[0]); const float y = eval(e.a[1]); return x / y; }
491 case K::Pow: { const float x = eval(e.a[0]); const float y = eval(e.a[1]); return std::pow(x, y); }
492 case K::Exp: return std::exp(eval(e.a[0]));
493 case K::Log: return std::log(eval(e.a[0]));
494 case K::Sqrt: return std::sqrt(eval(e.a[0]));
495 case K::Tanh: return std::tanh(eval(e.a[0]));
496 case K::Erf: return std::erf(eval(e.a[0]));
497 case K::Abs: return std::fabs(eval(e.a[0]));
498 case K::Sigmoid: {
499 const float x = eval(e.a[0]);
500 const float ex = std::exp(-x);
501 const float den = 1.0f + ex;
502 return 1.0f / den;
503 }
504 case K::Relu: { const float x = eval(e.a[0]); return x > 0.0f ? x : 0.0f; }
505 case K::Max: { const float x = eval(e.a[0]); const float y = eval(e.a[1]); return x >= y ? x : y; }
506 case K::Min: { const float x = eval(e.a[0]); const float y = eval(e.a[1]); return x <= y ? x : y; }
507 case K::Where: return eval(e.a[0]) > 0.0f ? eval(e.a[1]) : eval(e.a[2]);
508 case K::Sum: {
509 float acc = 0.0f; // +0
510 const int n = index_sizes[e.id];
511 for (int j = 0; j < n; ++j) {
512 iv[e.id] = j;
513 const float x = eval(e.a[0]);
514 acc = acc + x;
515 }
516 return acc;
517 }
518 }
519 return 0.0f;
520}
521
522// The body at every point of the assignment's index space, row-major (the last index fastest).
523void Program::assign(const Assign& a, float* out) const {
524 const size_t k = a.lhs.size();
525 for (size_t d = 0; d < k; ++d) iv[a.lhs[d]] = 0;
526 const size_t n = tensors[a.tensor].numel;
527 for (size_t flat = 0; flat < n; ++flat) {
528 out[flat] = eval(a.body);
529 for (size_t d = k; d-- > 0;) {
530 if (++iv[a.lhs[d]] < index_sizes[a.lhs[d]]) break;
531 iv[a.lhs[d]] = 0;
532 }
533 }
534}
535
536Recurrence::Recurrence() = default;
537Recurrence::~Recurrence() = default;
540
542const std::vector<Recurrence::WeightDecl>& Recurrence::weights() const { return p_->weight_info; }
543const std::vector<size_t>& Recurrence::input_widths() const { return p_->in_widths; }
544size_t Recurrence::out_width() const { return p_->out_width; }
545bool Recurrence::exact() const { return p_->exact; }
546
548 auto P = std::make_unique<Program>();
549 Program& p = *P;
550 std::set<std::string> names; // every declared name, distinct across all kinds
551 auto declare = [&](const std::string& name, const std::string& what) {
552 if (!is_identifier(name)) fail(31, what + " name '" + name + "' is not an identifier");
553 if (is_reserved(name)) fail(31, what + " name '" + name + "' is reserved");
554 if (!names.insert(name).second) fail(31, "the name '" + name + "' is declared twice");
555 };
556 // index
557 if (!node.contains("index") || !node.at("index").is_object() || node.at("index").empty())
558 fail(31, "'index' is not a map of names to positive integers");
559 for (const auto& kv : node.at("index").items()) {
560 if (!kv.value().is_number_integer() || kv.value().get<int64_t>() <= 0)
561 fail(31, "index '" + kv.key() + "' has size " + kv.value().dump() + ", not a positive integer");
562 if (kv.value().get<int64_t>() > (1 << 24)) // a capability limit (section 10.3), not a validation rule
563 throw std::runtime_error("IR recurrence: index '" + kv.key() + "' of size " + kv.value().dump() +
564 " is larger than ember's interpreter takes (2^24)");
565 declare(kv.key(), "index");
566 p.index_of[kv.key()] = static_cast<int>(p.index_names.size());
567 p.index_names.push_back(kv.key());
568 p.index_sizes.push_back(static_cast<int>(kv.value().get<int64_t>()));
569 }
570 p.iv.assign(p.index_names.size(), 0);
571 auto add_tensor = [&](const std::string& name, TKind kind, const std::vector<int>& ids, std::vector<int>& list) {
572 Tensor t;
573 t.name = name;
574 t.kind = kind;
575 for (int id : ids) t.sizes.push_back(p.index_sizes[id]);
576 t.strides.assign(t.sizes.size(), 1);
577 for (size_t d = t.sizes.size(); d-- > 1;) t.strides[d - 1] = t.strides[d] * static_cast<size_t>(t.sizes[d]);
578 for (int s : t.sizes) {
579 t.numel *= static_cast<size_t>(s);
580 if (t.numel > (size_t(1) << 28)) // a capability limit (section 10.3)
581 throw std::runtime_error("IR recurrence: tensor '" + name + "' holds more than 2^28 elements, more than ember's interpreter takes");
582 }
583 t.pos = static_cast<int>(list.size());
584 const int id = static_cast<int>(p.tensors.size());
585 p.tensor_of[name] = id;
586 p.tensors.push_back(std::move(t));
587 list.push_back(id);
588 return id;
589 };
590 // inputs: one declaration per value of in
591 size_t n_in = 0;
592 if (node.contains("in")) n_in = node.at("in").is_array() ? node.at("in").size() : 1;
593 const std::vector<std::string> inputs = strings(node, "inputs", true, true);
594 if (inputs.size() != n_in)
595 fail(31, "'inputs' declares " + std::to_string(inputs.size()) + " value(s) for the " + std::to_string(n_in) +
596 " of 'in'");
597 for (size_t v = 0; v < inputs.size(); ++v) {
598 const std::string where = "inputs[" + std::to_string(v) + "]";
599 const auto toks = lex(inputs[v], where);
600 size_t pos = 0;
601 const auto [name, ids] = head(p, toks, pos, where, false);
602 if (toks[pos].kind != Tok::End)
603 fail(31, where + " does not parse: unexpected '" + toks[pos].text + "' at column " + std::to_string(toks[pos].column));
604 declare(name, "input");
605 const int id = add_tensor(name, TKind::Input, ids, p.inputs);
606 p.in_widths.push_back(p.tensors[id].numel);
607 }
608 // weights: declarations to tensor names
609 if (node.contains("weights") && !node.at("weights").is_null()) {
610 if (!node.at("weights").is_object()) fail(31, "'weights' is not a map of declarations to tensor names");
611 for (const auto& kv : node.at("weights").items()) {
612 if (!kv.value().is_string() || kv.value().get<std::string>().empty())
613 fail(31, "weight '" + kv.key() + "' does not name a tensor");
614 const std::string where = "weights '" + kv.key() + "'";
615 const auto toks = lex(kv.key(), where);
616 size_t pos = 0;
617 const auto [name, ids] = head(p, toks, pos, where, false);
618 if (toks[pos].kind != Tok::End)
619 fail(31, where + " does not parse: unexpected '" + toks[pos].text + "' at column " + std::to_string(toks[pos].column));
620 declare(name, "weight");
621 const int id = add_tensor(name, TKind::Weight, ids, p.weights);
622 p.weight_info.push_back({name, kv.value().get<std::string>(), p.tensors[id].numel});
623 }
624 }
625 // The assignments' heads first (the names every body may read), then the bodies.
626 auto parse_heads = [&](const std::vector<std::string>& src, const char* section, std::vector<Assign>& dst,
627 std::vector<std::string>& names_out) {
628 for (size_t k = 0; k < src.size(); ++k) {
629 Assign a;
630 a.where = std::string(section) + "[" + std::to_string(k) + "]";
631 a.toks = lex(src[k], a.where);
632 size_t pos = 0;
633 auto h = head(p, a.toks, pos, a.where, true);
634 if (a.toks[pos].kind != Tok::Eq)
635 fail(31, a.where + " does not parse: expected '=' at column " + std::to_string(a.toks[pos].column));
636 a.body_tok = pos + 1;
637 a.lhs = h.second;
638 names_out.push_back(h.first);
639 dst.push_back(std::move(a));
640 }
641 };
642 std::vector<std::string> init_names, let_names, update_names, readout_names;
643 const std::vector<std::string> init_src = strings(node, "init", false, false);
644 const std::vector<std::string> let_src = strings(node, "let", false, false);
645 const std::vector<std::string> update_src = strings(node, "update", true, true);
646 if (!node.contains("readout") || !node.at("readout").is_string()) fail(31, "'readout' is not one assignment");
647 parse_heads(update_src, "update", p.update, update_names);
648 parse_heads(let_src, "let", p.let, let_names);
649 parse_heads(init_src, "init", p.init, init_names);
650 {
651 std::vector<Assign> ro;
652 parse_heads({node.at("readout").get<std::string>()}, "readout", ro, readout_names);
653 ro[0].where = "readout";
654 p.readout = std::move(ro[0]);
655 }
656 for (size_t k = 0; k < p.update.size(); ++k) {
657 declare(update_names[k], "state");
658 p.update[k].tensor = add_tensor(update_names[k], TKind::State, p.update[k].lhs, p.states);
659 }
660 for (size_t k = 0; k < p.let.size(); ++k) {
661 declare(let_names[k], "let");
662 p.let[k].tensor = add_tensor(let_names[k], TKind::Let, p.let[k].lhs, p.lets);
663 }
664 {
665 declare(readout_names[0], "readout");
666 std::vector<int> outs;
667 p.readout.tensor = add_tensor(readout_names[0], TKind::Out, p.readout.lhs, outs);
668 p.out = p.readout.tensor;
669 p.out_width = p.tensors[p.out].numel;
670 }
671 p.init_of_state.assign(p.states.size(), -1);
672 for (size_t k = 0; k < p.init.size(); ++k) {
673 const auto it = p.tensor_of.find(init_names[k]);
674 if (it == p.tensor_of.end() || p.tensors[it->second].kind != TKind::State)
675 fail(33, p.init[k].where + " assigns '" + init_names[k] + "', which is not a state tensor (an update's)");
676 const Tensor& st = p.tensors[it->second];
677 if (p.init_of_state[st.pos] >= 0) fail(33, p.init[k].where + " assigns '" + init_names[k] + "' a second time");
678 std::vector<int> sizes;
679 for (int id : p.init[k].lhs) sizes.push_back(p.index_sizes[id]);
680 if (sizes != st.sizes) fail(33, p.init[k].where + " assigns '" + init_names[k] + "' with other sizes than its update");
681 p.init_of_state[st.pos] = static_cast<int>(k);
682 p.init[k].tensor = it->second;
683 }
684 // Bodies, with what each section reads (section 6.10.2).
685 auto body = [&](Assign& a, const ReadRules& r) { a.body = BodyParser(p, a.toks, a.body_tok, a.where, r, a.lhs).parse(); };
686 for (auto& a : p.init) body(a, ReadRules{false, true, false, 0});
687 for (size_t k = 0; k < p.let.size(); ++k) body(p.let[k], ReadRules{true, true, true, static_cast<int>(k)});
688 for (auto& a : p.update) body(a, ReadRules{true, true, true, static_cast<int>(p.let.size())});
689 body(p.readout, ReadRules{true, true, true, static_cast<int>(p.let.size())});
690 for (auto* v : {&p.init, &p.let, &p.update})
691 for (auto& a : *v) std::vector<Token>().swap(a.toks);
692 std::vector<Token>().swap(p.readout.toks);
693 // The public description and the scratch.
694 for (size_t s = 0; s < p.states.size(); ++s) {
695 const Tensor& t = p.tensors[p.states[s]];
696 StateTensor st;
697 st.name = t.name;
698 for (int x : t.sizes) st.shape.push_back(x);
699 st.numel = t.numel;
700 st.init_body = p.init_of_state[s] >= 0;
701 p.state_info.push_back(std::move(st));
702 p.new_state.emplace_back(t.numel);
703 }
704 for (int id : p.lets) p.let_buf.emplace_back(p.tensors[id].numel);
705 p.data.assign(p.tensors.size(), nullptr);
706 Recurrence r;
707 r.p_ = std::move(P);
708 return r;
709}
710
711std::string Recurrence::slot_errors(const json& slot) const {
712 const std::string name = slot.contains("name") ? slot.at("name").dump() : "?";
713 if (slot.value("op", json()) != "recurrence") return "the slot " + name + " of a recurrence is not of op recurrence";
714 if (!slot.contains("tensors") || !slot.at("tensors").is_array()) return "the slot " + name + " declares no tensors";
715 std::map<std::string, const json*> declared;
716 for (const auto& t : slot.at("tensors")) {
717 if (!t.is_object() || !t.contains("name") || !t.at("name").is_string())
718 return "the slot " + name + " holds a tensor without a name";
719 if (!declared.emplace(t.at("name").get<std::string>(), &t).second)
720 return "the slot " + name + " declares tensor '" + t.at("name").get<std::string>() + "' twice";
721 }
722 for (const auto& st : p_->state_info) {
723 const auto it = declared.find(st.name);
724 if (it == declared.end()) return "the slot " + name + " has no tensor '" + st.name + "' (the update assigns it)";
725 const json& t = *it->second;
726 const json want_shape = st.shape;
727 if (t.value("shape", json()) != want_shape)
728 return "the slot " + name + "'s tensor '" + st.name + "' has shape " + t.value("shape", json()).dump() +
729 ", the update's is " + want_shape.dump();
730 const json init = t.value("init", json());
731 if (st.init_body ? init != "body" : init != json(0))
732 return "the slot " + name + "'s tensor '" + st.name + "' has init " + init.dump() + ", not " +
733 (st.init_body ? "\"body\" (the node's init assigns it)" : "0 (the node's init does not assign it)");
734 }
735 if (declared.size() != p_->state_info.size()) {
736 for (const auto& kv : declared) {
737 bool found = false;
738 for (const auto& st : p_->state_info) found = found || st.name == kv.first;
739 if (!found) return "the slot " + name + "'s tensor '" + kv.first + "' is not one the update assigns";
740 }
741 }
742 return {};
743}
744
745void Recurrence::init(const std::vector<const float*>& weights, const std::vector<float*>& state) const {
746 const Program& p = *p_;
747 for (size_t w = 0; w < p.weights.size(); ++w) p.data[p.weights[w]] = weights[w];
748 for (size_t s = 0; s < p.states.size(); ++s) {
749 const int k = p.init_of_state[s];
750 if (k < 0)
751 std::fill(state[s], state[s] + p.state_info[s].numel, 0.0f);
752 else
753 p.assign(p.init[k], state[s]);
754 }
755}
756
757void Recurrence::step(const std::vector<const float*>& in, const std::vector<const float*>& weights,
758 const std::vector<float*>& state, float* out) const {
759 const Program& p = *p_;
760 for (size_t v = 0; v < p.inputs.size(); ++v) p.data[p.inputs[v]] = in[v];
761 for (size_t w = 0; w < p.weights.size(); ++w) p.data[p.weights[w]] = weights[w];
762 for (size_t s = 0; s < p.states.size(); ++s) p.data[p.states[s]] = state[s];
763 for (size_t l = 0; l < p.lets.size(); ++l) {
764 p.assign(p.let[l], p.let_buf[l].data());
765 p.data[p.lets[l]] = p.let_buf[l].data();
766 }
767 // The whole new state from the state before the token, then it replaces the old.
768 for (size_t s = 0; s < p.states.size(); ++s) p.assign(p.update[s], p.new_state[s].data());
769 for (size_t s = 0; s < p.states.size(); ++s) std::copy(p.new_state[s].begin(), p.new_state[s].end(), state[s]);
770 p.assign(p.readout, out);
771}
772
773} // namespace rec
774} // namespace ember_ir
static Recurrence compile(const nlohmann::json &node)
std::unique_ptr< Program > p_
const std::vector< size_t > & input_widths() const
const std::vector< WeightDecl > & weights() const
const float * in[expr::Expr::kMaxInputs]
const float * p[kIrDepthSourcesMax]
const float * col[expr::kMaxColumns]
int n
std::string text
std::vector< float > v
size_t column
const char * name
enum ember_ir::expr::@33::Value::Shape shape
Kind kind
float c
size_t body_tok
size_t numel
size_t i_
const std::vector< Token > & t_
ReadRules rules_
std::vector< Token > toks
std::vector< int > sizes
bool integer
std::vector< size_t > strides
std::vector< int > lhs
std::vector< bool > in_scope_
std::string where
std::vector< Ix > ix
std::string where_
std::vector< E > a
Program & p_
bool inputs
bool weights
node expr
node list
node out
node required
size_t j
Definition api.h:20
nlohmann::json json
Definition server.cpp:68
std::vector< std::vector< float > > let_buf
std::vector< Recurrence::StateTensor > state_info
std::map< std::string, int > index_of
std::vector< Assign > let
std::map< std::string, int > tensor_of
std::vector< Assign > init
std::vector< size_t > in_widths
std::vector< std::string > index_names
std::vector< Recurrence::WeightDecl > weight_info
std::vector< std::vector< float > > new_state
std::vector< int > init_of_state
float eval(const E &e) const
std::vector< const float * > data
std::vector< Assign > update
std::vector< Tensor > tensors
void assign(const Assign &a, float *out) const