ember.cpp 0.1.0
C++ inference engine for Llama-family and paperkiln models (CPU, CUDA, WebAssembly)
Loading...
Searching...
No Matches
ember_ir::rec::Recurrence Class Reference

#include <graph_ir_recurrence.h>

Collaboration diagram for ember_ir::rec::Recurrence:
Collaboration graph

Classes

struct  StateTensor
 
struct  WeightDecl
 

Public Member Functions

const std::vector< StateTensor > & state () const
 
const std::vector< WeightDecl > & weights () const
 
const std::vector< size_t > & input_widths () const
 
size_t out_width () const
 
bool exact () const
 
std::string slot_errors (const nlohmann::json &slot) const
 
void init (const std::vector< const float * > &weights, const std::vector< float * > &state) const
 
void step (const std::vector< const float * > &in, const std::vector< const float * > &weights, const std::vector< float * > &state, float *out) const
 
 Recurrence ()
 
 ~Recurrence ()
 
 Recurrence (Recurrence &&) noexcept
 
Recurrence & operator= (Recurrence &&) noexcept
 

Static Public Member Functions

static Recurrence compile (const nlohmann::json &node)
 

Private Attributes

std::unique_ptr< Program > p_
 

Detailed Description

Definition at line 35 of file graph_ir_recurrence.h.

Constructor & Destructor Documentation

◆ Recurrence() [1/2]

ember_ir::rec::Recurrence::Recurrence ( )
default

◆ ~Recurrence()

ember_ir::rec::Recurrence::~Recurrence ( )
default

◆ Recurrence() [2/2]

ember_ir::rec::Recurrence::Recurrence ( Recurrence &&  )
defaultnoexcept

Member Function Documentation

◆ compile()

Recurrence ember_ir::rec::Recurrence::compile ( const nlohmann::json &  node)
static

Definition at line 547 of file graph_ir_recurrence.cpp.

547 {
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);
707 r.p_ = std::move(P);
708 return r;
709}
void init(const std::vector< const float * > &weights, const std::vector< float * > &state) const
const float * p[kIrDepthSourcesMax]
std::string text
std::vector< float > v
size_t column
const char * name
enum ember_ir::expr::@33::Value::Shape shape
Kind kind
size_t numel
std::vector< Token > toks
std::vector< int > sizes
std::vector< int > lhs
std::string where
std::vector< E > a
bool inputs
node list
size_t j
std::vector< Value > items
Definition ir_digest.cpp:67

References a, body, column, id, inputs, j, k, kind, list, name, p, pos, sizes, text, toks, v, and where.

Referenced by ember_ir::Interpreter::init().

◆ exact()

bool ember_ir::rec::Recurrence::exact ( ) const

Definition at line 545 of file graph_ir_recurrence.cpp.

545{ return p_->exact; }
std::unique_ptr< Program > p_

References p_.

◆ init()

void ember_ir::rec::Recurrence::init ( const std::vector< const float * > &  weights,
const std::vector< float * > &  state 
) const

Definition at line 745 of file graph_ir_recurrence.cpp.

745 {
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}
const std::vector< StateTensor > & state() const
const std::vector< WeightDecl > & weights() const

References ember_ir::rec::Program::data, j, k, p, p_, state, and weights.

◆ input_widths()

const std::vector< size_t > & ember_ir::rec::Recurrence::input_widths ( ) const

Definition at line 543 of file graph_ir_recurrence.cpp.

543{ return p_->in_widths; }

References p_.

◆ operator=()

Recurrence & ember_ir::rec::Recurrence::operator= ( Recurrence &&  )
defaultnoexcept

◆ out_width()

size_t ember_ir::rec::Recurrence::out_width ( ) const

Definition at line 544 of file graph_ir_recurrence.cpp.

544{ return p_->out_width; }

References p_.

◆ slot_errors()

std::string ember_ir::rec::Recurrence::slot_errors ( const nlohmann::json &  slot) const

Definition at line 711 of file graph_ir_recurrence.cpp.

711 {
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}
nlohmann::json json
Definition server.cpp:68

References j, name, and p_.

◆ state()

const std::vector< Recurrence::StateTensor > & ember_ir::rec::Recurrence::state ( ) const

Definition at line 541 of file graph_ir_recurrence.cpp.

541{ return p_->state_info; }

References p_, and ember_ir::rec::Program::state_info.

◆ step()

void ember_ir::rec::Recurrence::step ( const std::vector< const float * > &  in,
const std::vector< const float * > &  weights,
const std::vector< float * > &  state,
float *  out 
) const

Definition at line 757 of file graph_ir_recurrence.cpp.

758 {
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}
const float * in[expr::Expr::kMaxInputs]
node out

References ember_ir::rec::Program::data, in, j, out, p, p_, state, v, and weights.

◆ weights()

const std::vector< Recurrence::WeightDecl > & ember_ir::rec::Recurrence::weights ( ) const

Definition at line 542 of file graph_ir_recurrence.cpp.

542{ return p_->weight_info; }

References p_.

Member Data Documentation

◆ p_

std::unique_ptr<Program> ember_ir::rec::Recurrence::p_
private

Definition at line 76 of file graph_ir_recurrence.h.

Referenced by exact(), input_widths(), out_width(), and weights().


The documentation for this class was generated from the following files: