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

#include <graph_ir.h>

Collaboration diagram for ember_ir::Interpreter:
Collaboration graph

Classes

struct  AppIndex
 
struct  Buf
 
struct  RecSlot
 
struct  RecTensor
 
struct  RecurrentTensorInfo
 
struct  State
 
struct  Weight
 

Public Member Functions

 Interpreter (const std::string &ir_json, const GGUFData &gguf, bool use_gpu=false)
 
 ~Interpreter ()
 
 Interpreter (const Interpreter &)=delete
 
Interpreter & operator= (const Interpreter &)=delete
 
std::vector< float > forward (const std::vector< int > &tokens, int start_pos, bool all_rows=false, int tail_rows=0)
 
bool has_recurrent () const
 
int resume_point (int want) const
 
int vocab () const
 
int max_len () const
 
int n_layers () const
 
const std::string & family_hint () const
 
const std::vector< int > & last_depths () const
 
std::vector< int > held_positions (int application) const
 
bool on_gpu () const
 
const char * kv_format () const
 
size_t kv_cache_bytes () const
 
size_t kv_scratch_bytes () const
 
int state_k () const
 
bool turn_state () const
 
bool evicts () const
 
int kv_rows_held () const
 
const std::string & kv_slot () const
 
bool evict_declared () const
 
int seq_len () const
 
bool has_depth_route () const
 
std::vector< int > kv_positions (int application) const
 
std::vector< RecurrentTensorInfo > recurrent_tensors () const
 
std::vector< float > recurrent_values (const std::string &slot, const std::string &tensor, int application) const
 
void truncate (int n)
 
State save_state () const
 
void load_state (const State &s)
 

Private Types

using Env = std::unordered_map< std::string, Buf >
 

Private Member Functions

void init (const nlohmann::json &j, const std::string &ir_json, const GGUFData &gguf, bool use_gpu)
 
int kv_rows_in_use (int application) const
 
void run_nodes (const std::vector< Node > &nodes, int layer, Env &env, int T, int start_pos, bool head_last_only)
 
void attention_window (const Node &n, int layer, int &window, int &sinks) const
 
const std::vector< std::pair< float, float > > & rope_table (int layer) const
 
const std::vector< float > & weight (const std::string &name) const
 
const Weight & bound (const std::string &name) const
 
std::string layer_name (const std::string &pattern, int layer) const
 
void bind (const std::vector< Node > &nodes, int layer, const GGUFData &gguf)
 
void bind_tensor (const std::string &name, const GGUFData &gguf, bool keep_quantised=false)
 
void mor_step (int layer, Env &env, int T, int start_pos)
 
void ts_begin (const std::vector< int > &tokens, int start_pos)
 
void ts_evict_below (int seg)
 
int ts_seg_after (int p) const
 
int ts_segment_start (int m) const
 
void build_rope_tables (const GGUFData &gguf)
 
void set_rope_long (bool v)
 
std::vector< float > forward_impl (const std::vector< int > &tokens, int start_pos, bool all_rows, int tail_rows)
 
const RecSlot & rec_slot (const Node &n, const char *op) const
 
float * rec_tensor (const RecSlot &slot, const char *tensor, int layer, size_t floats)
 
void rec_begin (int start_pos, int T, bool all_rows)
 
void rec_row_done (const RecSlot &slot, int layer, int t)
 
void rec_end (int start_pos, int T)
 

Private Attributes

std::string kv_slot_
 
bool evict_declared_ = false
 
int seq_len_ = 0
 
bool app_index_ = false
 
std::vector< Node > embed_
 
std::vector< Node > block_
 
std::vector< Node > head_
 
std::map< std::pair< const Node *, int >, int > da_sources_
 
int n_layers_ = 0
 
int vocab_ = 0
 
int max_len_ = 0
 
std::vector< int > weight_map_
 
std::vector< int > mor_step_
 
std::vector< std::vector< int > > mor_rows_
 
int mor_R_ = 0
 
bool mor_gate_score_ = false
 
std::string mor_x_
 
std::string mor_out_
 
std::string mor_router_
 
std::vector< int > mor_depth_
 
std::vector< float > mor_g_
 
std::vector< float > mor_xin_
 
int attn_row_ = -1
 
int state_k_ = 0
 
std::string state_weight_
 
bool turn_state_ = false
 
bool evict_ = false
 
std::vector< int > ts_seg_
 
std::vector< int > ts_sidx_
 
std::vector< int > kv_pos_
 
std::vector< std::vector< int > > ts_vis_
 
int kv_row0_ = 0
 
int evicted_below_ = 0
 
std::string family_hint_
 
bool dequant_all_ = false
 
std::unordered_map< std::string, Weight > weights_
 
std::unordered_map< std::string, std::vector< int64_t > > shapes_
 
std::vector< KVCacheLayer > kv_
 
int kv_heads_ = 0
 
int head_dim_ = 0
 
int kv_fmt_ = 0
 
int window_ = 0
 
int sinks_ = 0
 
float rope_theta_ = 0.0f
 
int rope_dim_ = 0
 
bool rope_neox_ = false
 
float rope_freq_scale_ = 1.0f
 
std::vector< int > window_layer_
 
std::vector< float > rope_theta_layer_
 
std::vector< float > rope_scale_layer_
 
std::vector< float > rope_attn_layer_
 
std::vector< std::vector< std::pair< float, float > > > rope_tabs_
 
std::vector< int > rope_tab_short_
 
std::vector< int > rope_tab_long_
 
nlohmann::json rope_ff_
 
int rope_ff_ctx_ = 0
 
bool rope_long_ = false
 
std::vector< int > ids_
 
std::set< std::string > offset_applied_
 
bool gpu_ = false
 
std::vector< RecSlot > rec_slots_
 
size_t rec_floats_ = 0
 
std::vector< float > rec_init_
 
std::map< const Node *, std::shared_ptr< rec::Recurrence > > recs_
 
std::vector< float > rec_
 
int rec_pos_ = 0
 
std::map< int, std::vector< float > > rec_snaps_
 
size_t rec_max_snaps_ = 0
 
std::vector< int > rec_snap_rows_
 
std::vector< float > rec_rows_
 

Detailed Description

Definition at line 102 of file graph_ir.h.

Member Typedef Documentation

◆ Env

using ember_ir::Interpreter::Env = std::unordered_map<std::string, Buf>
private

Definition at line 230 of file graph_ir.h.

Constructor & Destructor Documentation

◆ Interpreter() [1/2]

ember_ir::Interpreter::Interpreter ( const std::string &  ir_json,
const GGUFData &  gguf,
bool  use_gpu = false 
)

Definition at line 1099 of file graph_ir.cpp.

1099 {
1100 const json j = parse_ir_document(ir_json); // a member name twice is refused (IR_SPEC 3.2, rule 35)
1101 try {
1103 } catch (const json::exception&) {
1104 // ember's own checks read the document as its schema shapes it: a document that is not is
1105 // refused naming the rules it breaks (IR_SPEC 10.1), not a JSON accessor's message.
1106 validate_ir(j, gguf);
1107 throw;
1108 }
1109}
void init(const nlohmann::json &j, const std::string &ir_json, const GGUFData &gguf, bool use_gpu)
size_t j
json parse_ir_document(const std::string &text)
void validate_ir(const json &doc, const GGUFData &gguf)
nlohmann::json json
Definition server.cpp:68

References init(), j, ember_ir::parse_ir_document(), and ember_ir::validate_ir().

◆ ~Interpreter()

ember_ir::Interpreter::~Interpreter ( )

Definition at line 1759 of file graph_ir.cpp.

1759 {
1760#ifdef HAS_CUDA
1761 free_device();
1762#endif
1763}

References j.

◆ Interpreter() [2/2]

ember_ir::Interpreter::Interpreter ( const Interpreter &  )
delete

Member Function Documentation

◆ attention_window()

void ember_ir::Interpreter::attention_window ( const Node &  n,
int  layer,
int &  window,
int &  sinks 
) const
private

Definition at line 1783 of file graph_ir.cpp.

1783 {
1784 window = has(n.attrs, "window") ? at_layer(n.attrs.at("window"), layer).get<int>()
1786 sinks = has(n.attrs, "sinks") ? at_layer(n.attrs.at("sinks"), layer).get<int>() : sinks_;
1787}
std::vector< int > window_layer_
Definition graph_ir.h:321
int n

References j, and n.

Referenced by init().

◆ bind()

void ember_ir::Interpreter::bind ( const std::vector< Node > &  nodes,
int  layer,
const GGUFData &  gguf 
)
private

Definition at line 1948 of file graph_ir.cpp.

1948 {
1949 for (const auto& n : nodes) {
1950 if (!applies(n, layer)) continue; // its tensors are needed only where it runs (IR_SPEC 3.8, revision 1.6)
1951 AppIndex ti(app_index_, n);
1952 // depth_attend over one source never reads its pseudo-query (IR_SPEC 5.18), so a file may leave it
1953 // out; with S >= 2 sources a missing one is refused here, at load (rule 25).
1954 if (n.op == "depth_attend" && has(n.attrs, "weight")) {
1955 const std::string w = layer_name(n.attrs.at("weight").get<std::string>(), layer);
1956 if (!gguf.tensor_infos_map.count(w)) {
1957 const auto it = da_sources_.find({&n, layer});
1958 const int S = it == da_sources_.end() ? 0 : it->second;
1959 if (S == 1) continue;
1960 throw std::runtime_error("IR_SPEC 5.18: depth_attend" +
1961 (layer >= 0 ? " at application " + std::to_string(layer) : std::string()) + " has " +
1962 std::to_string(S) + " sources, and its pseudo-query '" + w + "' is not in the GGUF");
1963 }
1964 }
1965 if (has(n.attrs, "weight")) {
1966 std::string w = layer_name(n.attrs.at("weight").get<std::string>(), layer);
1967 if (!gguf.tensor_infos_map.count(w) && has(n.attrs, "tied_to"))
1968 w = layer_name(n.attrs.at("tied_to").get<std::string>(), layer);
1969 bind_tensor(w, gguf, n.op == "linear" || n.op == "embedding");
1970 }
1971 if (has(n.attrs, "bias")) bind_tensor(layer_name(n.attrs.at("bias").get<std::string>(), layer), gguf);
1972 if (n.op == "attention" && has(n.attrs, "position_bias") && n.attrs.at("position_bias").is_object() &&
1973 n.attrs.at("position_bias").contains("weight") && n.attrs.at("position_bias").at("weight").is_string())
1974 bind_tensor(layer_name(n.attrs.at("position_bias").at("weight").get<std::string>(), layer), gguf);
1975 if (n.op == "attention" && has(n.attrs, "relative_kv") && n.attrs.at("relative_kv").is_object())
1976 for (const char* member : {"key", "value"})
1977 if (n.attrs.at("relative_kv").contains(member) && n.attrs.at("relative_kv").at(member).is_string())
1978 bind_tensor(layer_name(n.attrs.at("relative_kv").at(member).get<std::string>(), layer), gguf);
1979 // rmsnorm weight_offset c: the weight is read as w + c, rounded to f32 once at load (Gemma's (1 + w)
1980 // for a file holding HF's raw w; llama.cpp's convert already stores 1 + w).
1981 if (n.op == "rmsnorm" && has(n.attrs, "weight_offset") && n.attrs.at("weight_offset").get<float>() != 0.0f) {
1982 const std::string w = layer_name(n.attrs.at("weight").get<std::string>(), layer);
1983 if (offset_applied_.insert(w).second) {
1984 const float off = n.attrs.at("weight_offset").get<float>();
1985 for (float& x : weights_.at(w).f32) x = x + off;
1986 }
1987 }
1988 if (n.op == "moe_ffn") {
1989 const std::string norm = n.attrs.value("gate_norm", "renorm"), act = n.attrs.value("activation", "silu");
1990 if (norm != "renorm" && norm != "raw") throw std::runtime_error("IR moe_ffn gate_norm '" + norm + "' is not implemented");
1991 if (act != "silu") throw std::runtime_error("IR moe_ffn activation '" + act + "' is not implemented");
1992 bind_tensor(layer_name(n.attrs.at("router").get<std::string>(), layer), gguf);
1993 for (const char* part : {"gate", "up", "down"})
1994 bind_tensor(layer_name(n.attrs.at("experts").at(part).get<std::string>(), layer), gguf);
1995 if (has(n.attrs, "selection_bias")) { // ne {n_experts} (IR_SPEC 5.16, rule 44, revision 1.6)
1996 const std::string b = layer_name(n.attrs.at("selection_bias").get<std::string>(), layer);
1997 bind_tensor(b, gguf);
1998 const int E = at_layer(n.attrs.at("n_experts"), std::max(layer, 0)).get<int>();
1999 if (bound(b).numel != static_cast<size_t>(E) || shapes_.at(b).size() != 1)
2000 throw std::runtime_error("IR_SPEC 5.16 (rule 44): moe_ffn selection_bias '" + b + "' is not of ne {n_experts} = {" +
2001 std::to_string(E) + "}");
2002 }
2003 }
2004 if (n.op == "mla_attention") { // k_b ne {dn, r, H}, v_b ne {r, dv, H} (IR_SPEC 5.20, rule 42)
2005 const auto at = [&](const char* k) { return static_cast<int64_t>(at_layer(n.attrs.at(k), std::max(layer, 0)).get<int>()); };
2006 const int64_t H = at("heads"), r = at("kv_rank"), dn = at("qk_nope"), dv = at("v_head_dim");
2007 const std::pair<const char*, std::vector<int64_t>> parts[] = {{"k_b", {dn, r, H}}, {"v_b", {r, dv, H}}};
2008 for (const auto& [key, want] : parts) {
2009 const std::string w = layer_name(n.attrs.at(key).get<std::string>(), layer);
2010 bind_tensor(w, gguf);
2011 std::vector<int64_t> got = shapes_.at(w);
2012 while (got.size() < 3) got.push_back(1); // trailing ne of 1 (one head) may be left out
2013 if (got != want)
2014 throw std::runtime_error("IR_SPEC 5.20 (rule 42): mla_attention " + std::string(key) + " '" + w +
2015 "' is not of ne {" + std::to_string(want[0]) + ", " + std::to_string(want[1]) + ", " +
2016 std::to_string(want[2]) + "}");
2017 }
2018 }
2019 if (n.op == "ssm_scan") {
2020 const size_t sn = n.attrs.at("n").get<size_t>();
2021 const std::pair<const char*, size_t> parts[] = {{"a", sn * sn}, {"b", sn}, {"c", sn}, {"d", 1}};
2022 for (const auto& [part, numel] : parts) {
2023 const std::string w = layer_name(ssm_weight(n.attrs, part), layer);
2024 bind_tensor(w, gguf);
2025 if (bound(w).numel != numel)
2026 throw std::runtime_error("IR ssm_scan '" + w + "' has " + std::to_string(bound(w).numel) +
2027 " elements, expected " + std::to_string(numel));
2028 }
2029 }
2030 auto expect = [&](const std::string& w, size_t numel) {
2031 if (bound(w).numel != numel)
2032 throw std::runtime_error("IR " + n.op + " '" + w + "' has " + std::to_string(bound(w).numel) +
2033 " elements, expected " + std::to_string(numel));
2034 };
2035 if (n.op == "conv1d_causal") { // weight [channels, kernel], bias [channels] (bound above)
2036 const size_t C = n.attrs.at("channels").get<size_t>(), K = n.attrs.at("kernel").get<size_t>();
2037 expect(layer_name(n.attrs.at("weight").get<std::string>(), layer), C * K);
2038 if (has(n.attrs, "bias")) expect(layer_name(n.attrs.at("bias").get<std::string>(), layer), C);
2039 }
2040 if (n.op == "selective_scan") { // a [channels, d_state] as stored, d [channels] optional
2041 const size_t D = n.attrs.at("channels").get<size_t>(), N = n.attrs.at("d_state").get<size_t>();
2042 const std::string a = layer_name(n.attrs.at("a").get<std::string>(), layer);
2043 bind_tensor(a, gguf);
2044 expect(a, D * N);
2045 if (has(n.attrs, "d")) {
2046 const std::string d = layer_name(n.attrs.at("d").get<std::string>(), layer);
2047 bind_tensor(d, gguf);
2048 expect(d, D);
2049 }
2050 }
2051 if (n.op == "recurrence") { // each weight as many elements as its declaration, read row-major (IR_SPEC 6.10.1)
2052 const auto it = recs_.find(&n);
2053 if (it == recs_.end()) throw std::runtime_error("IR recurrence: the node was not compiled");
2054 for (const auto& wd : it->second->weights()) {
2055 const std::string w = layer_name(wd.tensor, layer);
2056 bind_tensor(w, gguf);
2057 if (bound(w).numel != wd.numel)
2058 throw std::runtime_error("IR_SPEC 6.10 (rule 34): recurrence weight " + wd.decl + " reads '" + w + "', which has " +
2059 std::to_string(bound(w).numel) + " elements; its declaration has " +
2060 std::to_string(wd.numel));
2061 }
2062 }
2063 }
2064}
std::set< std::string > offset_applied_
Definition graph_ir.h:339
std::map< const Node *, std::shared_ptr< rec::Recurrence > > recs_
Definition graph_ir.h:363
void bind_tensor(const std::string &name, const GGUFData &gguf, bool keep_quantised=false)
std::map< std::pair< const Node *, int >, int > da_sources_
Definition graph_ir.h:269
const Weight & bound(const std::string &name) const
std::unordered_map< std::string, Weight > weights_
Definition graph_ir.h:308
std::string layer_name(const std::string &pattern, int layer) const
std::unordered_map< std::string, std::vector< int64_t > > shapes_
Definition graph_ir.h:309
size_t numel
std::vector< E > a
bool weights

References a, j, k, n, numel, and off.

◆ bind_tensor()

void ember_ir::Interpreter::bind_tensor ( const std::string &  name,
const GGUFData &  gguf,
bool  keep_quantised = false 
)
private

Definition at line 1882 of file graph_ir.cpp.

1882 {
1883 if (auto bw = weights_.find(name); bw != weights_.end()) {
1884 Weight& w = bw->second;
1885 if (keep_quantised || !w.quantised()) return;
1886 // Bound quantised for a row-wise op, now needed whole (a norm, a bias): f32.
1887 w.f32.resize(w.numel);
1888 dequant_span_to_f32(w.type, w.qp, 0, w.numel, w.f32.data());
1889 std::vector<uint8_t>().swap(w.q);
1890 w.qp = nullptr;
1891 w.qbytes = 0;
1892 return;
1893 }
1894 auto it = gguf.tensor_infos_map.find(name);
1895 if (it == gguf.tensor_infos_map.end()) throw std::runtime_error("IR weight '" + name + "' is not in the GGUF");
1896 const GGUFTensorInfo& info = it->second;
1897 const uint8_t* base = gguf.mapped_tensor_data
1898 ? static_cast<const uint8_t*>(gguf.mapped_tensor_data) + gguf.offset_diff_for_mmap
1899 : gguf.tensor_data.data();
1900 const uint8_t* src = base + info.offset;
1901 std::vector<int64_t> shape(info.shape.begin(), info.shape.end());
1902 const GGMLType t = info.type;
1906 k_quant;
1907 const int64_t width = k_quant ? GGML_QK_K : 32; // elements per block
1909 if (keep_quantised && !dequant_all_ && !shape.empty() && (half || (block_type && shape[0] % width == 0))) {
1910 // Rows (ne0 elements) start on block boundaries, so a row is a whole
1911 // number of blocks and dequantises on its own.
1912 const size_t bytes = half ? info.num_elements * 2 : info.num_elements / width * ggml_type_size(t);
1913 if (info.size_in_bytes < bytes)
1914 throw std::runtime_error("IR weight '" + name + "' is shorter than its element count implies");
1915 Weight w;
1916 if (gguf.mapped_tensor_data) {
1917 w.qp = src; // read in place: the mapping lives as long as the GGUFData
1918 } else {
1919 w.q.assign(src, src + bytes);
1920 w.qp = w.q.data(); // a vector's buffer survives the moves below
1921 }
1922 w.qbytes = bytes;
1923 w.type = t;
1924 w.numel = info.num_elements;
1925 shapes_[name] = shape;
1926 weights_[name] = std::move(w);
1927 return;
1928 }
1929 std::vector<float> out;
1930 if (info.type == GGMLType::GGML_TYPE_F32) {
1931 out.resize(info.num_elements);
1932 std::memcpy(out.data(), src, info.num_elements * sizeof(float));
1933 } else if (info.type == GGMLType::GGML_TYPE_Q8_0) {
1934 std::vector<block_q8_0> blocks(info.size_in_bytes / sizeof(block_q8_0));
1935 std::memcpy(blocks.data(), src, blocks.size() * sizeof(block_q8_0));
1937 } else if (!dequant_legacy_to_f32(info, src, out)) {
1938 throw std::runtime_error("IR weight '" + name + "': tensor type " + ggml_type_name(info.type) +
1939 " is not supported by ember's interpreter");
1940 }
1941 shapes_[name] = shape;
1942 Weight w;
1943 w.numel = out.size();
1944 w.f32 = std::move(out);
1945 weights_[name] = std::move(w);
1946}
GGMLType
Enumeration of GGML tensor data types.
Definition ggml_types.h:21
@ GGML_TYPE_F32
Definition ggml_types.h:22
@ GGML_TYPE_BF16
Definition ggml_types.h:40
@ GGML_TYPE_Q5_0
Definition ggml_types.h:27
@ GGML_TYPE_F16
Definition ggml_types.h:23
@ GGML_TYPE_Q6_K
Definition ggml_types.h:35
@ GGML_TYPE_Q4_1
Definition ggml_types.h:25
@ GGML_TYPE_Q8_0
Definition ggml_types.h:29
@ GGML_TYPE_Q5_K
Definition ggml_types.h:34
@ GGML_TYPE_Q5_1
Definition ggml_types.h:28
@ GGML_TYPE_Q4_K
Definition ggml_types.h:33
@ GGML_TYPE_Q4_0
Definition ggml_types.h:24
constexpr size_t GGML_QK_K
Block size constants for different quantization formats.
Definition gguf_parser.h:42
const char * name
enum ember_ir::expr::@33::Value::Shape shape
node out
size_t ggml_type_size(GGMLType type)
Gets the size in bytes of a GGML type.
const char * ggml_type_name(GGMLType type)
Gets the string name of a GGML type.
void dequantize_vector_q8_0_to_f32(const std::vector< block_q8_0 > &q_weights, std::vector< float > &f32_weights, size_t total_num_elements, int log_first_n_blocks)
Dequantizes a vector of Q8_0 blocks to a vector of float32.
Information about a tensor stored in a GGUF file.
size_t size_in_bytes
size_t num_elements
uint64_t offset
std::vector< uint64_t > shape
Simple 8-bit quantized block structure.
bool dequant_legacy_to_f32(const GGUFTensorInfo &info, const uint8_t *src, std::vector< float > &out)
bool dequant_span_to_f32(GGMLType t, const uint8_t *src, size_t first, size_t n, float *out)

References dequant_legacy_to_f32(), dequant_span_to_f32(), dequantize_vector_q8_0_to_f32(), ember_ir::Interpreter::Weight::f32, GGML_QK_K, GGML_TYPE_BF16, GGML_TYPE_F16, GGML_TYPE_F32, ggml_type_name(), GGML_TYPE_Q4_0, GGML_TYPE_Q4_1, GGML_TYPE_Q4_K, GGML_TYPE_Q5_0, GGML_TYPE_Q5_1, GGML_TYPE_Q5_K, GGML_TYPE_Q6_K, GGML_TYPE_Q8_0, ggml_type_size(), j, name, GGUFTensorInfo::num_elements, ember_ir::Interpreter::Weight::numel, GGUFTensorInfo::offset, out, ember_ir::Interpreter::Weight::qp, GGUFTensorInfo::shape, shape, GGUFTensorInfo::size_in_bytes, and GGUFTensorInfo::type.

◆ bound()

const Interpreter::Weight & ember_ir::Interpreter::bound ( const std::string &  name) const
private

Definition at line 2345 of file graph_ir.cpp.

2345 {
2346 auto it = weights_.find(name);
2347 if (it == weights_.end()) throw std::runtime_error("IR weight '" + name + "' was not bound");
2348 return it->second;
2349}

References j, and name.

◆ build_rope_tables()

void ember_ir::Interpreter::build_rope_tables ( const GGUFData &  gguf)
private

Definition at line 1802 of file graph_ir.cpp.

1802 {
1803 rope_tabs_.clear();
1804 rope_tab_short_.clear();
1805 rope_tab_long_.clear();
1806 rope_ff_ctx_ = 0;
1807 if (rope_theta_layer_.empty()) return;
1808 const int half = rope_dim_ / 2;
1809 std::string short_name, long_name; // tensor names as written ({i} allowed)
1810 std::vector<float> values; // freq_factors given by value (revision 1.6: YaRN's divisors), each as f32
1811 if (rope_ff_.is_array()) { // n_rot / 2 positive numbers (rule 41, validate_ir)
1812 for (const auto& v : rope_ff_) values.push_back(v.get<float>());
1813 if (values.size() != static_cast<size_t>(half))
1814 throw std::runtime_error("IR_SPEC 5.14 (rule 41): rope freq_factors holds " + std::to_string(values.size()) +
1815 " values, not n_rot / 2 = " + std::to_string(half));
1816 short_name = "=values"; // the table's key; no tensor is read
1817 } else if (rope_ff_.is_string()) {
1818 short_name = rope_ff_.get<std::string>();
1819 } else if (rope_ff_.is_object()) { // {short, long, original_ctx}: rule 41 holds (validate_ir)
1820 short_name = rope_ff_.at("short").get<std::string>();
1821 long_name = rope_ff_.at("long").get<std::string>();
1822 rope_ff_ctx_ = rope_ff_.at("original_ctx").get<int>();
1823 }
1824 std::map<std::tuple<float, float, float, std::string>, int> index;
1825 auto table = [&](int l, const std::string& pattern) -> int {
1826 const float theta = rope_theta_layer_[l], fs = rope_scale_layer_[l], m = rope_attn_layer_.empty() ? 1.0f : rope_attn_layer_[l];
1827 if (!(theta > 0.0f)) return -1;
1828 const std::string name = pattern.empty() || pattern == "=values" ? pattern : layer_name(pattern, l);
1829 const auto key = std::make_tuple(theta, fs, m, name);
1830 if (const auto it = index.find(key); it != index.end()) return it->second;
1831 std::vector<float> f;
1832 if (pattern == "=values") {
1833 f = values;
1834 } else if (!name.empty()) {
1836 f = weight(name);
1837 if (f.size() != static_cast<size_t>(half))
1838 throw std::runtime_error("IR_SPEC 5.14 (rule 41): rope freq_factors '" + name + "' holds " +
1839 std::to_string(f.size()) + " values, not n_rot / 2 = " + std::to_string(half));
1840 }
1841 std::vector<std::pair<float, float>> tab(static_cast<size_t>(max_len_) * half);
1842 for (int pos = 0; pos < max_len_; ++pos)
1843 for (int i = 0; i < rope_dim_; i += 2) {
1844 const float freq = 1.0f / std::pow(theta, float(i) / rope_dim_);
1845 float val = (static_cast<float>(pos) * fs) * freq;
1846 if (!f.empty()) val = val / f[i / 2];
1847 tab[static_cast<size_t>(pos) * half + i / 2] = {std::cos(val) * m, std::sin(val) * m};
1848 }
1849 rope_tabs_.push_back(std::move(tab));
1850 index[key] = static_cast<int>(rope_tabs_.size()) - 1;
1851 return index[key];
1852 };
1853 for (int l = 0; l < static_cast<int>(rope_theta_layer_.size()); ++l) {
1854 rope_tab_short_.push_back(table(l, short_name));
1855 if (!long_name.empty()) rope_tab_long_.push_back(table(l, long_name));
1856 }
1857 if (rope_ff_ctx_ > 0)
1858 Logger::info("[IR] LongRoPE: frequency factors '" + short_name + "' up to " + std::to_string(rope_ff_ctx_) +
1859 " positions, '" + long_name + "' past them; the set follows the sequence's length (a call or a "
1860 "truncate that changes it runs the sequence again)");
1861}
static void info(const std::string &message)
Definition logger.cpp:135
std::vector< int > rope_tab_short_
Definition graph_ir.h:327
std::vector< std::vector< std::pair< float, float > > > rope_tabs_
Definition graph_ir.h:326
std::vector< float > rope_scale_layer_
Definition graph_ir.h:322
std::vector< int > rope_tab_long_
Definition graph_ir.h:327
nlohmann::json rope_ff_
Definition graph_ir.h:332
std::vector< float > rope_theta_layer_
Definition graph_ir.h:322
const std::vector< float > & weight(const std::string &name) const
std::vector< float > rope_attn_layer_
Definition graph_ir.h:322
std::vector< float > v
Template get(const std::string &id)

References index, Logger::info(), j, name, pos, and v.

◆ evict_declared()

bool ember_ir::Interpreter::evict_declared ( ) const
inline

Definition at line 181 of file graph_ir.h.

181{ return evict_declared_; }

References evict_declared_.

Referenced by init().

◆ evicts()

bool ember_ir::Interpreter::evicts ( ) const
inline

Definition at line 169 of file graph_ir.h.

169{ return evict_; }

References evict_.

◆ family_hint()

const std::string & ember_ir::Interpreter::family_hint ( ) const
inline

Definition at line 138 of file graph_ir.h.

138{ return family_hint_; }
std::string family_hint_
Definition graph_ir.h:306

References family_hint_.

◆ forward()

std::vector< float > ember_ir::Interpreter::forward ( const std::vector< int > &  tokens,
int  start_pos,
bool  all_rows = false,
int  tail_rows = 0 
)

Definition at line 2957 of file graph_ir.cpp.

2957 {
2958 const int T = static_cast<int>(tokens.size());
2959 if (T == 0) return {};
2960 if (rope_ff_ctx_ <= 0) return forward_impl(tokens, start_pos, all_rows, tail_rows);
2961 // LongRoPE with two sets (IR_SPEC 5.14, 6.9; revision 1.5): n = start_pos + T selects the long set when
2962 // n > original_ctx, for this call's rows and every row the kv slot holds.
2963 if (start_pos < 0 || start_pos > static_cast<int>(ids_.size()))
2964 throw std::runtime_error("IR forward at position " + std::to_string(start_pos) + ": the sequence holds " +
2965 std::to_string(ids_.size()) + " ids");
2966 const bool want = start_pos + T > rope_ff_ctx_;
2967 if (want == rope_long_ || start_pos == 0) {
2969 std::vector<float> out = forward_impl(tokens, start_pos, all_rows, tail_rows);
2970 ids_.resize(start_pos);
2971 ids_.insert(ids_.end(), tokens.begin(), tokens.end());
2972 return out;
2973 }
2974 // The held rows were rotated with the other set: the whole sequence again from position 0, as Phi-3's own
2975 // generation recomputes its cache when the length first passes original_ctx; the same rows of logits come back.
2976 std::vector<int> all(ids_.begin(), ids_.begin() + start_pos);
2977 all.insert(all.end(), tokens.begin(), tokens.end());
2979 Logger::info("[IR] LongRoPE: the sequence's length " + std::to_string(all.size()) + (want ? " passes " : " is within ") +
2980 std::to_string(rope_ff_ctx_) + ": the " + (want ? "long" : "short") +
2981 " set, every kv row recomputed from position 0");
2982 std::vector<float> out;
2983 if (all_rows) {
2984 out = forward_impl(all, 0, true, 0);
2985 const size_t width = out.size() / all.size(); // the head's row width
2986 out.erase(out.begin(), out.end() - static_cast<size_t>(T) * width);
2987 } else {
2988 out = forward_impl(all, 0, false, std::min(T, std::max(1, tail_rows)));
2989 }
2990 ids_ = std::move(all);
2991 return out;
2992}
std::vector< int > ids_
Definition graph_ir.h:335
void set_rope_long(bool v)
std::vector< float > forward_impl(const std::vector< int > &tokens, int start_pos, bool all_rows, int tail_rows)

References Logger::info(), j, and out.

◆ forward_impl()

std::vector< float > ember_ir::Interpreter::forward_impl ( const std::vector< int > &  tokens,
int  start_pos,
bool  all_rows,
int  tail_rows 
)
private

Definition at line 2994 of file graph_ir.cpp.

2995 {
2996 const int T = static_cast<int>(tokens.size());
2997 if (T == 0) return {};
2999 throw std::runtime_error("IR recurrent state: positions " + std::to_string(start_pos) + ".." +
3000 std::to_string(start_pos + T - 1) + " exceed model.ctx (" + std::to_string(max_len_) + ")");
3001#ifdef HAS_CUDA
3002 if (gpu_) {
3003 std::vector<float> out = forward_device(tokens, start_pos, all_rows, tail_rows);
3004 seq_len_ = start_pos + T;
3005 return out;
3006 }
3007#endif
3008 if (state_k_ > 0 || turn_state_) ts_begin(tokens, start_pos);
3010 Env env;
3011 env["tokens"] = Buf{std::vector<float>(tokens.begin(), tokens.end()), 1};
3012 run_nodes(embed_, -1, env, T, start_pos, false);
3013 if (!mor_step_.empty()) {
3014 // Resuming at start_pos (a new sequence, prefix reuse, a rewind): each compact cache keeps
3015 // the rows of positions below it.
3016 for (auto& rows : mor_rows_)
3017 while (!rows.empty() && rows.back() >= start_pos) rows.pop_back();
3018 mor_depth_.clear();
3019 }
3020 for (int l = 0; l < n_layers_; ++l) {
3021 if (!mor_step_.empty() && mor_step_[l] >= 0)
3022 mor_step(l, env, T, start_pos);
3023 else
3024 run_nodes(block_, l, env, T, start_pos, false);
3025 }
3026 if (turn_state_) {
3027 // Every block has written this call's rows at kv_row0_ ..: they now hold these positions.
3028 for (int t = 0; t < T; ++t) kv_pos_.push_back(start_pos + t);
3029 // The text rows of segments before the last token's are invisible to every later query.
3030 if (evict_) ts_evict_below(ts_seg_.back());
3031 }
3032 if (rec_floats_ > 0) rec_end(start_pos, T);
3033 // Head ops are row-wise: run them on the last row only, or on the last tail_rows rows.
3034 const int rows = all_rows ? T : std::min(T, std::max(1, tail_rows));
3035 if (rows < T) {
3036 auto last_rows = [T, rows](Buf& b) {
3037 if (b.width > 0 && b.v.size() == static_cast<size_t>(T) * b.width)
3038 b.v.erase(b.v.begin(), b.v.end() - static_cast<size_t>(rows) * b.width);
3039 };
3040 for (auto& kv : env) {
3041 last_rows(kv.second);
3042 for (Buf& item : kv.second.items) last_rows(item); // a list's tensors
3043 }
3044 }
3046 auto it = env.find("logits");
3047 if (it == env.end()) throw std::runtime_error("IR head produced no 'logits'");
3048 seq_len_ = start_pos + T;
3049 return std::move(it->second.v);
3050}
void ts_evict_below(int seg)
std::vector< int > mor_step_
Definition graph_ir.h:283
void run_nodes(const std::vector< Node > &nodes, int layer, Env &env, int T, int start_pos, bool head_last_only)
std::vector< std::vector< int > > mor_rows_
Definition graph_ir.h:284
std::vector< int > kv_pos_
Definition graph_ir.h:303
void rec_end(int start_pos, int T)
void ts_begin(const std::vector< int > &tokens, int start_pos)
std::vector< int > ts_seg_
Definition graph_ir.h:303
std::vector< Node > head_
Definition graph_ir.h:266
std::vector< Node > block_
Definition graph_ir.h:266
std::vector< Node > embed_
Definition graph_ir.h:266
std::vector< int > mor_depth_
Definition graph_ir.h:288
void mor_step(int layer, Env &env, int T, int start_pos)
std::unordered_map< std::string, Buf > Env
Definition graph_ir.h:230
void rec_begin(int start_pos, int T, bool all_rows)
std::vector< Value > items
Definition ir_digest.cpp:67

References j, and out.

◆ has_depth_route()

bool ember_ir::Interpreter::has_depth_route ( ) const
inline

Definition at line 183 of file graph_ir.h.

183{ return !mor_step_.empty(); }

References mor_step_.

◆ has_recurrent()

bool ember_ir::Interpreter::has_recurrent ( ) const
inline

Definition at line 127 of file graph_ir.h.

127{ return rec_floats_ > 0; }

References rec_floats_.

◆ held_positions()

std::vector< int > ember_ir::Interpreter::held_positions ( int  application) const

Definition at line 3077 of file graph_ir.cpp.

3077 {
3079 throw std::runtime_error("IR held_positions: application " + std::to_string(application) + " of " +
3080 std::to_string(n_layers_));
3081 if (kv_heads_ <= 0) return {};
3082 if (turn_state_) return kv_pos_;
3083 if (!mor_step_.empty() && mor_step_[application] >= 0) return mor_rows_[application];
3084 std::vector<int> p(static_cast<size_t>(seq_len_));
3085 for (int i = 0; i < seq_len_; ++i) p[i] = i;
3086 return p;
3087}
const float * p[kIrDepthSourcesMax]

References j, and p.

◆ init()

void ember_ir::Interpreter::init ( const nlohmann::json &  j,
const std::string &  ir_json,
const GGUFData &  gguf,
bool  use_gpu 
)
private

Definition at line 1111 of file graph_ir.cpp.

1111 {
1112 const int version = j.at("ir_version").get<int>();
1113 // 1 is spec/IR_SPEC.md's first normative text of the same document as 0 (paperkiln, 25 Oct 2026)
1114 if (version != 0 && version != 1)
1115 throw std::runtime_error("IR version " + std::to_string(version) + " is not supported (ember reads 0 and 1)");
1116 family_hint_ = j.value("family_hint", "");
1117 embed_ = parse_nodes(j.at("embed"), "embed");
1118 block_ = parse_nodes(j.at("block"), "block");
1119 head_ = parse_nodes(j.at("head"), "head");
1120 for (const auto& n : head_)
1121 if (!kRowWiseOps.count(n.op)) throw std::runtime_error("IR head op '" + n.op + "' is not row-wise");
1122 n_layers_ = j.at("block").at("repeat").get<int>();
1123 if (n_layers_ < 1) throw std::runtime_error("IR_SPEC 3.3: block.repeat " + std::to_string(n_layers_) + " is not a positive integer");
1124 // Tied-block recursion (paperkiln ir.hpp weight_index): application i reads
1125 // stored layer m_i's tensors; its state slots, kv cache and per-layer
1126 // attributes stay its own (indexed by i). Stored layers are 0 .. max m_i, each
1127 // used, and their count is the file's <arch>.block_count.
1128 if (j.at("block").contains("weight_index")) {
1129 const json& wi = j.at("block").at("weight_index");
1130 if (!is_per_layer(wi) || !wi.at("per_layer").is_array())
1131 throw std::runtime_error("IR block weight_index must be {\"per_layer\": [m_0, ..]}");
1132 for (const auto& e : wi.at("per_layer")) {
1133 if (!e.is_number_integer() || e.get<int>() < 0)
1134 throw std::runtime_error("IR block weight_index entries must be integers >= 0");
1135 weight_map_.push_back(e.get<int>());
1136 }
1137 if (static_cast<int>(weight_map_.size()) != n_layers_)
1138 throw std::runtime_error("IR block weight_index has " + std::to_string(weight_map_.size()) + " entries for " +
1139 std::to_string(n_layers_) + " block applications");
1140 const int stored = *std::max_element(weight_map_.begin(), weight_map_.end()) + 1;
1141 std::vector<bool> used(static_cast<size_t>(stored), false);
1142 for (int m : weight_map_) used[m] = true;
1143 for (int m = 0; m < stored; ++m)
1144 if (!used[m]) throw std::runtime_error("IR block weight_index never uses stored layer " + std::to_string(m));
1145 const std::string arch = meta_string(gguf, "general.architecture").value_or("");
1146 if (const auto bc = meta_number(gguf, arch + ".block_count"); bc && static_cast<int>(*bc) != stored)
1147 throw std::runtime_error("IR block weight_index uses " + std::to_string(stored) + " stored layers, the file's " +
1148 arch + ".block_count is " + std::to_string(static_cast<int>(*bc)));
1149 std::string m;
1150 for (int v : weight_map_) m += (m.empty() ? "" : ", ") + std::to_string(v);
1151 Logger::info("[IR] tied-block recursion: " + std::to_string(n_layers_) + " applications of " +
1152 std::to_string(stored) + " stored layers, weight_index [" + m + "]");
1153 }
1154 vocab_ = j.at("model").at("vocab").get<int>();
1155 max_len_ = j.at("model").value("ctx", 0);
1156
1157 bool has_kv = false, kv_rows_active = false, evict_declared = false;
1158 for (const auto& s : j.at("block").value("state", json::array())) {
1159 const std::string kind = s.at("kind").get<std::string>();
1160 if (kind == "recurrent") {
1161 RecSlot slot{s.at("name").get<std::string>(), s.at("op").get<std::string>(), {}};
1162 if (!kRecurrentOps.count(slot.op))
1163 throw std::runtime_error("IR recurrent state '" + slot.name + "': op '" + slot.op + "' is not a recurrent op");
1164 if (s.value("dtype", "f32") != "f32")
1165 throw std::runtime_error("IR recurrent state '" + slot.name + "': dtype must be f32");
1166 if (!s.contains("tensors") || s.at("tensors").empty())
1167 throw std::runtime_error("IR recurrent state '" + slot.name + "' declares no tensors");
1168 for (const auto& t : s.at("tensors")) {
1169 RecTensor rt;
1170 rt.name = t.at("name").get<std::string>();
1171 rt.shape = t.at("shape").get<std::vector<int64_t>>();
1172 rt.floats = 1;
1173 for (int64_t e : rt.shape) {
1174 if (e <= 0) throw std::runtime_error("IR recurrent state '" + slot.name + "': bad shape for " + rt.name);
1175 rt.floats *= static_cast<size_t>(e);
1176 }
1177 // init 0, or "body" for op recurrence: the node's init assigns it (IR_SPEC 6.10.1; set after binding)
1178 if (t.contains("init") && t.at("init").is_string()) {
1179 if (slot.op != "recurrence" || t.at("init") != "body")
1180 throw std::runtime_error("IR recurrent state '" + slot.name + "': init " + t.at("init").dump() + " for " +
1181 rt.name + " (0, or \"body\" for op recurrence; IR_SPEC 6.3, 10.1 rule 13)");
1182 rt.init = 0.0f;
1183 } else {
1184 rt.init = t.value("init", 0.0f);
1185 }
1186 rt.offset = rec_floats_;
1187 rec_floats_ += rt.floats;
1188 slot.tensors.push_back(std::move(rt));
1189 }
1190 for (const auto& other : rec_slots_)
1191 if (other.name == slot.name) throw std::runtime_error("IR recurrent state '" + slot.name + "' is declared twice");
1192 rec_slots_.push_back(std::move(slot));
1193 continue;
1194 }
1195 if (kind != "kv") throw std::runtime_error("IR state kind '" + kind + "' has no CPU implementation in ember");
1196 has_kv = true;
1197 kv_slot_ = s.value("name", "kv");
1198 if (s.contains("kv_from")) throw std::runtime_error("IR kv state: kv_from (recursive KV sharing) is not implemented in ember");
1199 if (s.contains("kv_rows")) {
1200 if (s.at("kv_rows") != "active")
1201 throw std::runtime_error("IR kv state: kv_rows '" + s.at("kv_rows").dump() + "' is not implemented in ember");
1202 kv_rows_active = true;
1203 }
1204 if (has(s, "evict")) {
1205 if (s.at("evict") != "turn_state")
1206 throw std::runtime_error("IR kv state: evict " + s.at("evict").dump() + " is not implemented in ember (turn_state only)");
1207 evict_declared = true;
1208 }
1209 for (const char* k : {"heads", "kv_heads", "head_dim", "max_len"})
1210 if (has(s, k) && is_per_layer(s.at(k)))
1211 throw std::runtime_error(std::string("IR kv state: a per-layer '") + k + "' is not implemented in ember");
1212 kv_heads_ = s.at("kv_heads").get<int>();
1213 head_dim_ = s.at("head_dim").get<int>();
1214 max_len_ = s.at("max_len").get<int>();
1215 // Sliding window + sinks (paperkiln swa_attention). The cache keeps every
1216 // position up to max_len, which holds the first `sinks` and the last
1217 // `window` rows the rule needs.
1218 if (has(s, "window") && is_per_layer(s.at("window"))) {
1219 window_layer_ = s.at("window").at("per_layer").get<std::vector<int>>();
1220 if (static_cast<int>(window_layer_.size()) != n_layers_)
1221 throw std::runtime_error("IR kv state: per-layer window has " + std::to_string(window_layer_.size()) +
1222 " entries for " + std::to_string(n_layers_) + " blocks");
1223 window_ = *std::max_element(window_layer_.begin(), window_layer_.end());
1224 } else {
1225 window_ = s.value("window", 0);
1226 }
1227 if (has(s, "sinks") && is_per_layer(s.at("sinks")))
1228 throw std::runtime_error("IR kv state: per-layer sinks are not implemented in ember");
1229 sinks_ = s.value("sinks", 0);
1230 }
1231 for (const auto& n : block_) {
1232 if (n.op != "attention" && n.op != "mla_attention") continue;
1233 if (n.op == "mla_attention" && !(n.attrs.contains("causal") && n.attrs.at("causal") == json(true)))
1234 throw std::runtime_error("IR_SPEC 5.20 (rule 14): mla_attention causal must be true");
1235 if (!n.attrs.value("causal", true))
1236 throw std::runtime_error("IR attention with causal=false is not implemented in ember");
1237 for (int l = 0; l < n_layers_; ++l) {
1238 int w = 0, s = 0;
1239 attention_window(n, l, w, s);
1240 if (w < 0 || s < 0) throw std::runtime_error("IR attention window/sinks must be non-negative");
1241 // A windowed kv state declares what its cache holds; an attention node
1242 // reading it with another window or sink count is an exporter error.
1243 const int kvw = window_layer_.empty() ? window_ : window_layer_[l];
1244 if (kvw > 0 && (w != kvw || s != sinks_))
1245 throw std::runtime_error("IR attention window/sinks (" + std::to_string(w) + ", " + std::to_string(s) +
1246 ") in block " + std::to_string(l) + " differ from its kv state's (" +
1247 std::to_string(kvw) + ", " + std::to_string(sinks_) + ")");
1248 if (w > 0 && (l == 0 || window_layer_.size() > 0))
1249 Logger::info("[IR] sliding-window attention: block " + std::to_string(l) + " window " + std::to_string(w) +
1250 ", sinks " + std::to_string(s));
1251 }
1252 }
1253 for (const auto* sub : {&embed_, &block_, &head_})
1254 for (const auto& n : *sub)
1255 if (n.op == "gelu" && n.attrs.value("approx", "none") != "tanh" && n.attrs.value("approx", "none") != "none")
1256 throw std::runtime_error("IR gelu approx '" + n.attrs.value("approx", "") + "' is not implemented in ember");
1257 // The gating ops' attributes and arities (paperkiln ir.hpp): checked here so
1258 // a malformed node fails at load, not mid-generation.
1259 for (const auto* sub : {&embed_, &block_, &head_})
1260 for (const auto& n : *sub) {
1261 auto arity = [&](size_t k, bool at_least) {
1262 if (at_least ? n.in.size() < k : n.in.size() != k)
1263 throw std::runtime_error("IR " + n.op + " needs " + (at_least ? "at least " : "") + std::to_string(k) +
1264 " input(s), has " + std::to_string(n.in.size()));
1265 if (n.out.size() != 1) throw std::runtime_error("IR " + n.op + " needs one output");
1266 };
1267 if (n.op == "sub" || n.op == "mul_col" || n.op == "append") arity(2, false);
1268 if (n.op == "relu" || n.op == "sigmoid" || n.op == "affine" || n.op == "rms_row" || n.op == "scale") arity(1, false);
1269 if (n.op == "scale" && !has(n.attrs, "weight")) throw std::runtime_error("IR scale needs its weight [d]");
1270 if (n.op == "list" || n.op == "depth_attend") arity(1, true);
1271 if (n.op == "affine" && (!has(n.attrs, "scale") || !has(n.attrs, "shift")))
1272 throw std::runtime_error("IR affine needs float attributes scale and shift");
1273 if (n.op == "rms_row" && !has(n.attrs, "eps")) throw std::runtime_error("IR rms_row needs its eps attribute");
1274 for (const auto& kv : n.attrs.items()) { // per-layer attributes: block nodes, the attributes ember reads per block
1275 if (!is_per_layer(kv.value())) continue;
1276 const bool ok = sub == &block_ && (((n.op == "attention" || n.op == "mla_attention") &&
1277 (kv.key() == "window" || kv.key() == "sinks")) ||
1278 (n.op == "attention" && kv.key() == "softcap") ||
1279 (n.op == "rope" && (kv.key() == "theta" || kv.key() == "freq_scale" ||
1280 kv.key() == "attn_factor")) ||
1281 (n.op == "affine" && (kv.key() == "scale" || kv.key() == "shift")) ||
1282 (n.op == "depth_route" && kv.key() == "step") ||
1283 (n.op == "moe_ffn" && kv.key() == "scoring") ||
1284 kv.key() == "apply"); // revision 1.6 (IR_SPEC 3.8): any stateless node
1285 if (ok && kv.key() == "apply") // its entries are true or false (IR_SPEC 3.8, rule 43, revision 1.6)
1286 for (const auto& e : kv.value().at("per_layer"))
1287 if (!e.is_boolean())
1288 throw std::runtime_error("IR_SPEC 3.8 (rule 43): block " + n.op + ": apply entry " + e.dump() +
1289 " is not true or false");
1290 if (!ok && n.op == "rope" && kv.key() == "freq_factors") // structural (IR_SPEC 3.8, revision 1.5)
1291 throw std::runtime_error("IR_SPEC 3.8 (rule 6): rope freq_factors is structural and cannot be per-layer");
1292 if (!ok)
1293 throw std::runtime_error("IR " + n.op + ": a per-layer '" + kv.key() + "' is not implemented in ember");
1294 if (static_cast<int>(kv.value().at("per_layer").size()) != n_layers_)
1295 throw std::runtime_error("IR " + n.op + ": per-layer '" + kv.key() + "' has " +
1296 std::to_string(kv.value().at("per_layer").size()) + " entries for " +
1297 std::to_string(n_layers_) + " blocks");
1298 }
1299 if (n.op == "expr") { // a runtime-defined op: parsed here, so a bad string fails at load naming its column
1300 arity(1, true);
1301 if (!n.attrs.contains("expr") || !n.attrs.at("expr").is_string())
1302 throw std::runtime_error("IR expr needs its string attribute expr");
1303 try {
1304 expr::cached(n.attrs.at("expr").get<std::string>(), n.in.size());
1305 } catch (const std::invalid_argument& e) {
1306 throw std::runtime_error(std::string("IR ") + e.what());
1307 }
1308 }
1309 }
1310#ifdef HAS_CUDA
1312 throw std::runtime_error("IR uses an op or a shape that has no CUDA kernel (EMBER_IR_DEVICE=cpu runs it)");
1313#endif
1314 // Multi-head latent attention (IR_SPEC 5.20): its kv slot holds [c | kr] per position (kv_heads 1, head_dim
1315 // r + dr), and its rope rotates qr and kr, dr wide per head, not the slot's head_dim.
1316 bool mla = false;
1317 for (const auto& n : block_) mla = mla || n.op == "mla_attention";
1318 for (const auto& n : block_) {
1319 if (n.op == "mla_attention") { // IR_SPEC 5.20: the slot holds c and kr, r + dr values per position (rule 12)
1320 for (const char* k : {"heads", "kv_rank", "qk_nope", "qk_rope", "v_head_dim"})
1321 if (!n.attrs.contains(k) || !n.attrs.at(k).is_number_integer() || n.attrs.at(k).get<int>() <= 0)
1322 throw std::runtime_error(std::string("IR_SPEC 5.20 (rule 8): mla_attention needs a positive integer ") + k);
1323 for (const char* k : {"k_b", "v_b"})
1324 if (!n.attrs.contains(k) || !n.attrs.at(k).is_string())
1325 throw std::runtime_error(std::string("IR_SPEC 5.20 (rule 8): mla_attention needs its tensor ") + k);
1326 const int r = n.attrs.at("kv_rank").get<int>(), dr = n.attrs.at("qk_rope").get<int>();
1327 if (kv_heads_ != 1 || head_dim_ != r + dr)
1328 throw std::runtime_error("IR_SPEC 5.20 (rule 12): mla_attention reads a kv slot of kv_heads " +
1329 std::to_string(kv_heads_) + " and head_dim " + std::to_string(head_dim_) +
1330 "; it holds c and kr: kv_heads 1, head_dim kv_rank + qk_rope = " + std::to_string(r + dr));
1331 }
1332 }
1333 for (const auto& n : block_) {
1334 if (n.op == "rope") {
1335 const std::string pairing = n.attrs.value("pairing", "interleaved");
1336 if (pairing != "interleaved" && pairing != "neox")
1337 throw std::runtime_error("IR rope pairing '" + pairing + "' is not implemented in ember");
1338 if (n.attrs.value("rope_heads", "all") != "all")
1339 throw std::runtime_error("IR rope_heads '" + n.attrs.value("rope_heads", "") + "' is not implemented in ember");
1340 if (kv_heads_ > 0 && has(n.attrs, "kv_heads") && n.attrs.at("kv_heads").get<int>() != kv_heads_)
1341 throw std::runtime_error("IR rope kv_heads differs from its kv state's");
1342 // The table is the kv state's head_dim wide, as before, unless the node
1343 // names its own n_rot (partial rotary) or NEOX pairing: then n_rot
1344 // (default head_dim) must fit the kv state's heads.
1345 const int hd = n.attrs.at("head_dim").get<int>();
1346 int n_rot = head_dim_;
1347 if (has(n.attrs, "n_rot") || pairing == "neox" || mla) {
1348 n_rot = has(n.attrs, "n_rot") ? n.attrs.at("n_rot").get<int>() : hd;
1350 throw std::runtime_error("IR rope: n_rot " + std::to_string(n_rot) + " / head_dim " + std::to_string(hd) +
1351 " (kv state head_dim " + std::to_string(head_dim_) + ") is not usable");
1352 }
1353 std::vector<float> thetas(static_cast<size_t>(n_layers_));
1354 std::vector<float> scales(static_cast<size_t>(n_layers_), 1.0f), attn(static_cast<size_t>(n_layers_), 1.0f);
1355 for (int l = 0; l < n_layers_; ++l) {
1356 thetas[l] = at_layer(n.attrs.at("theta"), l).get<float>();
1357 if (has(n.attrs, "freq_scale")) scales[l] = at_layer(n.attrs.at("freq_scale"), l).get<float>();
1358 if (!(scales[l] > 0.0f)) throw std::runtime_error("IR rope freq_scale must be positive");
1359 // LongRoPE's / YaRN's attention scaling (IR_SPEC 5.14, revision 1.5): the table holds cos * m and sin * m.
1360 if (has(n.attrs, "attn_factor") && at_layer(n.attrs.at("attn_factor"), l).is_number())
1361 attn[l] = at_layer(n.attrs.at("attn_factor"), l).get<float>();
1362 }
1363 const json ff = has(n.attrs, "freq_factors") ? n.attrs.at("freq_factors") : json();
1364 if (rope_dim_ != 0 && (rope_theta_layer_ != thetas || rope_scale_layer_ != scales || rope_attn_layer_ != attn ||
1365 rope_dim_ != n_rot || rope_neox_ != (pairing == "neox") || rope_ff_ != ff))
1366 throw std::runtime_error(
1367 "IR rope nodes with different theta, freq_scale, attn_factor, freq_factors, n_rot or pairing are not "
1368 "implemented in ember");
1370 rope_scale_layer_ = scales;
1372 rope_ff_ = ff; // its form is rule 41's (validate_ir below); the tables are built after it
1373 rope_theta_ = thetas[0];
1374 rope_freq_scale_ = scales[0];
1375 rope_dim_ = n_rot;
1376 rope_neox_ = pairing == "neox";
1377 }
1378 }
1379 // The rmsnorm groups (per-head q/k norm) and the attention head counts.
1380 for (const auto& n : block_) {
1381 if (n.op == "rmsnorm" && has(n.attrs, "group") && n.attrs.at("group").get<int>() <= 0)
1382 throw std::runtime_error("IR rmsnorm group must be positive");
1383 if (n.op == "attention") {
1384 const int hq = n.attrs.at("heads").get<int>(), hkv = n.attrs.at("kv_heads").get<int>();
1385 if (hkv <= 0 || hq % hkv != 0)
1386 throw std::runtime_error("IR attention: heads " + std::to_string(hq) + " is not a multiple of kv_heads " +
1387 std::to_string(hkv));
1388 }
1389 }
1390 if (mla) {
1391 for (const auto& n : block_)
1392 if (n.op == "depth_route")
1393 throw std::runtime_error("IR mla_attention under depth_route is not implemented in ember");
1394 }
1395 // Mixture-of-Recursions (ir.hpp depth_route, llama_mor): one depth_route node, the block's
1396 // first, outside it nowhere; its steps one consecutive run of equal-length steps 0 .. R-1; causal
1397 // token choice; a kv state with kv_rows "active"; no recurrent state. A window and sinks count
1398 // positions over each application's rows (IR_SPEC 5.15 and 7.5, v1.4.2, Q16).
1399 for (const auto* sub : {&embed_, &head_})
1400 for (const auto& n : *sub)
1401 if (n.op == "depth_route") throw std::runtime_error("IR depth_route outside the block");
1402 for (size_t k = 0; k < block_.size(); ++k) {
1403 const Node& n = block_[k];
1404 if (n.op != "depth_route") continue;
1405 if (k != 0) throw std::runtime_error("IR depth_route must be the block's first node");
1406 if (n.in.size() != 1 || n.out.size() != 1) throw std::runtime_error("IR depth_route reads [x] and writes one output");
1407 if (n.attrs.value("route", "") != "token_choice")
1408 throw std::runtime_error("IR depth_route route '" + n.attrs.value("route", "") +
1409 "' is not implemented in ember (causal token choice only)");
1410 const std::string gate = n.attrs.value("gate", "");
1411 if (gate != "score" && gate != "none") throw std::runtime_error("IR depth_route gate '" + gate + "' is not score or none");
1412 mor_R_ = n.attrs.at("n_rec").get<int>();
1413 if (mor_R_ < 1) throw std::runtime_error("IR depth_route n_rec must be at least 1");
1414 if (!is_per_layer(n.attrs.at("step"))) throw std::runtime_error("IR depth_route step must be {\"per_layer\": [...]}");
1415 mor_step_ = n.attrs.at("step").at("per_layer").get<std::vector<int>>();
1416 if (static_cast<int>(mor_step_.size()) != n_layers_)
1417 throw std::runtime_error("IR depth_route step has " + std::to_string(mor_step_.size()) + " entries for " +
1418 std::to_string(n_layers_) + " block applications");
1419 // One run of applications with steps 0, 0, .., 1, 1, .., R-1, all steps the same length.
1420 int first = -1, last = -1;
1421 for (int i = 0; i < n_layers_; ++i)
1422 if (mor_step_[i] >= 0) {
1423 if (first < 0) first = i;
1424 last = i;
1425 }
1426 const int span = first < 0 ? 0 : last - first + 1;
1427 if (first < 0 || span % mor_R_ != 0) throw std::runtime_error("IR depth_route step: the recursion is not R equal steps");
1428 for (int i = 0; i < n_layers_; ++i) {
1429 const int want = i < first || i > last ? -1 : (i - first) / (span / mor_R_);
1430 if (mor_step_[i] != want)
1431 throw std::runtime_error("IR depth_route step: application " + std::to_string(i) + " has step " +
1432 std::to_string(mor_step_[i]) + ", expected " + std::to_string(want) +
1433 " (consecutive, ascending, equal-length steps)");
1434 }
1435 mor_gate_score_ = gate == "score";
1436 mor_x_ = n.in[0];
1437 mor_out_ = n.out[0];
1438 mor_router_ = n.attrs.at("weight").get<std::string>();
1439 if (mor_router_.find("{i}") != std::string::npos)
1440 throw std::runtime_error("IR depth_route weight names its tensor in full (no {i})");
1441 }
1442 if (!mor_step_.empty()) {
1443 if (!kv_rows_active) throw std::runtime_error("IR depth_route needs a kv state with kv_rows \"active\"");
1444 if (!rec_slots_.empty()) throw std::runtime_error("IR depth_route with recurrent state is not implemented in ember");
1445 for (const auto& n : block_)
1446 if (n.op == "attention" && has(n.attrs, "position_bias"))
1447 throw std::runtime_error("IR depth_route with attention position_bias is not implemented in ember");
1448 else if (n.op == "attention" && has(n.attrs, "relative_kv"))
1449 throw std::runtime_error("IR depth_route with attention relative_kv is not implemented in ember");
1450 mor_rows_.assign(n_layers_, {});
1451 }
1452 // Per-turn state tokens (paperkiln IR_SPEC_DRAFT.md 1.1, 1.3, 1.4): the extended embedding
1453 // (state_weight, one embedding in the embed subgraph, its name in full), attention "mask"
1454 // "turn_state" (window 0 and sinks 0 only), kv "evict" "turn_state" (every attention node
1455 // turn_state). Not with depth_route (MoR's compact caches) or, for eviction, recurrent state.
1456 for (const auto* sub : {&embed_, &block_, &head_})
1457 for (const auto& n : *sub) {
1458 if (n.op == "embedding" && has(n.attrs, "state_weight")) {
1459 if (sub != &embed_) throw std::runtime_error("IR embedding state_weight outside the embed subgraph");
1460 if (!state_weight_.empty()) throw std::runtime_error("IR: more than one embedding with a state_weight");
1461 state_weight_ = n.attrs.at("state_weight").get<std::string>();
1462 if (state_weight_.find("{i}") != std::string::npos)
1463 throw std::runtime_error("IR embedding state_weight names its tensor in full (no {i})");
1464 }
1465 if (n.op == "attention" && n.attrs.contains("mask")) {
1466 const std::string mask = n.attrs.at("mask").is_string() ? n.attrs.at("mask").get<std::string>() : "";
1467 if (mask != "causal" && mask != "turn_state")
1468 throw std::runtime_error("IR attention mask " + n.attrs.at("mask").dump() +
1469 " is not implemented in ember (causal or turn_state)");
1470 if (mask == "turn_state") turn_state_ = true;
1471 }
1472 }
1473 if (turn_state_) {
1474 for (const auto& n : block_) {
1475 if (n.op != "attention" || n.attrs.value("mask", "causal") != "turn_state") continue;
1476 for (int l = 0; l < n_layers_; ++l) {
1477 int w = 0, s = 0;
1478 attention_window(n, l, w, s);
1479 if (w != 0 || s != 0)
1480 throw std::runtime_error("IR attention mask turn_state needs window 0 and sinks 0 (block " + std::to_string(l) +
1481 " has window " + std::to_string(w) + ", sinks " + std::to_string(s) + ")");
1482 }
1483 }
1484 if (window_ > 0 || !window_layer_.empty() || sinks_ > 0)
1485 throw std::runtime_error("IR attention mask turn_state with a windowed kv state is not allowed");
1486 if (!mor_step_.empty() || kv_rows_active)
1487 throw std::runtime_error("IR attention mask turn_state with depth_route / kv_rows active is not implemented in ember");
1488 }
1489 {
1490 bool all_turn_state = true;
1491 for (const auto& n : block_)
1492 if (n.op == "attention" && n.attrs.value("mask", "causal") != "turn_state") all_turn_state = false;
1494 throw std::runtime_error("IR kv state evict turn_state needs every attention node to have mask turn_state");
1497 if (const char* e = std::getenv("EMBER_EVICT")) {
1498 if (std::string(e) == "0") evict_ = false;
1499 if (std::string(e) == "1" && turn_state_) {
1500 if (!all_turn_state)
1501 throw std::runtime_error("EMBER_EVICT=1: eviction needs every attention node to have mask turn_state");
1502 evict_ = true;
1503 }
1504 }
1505 if (evict_ && !rec_slots_.empty())
1506 throw std::runtime_error("IR kv state evict turn_state with recurrent state is not implemented in ember");
1507 }
1508 // EMBER_IR_CTX=N: hold at most N positions (a third-party IR declares the
1509 // model's whole trained context, e.g. 32768, which is ~0.8 GB of f32 KV for
1510 // Qwen2.5-0.5B).
1511 if (const char* c = std::getenv("EMBER_IR_CTX")) {
1512 const int cap = std::atoi(c);
1513 if (cap > 0 && max_len_ > cap) {
1514 Logger::info("[IR] EMBER_IR_CTX: the kv state holds " + std::to_string(cap) + " of the IR's " +
1515 std::to_string(max_len_) + " positions");
1516 max_len_ = cap;
1517 }
1518 }
1519 // EMBER_KV=f16|q8|q4 (kv_quant.h, R2): the kv cache's storage format; unset or f32 changes nothing.
1520 kv_fmt_ = ember_kvq::parse_format(std::getenv("EMBER_KV"));
1521 if (kv_fmt_ != ember_kvq::kF32 && head_dim_ > 0) {
1522 const std::string why = ember_kvq::shape_error(kv_fmt_, head_dim_);
1523 if (!why.empty()) throw std::runtime_error(std::string("EMBER_KV=") + ember_kvq::format_name(kv_fmt_) + ": " + why);
1524 for (const auto& n : block_) // its latent cache [c | kr] has its own write path, not quantised
1525 if (n.op == "mla_attention")
1526 throw std::runtime_error(std::string("EMBER_KV=") + ember_kvq::format_name(kv_fmt_) +
1527 ": mla_attention's latent cache is not quantised");
1528 Logger::info(std::string("[IR] EMBER_KV: the kv cache stores ") + ember_kvq::format_name(kv_fmt_));
1529 }
1530 bool has_attention = false;
1531 for (const auto& n : block_) has_attention = has_attention || n.op == "attention" || n.op == "mla_attention";
1532 if ((has_kv || has_attention) && (kv_heads_ <= 0 || head_dim_ <= 0 || max_len_ <= 0))
1533 throw std::runtime_error("IR block declares no usable kv state (kv_heads, head_dim, max_len)");
1534 if (max_len_ <= 0) throw std::runtime_error("IR model.ctx is required when no kv state bounds the sequence");
1535 for (const auto* sub : {&embed_, &head_})
1536 for (const auto& n : *sub)
1537 if (kRecurrentOps.count(n.op)) throw std::runtime_error("IR op '" + n.op + "' outside the block has no state");
1538 // Every recurrent node names a slot of its op whose tensors fit the node.
1539 for (const auto& n : block_) {
1540 if (n.op == "linear_attention") {
1541 const RecSlot& slot = rec_slot(n, "linear_attention");
1542 const int heads = n.attrs.at("heads").get<int>(), hd = n.attrs.at("head_dim").get<int>();
1543 if (n.attrs.value("feature_map", "elu_plus_one") != "elu_plus_one")
1544 throw std::runtime_error("IR linear_attention feature_map '" + n.attrs.value("feature_map", "") +
1545 "' is not implemented in ember");
1546 if (!n.attrs.value("causal", true))
1547 throw std::runtime_error("IR linear_attention with causal=false is not implemented in ember");
1548 if (n.in.size() != 3 || n.out.size() != 1) throw std::runtime_error("IR linear_attention reads [q, k, v] and writes one output");
1549 for (const char* t : {"num", "den"}) {
1550 bool found = false;
1551 for (const auto& rt : slot.tensors)
1552 if (rt.name == t) {
1553 found = true;
1554 if (rt.shape != std::vector<int64_t>{heads, hd})
1555 throw std::runtime_error("IR recurrent state '" + slot.name + "': " + t + " is not [heads, head_dim]");
1556 }
1557 if (!found) throw std::runtime_error("IR recurrent state '" + slot.name + "' has no tensor '" + t + "'");
1558 }
1559 } else if (n.op == "ssm_scan") {
1560 const RecSlot& slot = rec_slot(n, "ssm_scan");
1561 const int64_t sn = n.attrs.at("n").get<int64_t>();
1562 if (n.in.size() != 1 || n.out.size() != 1) throw std::runtime_error("IR ssm_scan reads [u] and writes one output");
1563 if (slot.tensors.size() != 1 || slot.tensors[0].name != "s" || slot.tensors[0].shape != std::vector<int64_t>{sn})
1564 throw std::runtime_error("IR recurrent state '" + slot.name + "' must hold one tensor s [n]");
1565 } else if (n.op == "conv1d_causal") {
1566 const RecSlot& slot = rec_slot(n, "conv1d_causal");
1567 const int64_t C = n.attrs.at("channels").get<int64_t>(), K = n.attrs.at("kernel").get<int64_t>();
1568 if (n.in.size() != 1 || n.out.size() != 1) throw std::runtime_error("IR conv1d_causal reads [x] and writes one output");
1569 if (K < 2 || C < 1) throw std::runtime_error("IR conv1d_causal needs channels >= 1 and kernel >= 2");
1570 if (slot.tensors.size() != 1 || slot.tensors[0].name != "x" ||
1571 slot.tensors[0].shape != std::vector<int64_t>{K - 1, C})
1572 throw std::runtime_error("IR recurrent state '" + slot.name + "' must hold one tensor x [kernel - 1, channels]");
1573 } else if (n.op == "selective_scan") {
1574 const RecSlot& slot = rec_slot(n, "selective_scan");
1575 const int64_t D = n.attrs.at("channels").get<int64_t>(), N = n.attrs.at("d_state").get<int64_t>();
1576 if (n.in.size() != 4 || n.out.size() != 1)
1577 throw std::runtime_error("IR selective_scan reads [x, dt, B, C] and writes one output");
1578 if (slot.tensors.size() != 1 || slot.tensors[0].name != "s" || slot.tensors[0].shape != std::vector<int64_t>{D, N})
1579 throw std::runtime_error("IR recurrent state '" + slot.name + "' must hold one tensor s [channels, d_state]");
1580 } else if (n.op == "recurrence") { // IR_SPEC 6.10, 10.1 rules 31-33 (graph_ir_recurrence.h)
1581 auto prog = std::make_shared<rec::Recurrence>(rec::Recurrence::compile(n.attrs));
1582 if (n.out.size() != 1) throw std::runtime_error("IR recurrence writes one output");
1583 const std::string name = has(n.attrs, "state") ? n.attrs.at("state").get<std::string>() : "";
1584 const json* decl = nullptr; // into the document (not a copy: it is read below)
1585 if (j.at("block").contains("state") && j.at("block").at("state").is_array())
1586 for (const auto& s : j.at("block").at("state"))
1587 if (s.is_object() && s.value("name", "") == name && s.value("kind", "") == "recurrent") decl = &s;
1588 if (!decl) throw std::runtime_error("IR_SPEC 6.10 (rule 33): recurrence names no recurrent slot '" + name + "'");
1589 const std::string why = prog->slot_errors(*decl);
1590 if (!why.empty()) throw std::runtime_error("IR_SPEC 6.10 (rule 33): " + why);
1591 rec_slot(n, "recurrence");
1592 for (const auto& other : block_)
1593 if (&other != &n && has(other.attrs, "state") && other.attrs.at("state") == name)
1594 throw std::runtime_error("IR_SPEC 6.10 (rule 33): the slot '" + name + "' of a recurrence is read by another node (" +
1595 other.op + ")");
1596 recs_[&n] = std::move(prog);
1597 }
1598 }
1599 if (rec_floats_ > 0) {
1600 // Every application's initial state: +0, or (op recurrence with "init" "body") the init body over the
1601 // application's weights, set after binding.
1602 rec_init_.assign(static_cast<size_t>(n_layers_) * rec_floats_, 0.0f);
1603 for (int l = 0; l < n_layers_; ++l)
1604 for (const auto& slot : rec_slots_)
1605 for (const auto& rt : slot.tensors)
1606 std::fill(rec_init_.begin() + l * rec_floats_ + rt.offset, rec_init_.begin() + l * rec_floats_ + rt.offset + rt.floats,
1607 rt.init);
1608 rec_ = rec_init_;
1609 // Snapshots are a whole state each; at most 64, and at most 256 MiB.
1610 const size_t budget = (256u << 20) / sizeof(float);
1611 rec_max_snaps_ = std::max<size_t>(2, std::min<size_t>(64, budget / rec_.size()));
1612 }
1613
1614 // Quantised weights stay in their blocks for large models on the CPU (the
1615 // memory saving pays for the per-row dequantisation); small models, like
1616 // the llama path's dequantize_all_if_small, and the CUDA path (f32 kernels)
1617 // dequantise at load. EMBER_IR_QUANT=1 / EMBER_IR_DEQUANT=1 force either.
1618 {
1619 size_t params = 0;
1620 for (const auto& kv : gguf.tensor_infos_map) params += kv.second.num_elements;
1621#ifdef HAS_CUDA
1622 const bool device = use_gpu;
1623#else
1624 const bool device = false;
1625#endif
1626 dequant_all_ = device || params <= 64u * 1000 * 1000;
1627 const char* q = std::getenv("EMBER_IR_QUANT");
1628 if (q && std::string(q) == "1" && !device) dequant_all_ = false;
1629 if (const char* dq = std::getenv("EMBER_IR_DEQUANT")) dequant_all_ = dequant_all_ || std::string(dq) == "1";
1630 }
1631 // depth_attend's source count S (IR_SPEC 5.18) is structural: the entries list and append put in its list
1632 // before the node, in run order (embed once, the block per application, the head), plus its other inputs.
1633 {
1634 std::unordered_map<std::string, int> entries; // list values: their tensor count
1635 auto walk = [&](const std::vector<Node>& nodes, int layer) {
1636 for (const auto& n : nodes) {
1637 const auto held = [&](const std::string& v) {
1638 const auto it = entries.find(v);
1639 return it == entries.end() ? 0 : it->second;
1640 };
1641 if (n.op == "list") {
1642 entries[n.out.at(0)] = static_cast<int>(n.in.size());
1643 } else if (n.op == "append") {
1644 entries[n.out.at(0)] = held(n.in.at(0)) + 1;
1645 } else {
1646 if (n.op == "depth_attend") da_sources_[{&n, layer}] = held(n.in.at(0)) + static_cast<int>(n.in.size()) - 1;
1647 for (const auto& o : n.out) entries.erase(o);
1648 }
1649 }
1650 };
1651 walk(embed_, -1);
1652 for (int l = 0; l < n_layers_; ++l) walk(block_, l);
1653 walk(head_, -1);
1654 }
1655 bind(embed_, -1, gguf);
1656 for (int l = 0; l < n_layers_; ++l) bind(block_, l, gguf);
1657 bind(head_, -1, gguf);
1658 // A recurrence's initial state (IR_SPEC 6.10.3): its init bodies over each application's weights.
1659 for (const auto& [np, prog] : recs_) {
1660 const RecSlot& slot = rec_slot(*np, "recurrence");
1661 AppIndex ai(app_index_, *np);
1662 for (int l = 0; l < n_layers_; ++l) {
1663 std::vector<const float*> w;
1664 for (const auto& wd : prog->weights()) w.push_back(weight(layer_name(wd.tensor, l)).data());
1665 std::vector<float*> st;
1666 for (const auto& s : prog->state()) {
1667 const RecTensor* rt = nullptr;
1668 for (const auto& t : slot.tensors)
1669 if (t.name == s.name) rt = &t;
1670 st.push_back(rec_init_.data() + static_cast<size_t>(l) * rec_floats_ + rt->offset);
1671 }
1672 prog->init(w, st);
1673 }
1674 }
1675 if (!recs_.empty()) rec_ = rec_init_;
1676 // The state rows: state_weight [k, d] (ggml ne0 = d, the token embedding's width); k from its
1677 // size, equal to the file's paperkiln.state_tokens.k when the file names one.
1678 {
1679 const auto meta_k = meta_number(gguf, "paperkiln.state_tokens.k");
1680 if (!state_weight_.empty()) {
1682 int d = 0;
1683 for (const auto& n : embed_)
1684 if (n.op == "embedding" && has(n.attrs, "state_weight")) {
1685 std::string w = layer_name(n.attrs.at("weight").get<std::string>(), -1);
1686 d = static_cast<int>(shapes_.at(w).at(0));
1687 }
1688 const auto& sh = shapes_.at(state_weight_);
1689 const size_t numel = bound(state_weight_).numel;
1690 if (sh.empty() || sh[0] != d || d <= 0 || numel % static_cast<size_t>(d) != 0 || numel == 0)
1691 throw std::runtime_error("IR embedding state_weight '" + state_weight_ + "' is not [k, d] with d " +
1692 std::to_string(d) + " (the token embedding's width)");
1693 state_k_ = static_cast<int>(numel / static_cast<size_t>(d));
1694 if (meta_k && static_cast<int>(*meta_k) != state_k_)
1695 throw std::runtime_error("paperkiln.state_tokens.k is " + std::to_string(static_cast<int>(*meta_k)) +
1696 ", the IR's state_weight '" + state_weight_ + "' has " + std::to_string(state_k_) +
1697 " rows");
1698 Logger::info("[IR] per-turn state tokens: k " + std::to_string(state_k_) + " (ids " + std::to_string(vocab_) +
1699 " .. " + std::to_string(vocab_ + state_k_ - 1) + " read '" + state_weight_ + "')" +
1700 (turn_state_ ? std::string(", attention mask turn_state") : std::string()) +
1701 (evict_ ? std::string(", kv eviction at turn_state") : std::string()));
1702 } else if (meta_k && *meta_k > 0) {
1703 throw std::runtime_error("paperkiln.state_tokens.k is " + std::to_string(static_cast<int>(*meta_k)) +
1704 " but the IR's embedding has no state_weight");
1705 }
1706 }
1707 // paperkiln spec/IR_SPEC.md section 10.1 (graph_ir_validate.h): after ember's own checks above, so a
1708 // document they refuse keeps their message; then what ember does not implement among valid
1709 // documents (section 10.3).
1710 validate_ir(j, gguf);
1711 {
1712 int kv_slots = 0;
1713 for (const auto& s : j.at("block").value("state", json::array())) kv_slots += s.value("kind", "") == "kv";
1714 if (kv_slots > 1)
1715 throw std::runtime_error("IR block declares " + std::to_string(kv_slots) +
1716 " kv slots: more than one kv slot per block (IR_SPEC 6.1) is not implemented in ember");
1717 }
1718 size_t n_quant = 0, bytes_quant = 0, bytes_f32 = 0, n_mapped = 0;
1719 for (const auto& kv : weights_) {
1720 n_quant += kv.second.quantised();
1721 n_mapped += kv.second.quantised() && kv.second.q.empty();
1722 bytes_quant += kv.second.qbytes;
1723 bytes_f32 += kv.second.f32.size() * sizeof(float);
1724 }
1725
1727#ifdef HAS_CUDA
1728 if (use_gpu) {
1729 init_device();
1730 gpu_ = true;
1731 }
1732#else
1733 (void)use_gpu;
1734#endif
1735 if (!gpu_ && kv_heads_ > 0) {
1736 kv_.resize(n_layers_);
1737 for (auto& layer : kv_) {
1738 layer.k.assign(static_cast<size_t>(max_len_) * kv_heads_ * head_dim_, 0.0f);
1739 layer.v.assign(layer.k.size(), 0.0f);
1740 }
1741 }
1742 Logger::info("[IR] interpreter ready: family_hint=" + family_hint_ + ", " + std::to_string(n_layers_) + " blocks of " +
1743 std::to_string(block_.size()) + " ops, " + std::to_string(weights_.size()) + " weights bound (" +
1744 std::to_string(n_quant) + " kept quantised, " + std::to_string(bytes_quant >> 20) + " MiB" +
1745 (n_mapped ? " (" + std::to_string(n_mapped) + " read in place from the memory map)" : std::string()) +
1746 "; f32 " +
1747 std::to_string(bytes_f32 >> 20) + " MiB" + (dequant_all_ ? "; dequantised at load" : "") + "), device " +
1748 (gpu_ ? "cuda" : "cpu"));
1749 { // The document run, by its digest (IR_SPEC 3.10): a citation names it as sha256:<hex>.
1750 const DigestResult d = ir_digest(ir_json);
1751 Logger::info(d.ok ? "[IR] document sha256:" + d.sha256 : "[IR] the document has no digest (" + d.refused + ")");
1752 }
1753 if (rec_floats_ > 0)
1754 Logger::info("[IR] recurrent state: " + std::to_string(rec_slots_.size()) + " slot(s), " +
1755 std::to_string(rec_floats_) + " floats per block; up to " + std::to_string(rec_max_snaps_) +
1756 " snapshots for prefix reuse and speculative rewinds");
1757}
std::vector< RecSlot > rec_slots_
Definition graph_ir.h:359
void attention_window(const Node &n, int layer, int &window, int &sinks) const
std::vector< float > rec_init_
Definition graph_ir.h:361
std::string mor_x_
Definition graph_ir.h:287
bool evict_declared() const
Definition graph_ir.h:181
std::string mor_router_
Definition graph_ir.h:287
std::vector< KVCacheLayer > kv_
Definition graph_ir.h:310
std::vector< float > rec_
Definition graph_ir.h:364
std::string mor_out_
Definition graph_ir.h:287
const RecSlot & rec_slot(const Node &n, const char *op) const
std::string state_weight_
Definition graph_ir.h:301
void build_rope_tables(const GGUFData &gguf)
std::vector< int > weight_map_
Definition graph_ir.h:273
void bind(const std::vector< Node > &nodes, int layer, const GGUFData &gguf)
std::string kv_slot_
Definition graph_ir.h:221
static Recurrence compile(const nlohmann::json &node)
std::string why
Definition graph_ir.cpp:280
const float * in[expr::Expr::kMaxInputs]
float value
size_t arity
Kind kind
float c
node sub
const Expr & cached(const std::string &text, size_t n_inputs)
DigestResult ir_digest(const std::string &text, bool prose)
bool device_supported(const std::string &ir_json)
Definition graph_ir.cpp:463
const char * format_name(int f)
Definition kv_quant.h:45
std::string shape_error(int f, int head_dim)
Definition kv_quant.h:55
Format parse_format(const char *s)
Definition kv_quant.h:37

References arity, attention_window(), block_, c, ember_ir::expr::cached(), ember_ir::rec::Recurrence::compile(), ember_ir::device_supported(), embed_, evict_, evict_declared(), evict_declared_, family_hint_, ember_kvq::format_name(), head_, head_dim_, Logger::info(), j, k, ember_kvq::kF32, kind, kv_fmt_, kv_heads_, kv_slot_, max_len_, mor_gate_score_, mor_out_, mor_R_, mor_router_, mor_rows_, mor_step_, mor_x_, n, n_layers_, ember_ir::Interpreter::RecTensor::name, ember_ir::Interpreter::RecSlot::name, name, ember_kvq::parse_format(), rec_floats_, rec_slot(), rec_slots_, recs_, rope_attn_layer_, rope_dim_, rope_ff_, rope_freq_scale_, rope_neox_, rope_scale_layer_, rope_theta_, rope_theta_layer_, ember_kvq::shape_error(), sinks_, state_weight_, sub, ember_ir::Interpreter::RecSlot::tensors, turn_state_, v, vocab_, weight_map_, why, window_, and window_layer_.

Referenced by Interpreter().

◆ kv_cache_bytes()

size_t ember_ir::Interpreter::kv_cache_bytes ( ) const

Definition at line 1767 of file graph_ir.cpp.

1767 {
1768#ifdef HAS_CUDA
1769 if (gpu_)
1771 : static_cast<size_t>(n_layers_) * 2 * max_len_ * kv_heads_ * head_dim_ * sizeof(float);
1772#endif
1773 return 0;
1774}

References j, and ember_kvq::kF32.

◆ kv_format()

const char * ember_ir::Interpreter::kv_format ( ) const

Definition at line 1765 of file graph_ir.cpp.

References ember_kvq::format_name().

◆ kv_positions()

std::vector< int > ember_ir::Interpreter::kv_positions ( int  application) const

Definition at line 3064 of file graph_ir.cpp.

3064 {
3065 cpu_only(gpu_, "kv_positions");
3067 throw std::runtime_error("IR kv_positions: application " + std::to_string(application) + " of " +
3068 std::to_string(n_layers_));
3069 if (kv_.empty()) return {};
3070 if (turn_state_) return kv_pos_;
3071 if (!mor_step_.empty() && mor_step_[application] >= 0) return mor_rows_[application];
3072 std::vector<int> p(static_cast<size_t>(seq_len_));
3073 for (int i = 0; i < seq_len_; ++i) p[i] = i;
3074 return p;
3075}
static void cpu_only(bool gpu, const char *what)

References ember_ir::cpu_only(), j, and p.

◆ kv_rows_held()

int ember_ir::Interpreter::kv_rows_held ( ) const
inline

Definition at line 171 of file graph_ir.h.

171{ return turn_state_ ? static_cast<int>(kv_pos_.size()) : static_cast<int>(ts_seg_.size()); }

References kv_pos_, ts_seg_, and turn_state_.

◆ kv_rows_in_use()

int ember_ir::Interpreter::kv_rows_in_use ( int  application) const
private

Definition at line 3057 of file graph_ir.cpp.

3057 {
3058 if (kv_.empty()) return 0;
3059 if (turn_state_) return static_cast<int>(kv_pos_.size());
3060 if (!mor_step_.empty() && mor_step_.at(application) >= 0) return static_cast<int>(mor_rows_.at(application).size());
3061 return seq_len_;
3062}

References j.

◆ kv_scratch_bytes()

size_t ember_ir::Interpreter::kv_scratch_bytes ( ) const

Definition at line 1776 of file graph_ir.cpp.

1776 {
1777#ifdef HAS_CUDA
1778 if (gpu_) return kv_scratch_dev_bytes_;
1779#endif
1780 return 0;
1781}

References j.

◆ kv_slot()

const std::string & ember_ir::Interpreter::kv_slot ( ) const
inline

Definition at line 180 of file graph_ir.h.

180{ return kv_slot_; }

References kv_slot_.

◆ last_depths()

const std::vector< int > & ember_ir::Interpreter::last_depths ( ) const
inline

Definition at line 141 of file graph_ir.h.

141{ return mor_depth_; }

References mor_depth_.

◆ layer_name()

std::string ember_ir::Interpreter::layer_name ( const std::string &  pattern,
int  layer 
) const
private

Definition at line 1871 of file graph_ir.cpp.

1871 {
1872 std::string s = pattern;
1873 const auto p = s.find("{i}");
1874 if (p != std::string::npos) {
1875 if (layer < 0) throw std::runtime_error("IR weight '" + pattern + "' uses {i} outside the block");
1876 // m_i under weight_index, except in a node tagged "tensor_index": "application" (IR_SPEC 7.2): i.
1877 s.replace(p, 3, std::to_string(weight_map_.empty() || app_index_ ? layer : weight_map_.at(layer)));
1878 }
1879 return s;
1880}

References j, and p.

◆ load_state()

void ember_ir::Interpreter::load_state ( const State &  s)

Definition at line 3180 of file graph_ir.cpp.

3180 {
3181 cpu_only(gpu_, "load_state");
3182 if (s.k.size() != kv_.size() || s.v.size() != kv_.size() || s.rec.size() != rec_.size() ||
3183 s.mor_rows.size() != mor_rows_.size())
3184 throw std::runtime_error("IR load_state: the state is not this interpreter's");
3185 for (size_t l = 0; l < kv_.size(); ++l) {
3186 if (s.k[l].size() > kv_[l].k.size() || s.v[l].size() != s.k[l].size())
3187 throw std::runtime_error("IR load_state: the kv rows do not fit the cache");
3188 std::copy(s.k[l].begin(), s.k[l].end(), kv_[l].k.begin());
3189 std::copy(s.v[l].begin(), s.v[l].end(), kv_[l].v.begin());
3190 }
3191 seq_len_ = s.seq_len;
3192 ts_seg_ = s.ts_seg;
3193 ts_sidx_ = s.ts_sidx;
3194 kv_pos_ = s.kv_pos;
3195 evicted_below_ = s.evicted_below;
3196 mor_rows_ = s.mor_rows;
3197 mor_depth_ = s.mor_depth;
3198 rec_ = s.rec;
3199 rec_pos_ = s.rec_pos;
3200 rec_snaps_ = s.rec_snaps;
3201 ids_ = s.ids;
3202 rope_long_ = s.rope_long;
3203}
std::map< int, std::vector< float > > rec_snaps_
Definition graph_ir.h:366
std::vector< int > ts_sidx_
Definition graph_ir.h:303

References ember_ir::cpu_only(), and j.

◆ max_len()

int ember_ir::Interpreter::max_len ( ) const
inline

Definition at line 136 of file graph_ir.h.

136{ return max_len_; }

References max_len_.

◆ mor_step()

void ember_ir::Interpreter::mor_step ( int  layer,
Env &  env,
int  T,
int  start_pos 
)
private

Definition at line 2909 of file graph_ir.cpp.

2909 {
2910 const int s = mor_step_[layer];
2911 const bool first = layer == 0 || mor_step_[layer - 1] != s;
2912 const bool last = layer == n_layers_ - 1 || mor_step_[layer + 1] != s;
2913 auto it = env.find(mor_x_);
2914 if (it == env.end() || it->second.is_list) throw std::runtime_error("IR depth_route: input '" + mor_x_ + "' is not a tensor");
2915 Buf& x = it->second;
2916 const int d = x.width;
2917 if (s == 0 && first) {
2918 const auto& W = weight(mor_router_);
2919 if (shapes_.at(mor_router_).at(0) != d || W.size() != static_cast<size_t>(mor_R_) * d)
2920 throw std::runtime_error("IR depth_route '" + mor_router_ + "' is not [n_rec, d]");
2921 std::vector<float> logits(static_cast<size_t>(T) * mor_R_);
2922 matmul_f32_f32_batch_cpu(W, x.v, logits, T, mor_R_, d); // x W^T, as linear
2923 mor_depth_.assign(T, 0);
2924 mor_g_.assign(T, 0.0f);
2925 for (int t = 0; t < T; ++t) cpu::mor_route(logits.data() + static_cast<size_t>(t) * mor_R_, mor_R_, mor_depth_[t], mor_g_[t]);
2926 env[mor_out_] = Buf{std::vector<float>(mor_depth_.begin(), mor_depth_.end()), 1};
2927 }
2928 if (first) mor_xin_ = x.v;
2929 std::vector<int> active;
2930 for (int t = 0; t < T; ++t)
2931 if (mor_depth_.at(t) > s) active.push_back(t);
2932 for (int t : active) {
2933 Env row;
2934 for (const auto& kv : env)
2935 if (!kv.second.is_list && kv.second.width > 0 && kv.second.v.size() == static_cast<size_t>(T) * kv.second.width) {
2936 const auto b = kv.second.v.begin() + static_cast<size_t>(t) * kv.second.width;
2937 row[kv.first] = Buf{std::vector<float>(b, b + kv.second.width), kv.second.width};
2938 }
2939 attn_row_ = static_cast<int>(mor_rows_[layer].size());
2940 try {
2941 run_nodes(block_, layer, row, 1, start_pos + t, false);
2942 } catch (...) {
2943 attn_row_ = -1;
2944 throw;
2945 }
2946 attn_row_ = -1;
2947 mor_rows_[layer].push_back(start_pos + t);
2948 const Buf& y = row.at(mor_x_);
2949 if (y.width != d) throw std::runtime_error("IR depth_route: the block changes the width of '" + mor_x_ + "'");
2950 std::copy(y.v.begin(), y.v.end(), x.v.begin() + static_cast<size_t>(t) * d);
2951 }
2952 if (last && mor_gate_score_)
2953 for (int t : active)
2954 cpu::mor_gate(mor_xin_.data() + static_cast<size_t>(t) * d, x.v.data() + static_cast<size_t>(t) * d, d, mor_g_[t]);
2955}
std::vector< float > mor_xin_
Definition graph_ir.h:289
std::vector< float > mor_g_
Definition graph_ir.h:289
void mor_gate(const float *x_in, float *x, size_t n, float g)
void mor_route(const float *l, int R, int &depth, float &gate)
void matmul_f32_f32_batch_cpu(const std::vector< float > &mat_weights, const std::vector< float > &batch_input_activations, std::vector< float > &batch_output_activations, int num_tokens, int output_dim, int input_dim)
Definition utils.cpp:872

References j, matmul_f32_f32_batch_cpu(), ember_ir::Interpreter::Buf::v, and ember_ir::Interpreter::Buf::width.

◆ n_layers()

int ember_ir::Interpreter::n_layers ( ) const
inline

Definition at line 137 of file graph_ir.h.

137{ return n_layers_; }

References n_layers_.

◆ on_gpu()

bool ember_ir::Interpreter::on_gpu ( ) const
inline

Definition at line 147 of file graph_ir.h.

147{ return gpu_; }

References gpu_.

◆ operator=()

Interpreter & ember_ir::Interpreter::operator= ( const Interpreter &  )
delete

◆ rec_begin()

void ember_ir::Interpreter::rec_begin ( int  start_pos,
int  T,
bool  all_rows 
)
private

Definition at line 2242 of file graph_ir.cpp.

2242 {
2243#ifdef HAS_CUDA
2244 const size_t bytes = rec_.size() * sizeof(float);
2245#endif
2246 if (start_pos == 0) { // a new sequence
2247#ifdef HAS_CUDA
2248 if (gpu_) {
2250 while (!dsnaps_.empty()) dsnap_drop(dsnaps_.begin());
2251 } else
2252#endif
2253 {
2254 rec_ = rec_init_; // every application's initial state
2255 rec_snaps_.clear();
2256 }
2257 } else if (start_pos != rec_pos_) {
2258 auto missing = [&] {
2259 return std::runtime_error("IR recurrent state holds " + std::to_string(rec_pos_) + " tokens and has no snapshot at " +
2260 std::to_string(start_pos) + " (resume_point says where a prefix can resume)");
2261 };
2262#ifdef HAS_CUDA
2263 if (gpu_) {
2264 auto it = dsnaps_.find(start_pos);
2265 if (it == dsnaps_.end()) throw missing();
2267 } else
2268#endif
2269 {
2270 auto it = rec_snaps_.find(start_pos);
2271 if (it == rec_snaps_.end()) throw missing();
2272 rec_ = it->second;
2273 }
2274 }
2275 // Snapshots past start_pos belong to the continuation being replaced.
2276#ifdef HAS_CUDA
2277 if (gpu_)
2278 while (!dsnaps_.empty() && dsnaps_.rbegin()->first > start_pos) dsnap_drop(std::prev(dsnaps_.end()));
2279 else
2280#endif
2281 rec_snaps_.erase(rec_snaps_.upper_bound(start_pos), rec_snaps_.end());
2282 rec_pos_ = -1; // until this call completes
2283 // Rows whose state is kept: every row of an all_rows call (speculative
2284 // verification), else the second-to-last row (the same prompt again).
2285 rec_snap_rows_.assign(T, -1);
2286 int kept = 0;
2287 for (int t = 0; t + 1 < T; ++t)
2288 if (all_rows || t == T - 2) rec_snap_rows_[t] = kept++;
2289 // Each kept row starts as the current state (a slot no node updates keeps it).
2290#ifdef HAS_CUDA
2291 if (gpu_) {
2292 if (kept > 0) grow(drec_rows_, static_cast<size_t>(kept) * rec_.size());
2293 for (int k = 0; k < kept; ++k)
2295 return;
2296 }
2297#endif
2298 rec_rows_.resize(static_cast<size_t>(kept) * rec_.size());
2299 for (int k = 0; k < kept; ++k) std::copy(rec_.begin(), rec_.end(), rec_rows_.begin() + k * rec_.size());
2300}
std::vector< float > rec_rows_
Definition graph_ir.h:369
std::vector< int > rec_snap_rows_
Definition graph_ir.h:368

References j, and k.

◆ rec_end()

void ember_ir::Interpreter::rec_end ( int  start_pos,
int  T 
)
private

Definition at line 2318 of file graph_ir.cpp.

2318 {
2319#ifdef HAS_CUDA
2320 if (gpu_) {
2321 const size_t bytes = rec_.size() * sizeof(float);
2322 for (int t = 0; t + 1 < T; ++t) {
2323 const int k = rec_snap_rows_[t];
2324 if (k < 0) continue;
2326 cudaMemcpyDeviceToDevice, stream_));
2327 }
2329 while (dsnaps_.size() > rec_max_snaps_) dsnap_drop(dsnaps_.begin()); // the oldest positions go first
2330 rec_pos_ = start_pos + T;
2331 return;
2332 }
2333#endif
2334 for (int t = 0; t + 1 < T; ++t) {
2335 const int k = rec_snap_rows_[t];
2336 if (k < 0) continue;
2337 rec_snaps_[start_pos + t + 1].assign(rec_rows_.begin() + k * rec_.size(), rec_rows_.begin() + (k + 1) * rec_.size());
2338 }
2340 while (rec_snaps_.size() > rec_max_snaps_) rec_snaps_.erase(rec_snaps_.begin()); // the oldest positions go first
2341 rec_pos_ = start_pos + T;
2342 std::vector<float>().swap(rec_rows_);
2343}

References j, and k.

◆ rec_row_done()

void ember_ir::Interpreter::rec_row_done ( const RecSlot &  slot,
int  layer,
int  t 
)
private

Definition at line 2302 of file graph_ir.cpp.

2302 {
2303 const int k = rec_snap_rows_[t];
2304 if (k < 0) return;
2305 // This slot only: another slot of the block may be at another row.
2306 const size_t off = static_cast<size_t>(layer) * rec_floats_ + slot.tensors.front().offset;
2307 const size_t floats = slot.tensors.back().offset + slot.tensors.back().floats - slot.tensors.front().offset;
2308#ifdef HAS_CUDA
2309 if (gpu_) {
2310 gpuErrchk(cudaMemcpyAsync(drec_rows_.alloc + k * rec_.size() + off, drec_ + off, floats * sizeof(float),
2311 cudaMemcpyDeviceToDevice, stream_));
2312 return;
2313 }
2314#endif
2315 std::copy(rec_.begin() + off, rec_.begin() + off + floats, rec_rows_.begin() + k * rec_.size() + off);
2316}

References j, k, off, and ember_ir::Interpreter::RecSlot::tensors.

◆ rec_slot()

const Interpreter::RecSlot & ember_ir::Interpreter::rec_slot ( const Node &  n,
const char *  op 
) const
private

Definition at line 2066 of file graph_ir.cpp.

2066 {
2067 if (!has(n.attrs, "state")) throw std::runtime_error(std::string("IR ") + op + " names no state slot");
2068 const std::string name = n.attrs.at("state").get<std::string>();
2069 for (const auto& slot : rec_slots_)
2070 if (slot.name == name) {
2071 if (slot.op != op)
2072 throw std::runtime_error("IR recurrent state '" + name + "' belongs to op '" + slot.op + "', not " + op);
2073 return slot;
2074 }
2075 throw std::runtime_error(std::string("IR ") + op + ": no recurrent state named '" + name + "'");
2076}

References j, n, and name.

Referenced by init().

◆ rec_tensor()

float * ember_ir::Interpreter::rec_tensor ( const RecSlot &  slot,
const char *  tensor,
int  layer,
size_t  floats 
)
private

Definition at line 2078 of file graph_ir.cpp.

2078 {
2079 for (const auto& rt : slot.tensors)
2080 if (rt.name == tensor) {
2081 if (rt.floats != floats)
2082 throw std::runtime_error("IR recurrent state '" + slot.name + "': " + tensor + " does not fit the op");
2083 return rec_.data() + static_cast<size_t>(layer) * rec_floats_ + rt.offset;
2084 }
2085 throw std::runtime_error("IR recurrent state '" + slot.name + "' has no tensor '" + tensor + "'");
2086}

References j, ember_ir::Interpreter::RecSlot::name, tensor, and ember_ir::Interpreter::RecSlot::tensors.

◆ recurrent_tensors()

std::vector< Interpreter::RecurrentTensorInfo > ember_ir::Interpreter::recurrent_tensors ( ) const

Definition at line 3089 of file graph_ir.cpp.

3089 {
3090 std::vector<RecurrentTensorInfo> out;
3091 for (const auto& slot : rec_slots_)
3092 for (const auto& rt : slot.tensors) out.push_back({slot.name, rt.name, rt.shape});
3093 return out;
3094}

References j, and out.

◆ recurrent_values()

std::vector< float > ember_ir::Interpreter::recurrent_values ( const std::string &  slot,
const std::string &  tensor,
int  application 
) const

Definition at line 3096 of file graph_ir.cpp.

3097 {
3098 cpu_only(gpu_, "recurrent_values");
3100 throw std::runtime_error("IR recurrent_values: application " + std::to_string(application) + " of " +
3101 std::to_string(n_layers_));
3102 for (const auto& s : rec_slots_)
3103 if (s.name == slot)
3104 for (const auto& rt : s.tensors)
3105 if (rt.name == tensor) {
3106 const auto b = rec_.begin() + static_cast<size_t>(application) * rec_floats_ + rt.offset;
3107 return std::vector<float>(b, b + rt.floats);
3108 }
3109 throw std::runtime_error("IR recurrent_values: no tensor '" + tensor + "' in a slot '" + slot + "'");
3110}

References ember_ir::cpu_only(), j, and tensor.

◆ resume_point()

int ember_ir::Interpreter::resume_point ( int  want) const

Definition at line 2222 of file graph_ir.cpp.

2222 {
2223 if (turn_state_ && evict_ && want > 0 && want <= static_cast<int>(ts_seg_.size())) {
2224 const int m = ts_seg_after(want - 1);
2225 if (m < evicted_below_) return ts_segment_start(m);
2226 }
2227 if (rec_floats_ == 0 || want <= 0) return std::max(want, 0);
2228 if (want == rec_pos_) return want;
2229 auto below = [want](const auto& snaps) {
2230 auto it = snaps.upper_bound(want); // the first snapshot past want
2231 return it == snaps.begin() ? 0 : std::prev(it)->first;
2232 };
2233#ifdef HAS_CUDA
2234 if (gpu_) return below(dsnaps_);
2235#endif
2236 return below(rec_snaps_);
2237}
int ts_seg_after(int p) const
int ts_segment_start(int m) const

References j.

◆ rope_table()

const std::vector< std::pair< float, float > > & ember_ir::Interpreter::rope_table ( int  layer) const
private

Definition at line 1789 of file graph_ir.cpp.

1789 {
1790 const std::vector<int>& idx = rope_long_ && !rope_tab_long_.empty() ? rope_tab_long_ : rope_tab_short_;
1791 const int l = layer < 0 ? 0 : layer;
1792 if (l >= static_cast<int>(idx.size()) || idx[l] < 0)
1793 throw std::runtime_error("IR rope: block " + std::to_string(l) + " has no RoPE table (theta must be positive)");
1794 return rope_tabs_[idx[l]];
1795}

References j.

◆ run_nodes()

void ember_ir::Interpreter::run_nodes ( const std::vector< Node > &  nodes,
int  layer,
Env &  env,
int  T,
int  start_pos,
bool  head_last_only 
)
private

Definition at line 2357 of file graph_ir.cpp.

2358 {
2359 auto value = [&](const Node& n, size_t k, bool want_list) -> Buf& {
2360 auto it = env.find(n.in.at(k));
2361 if (it == env.end()) throw std::runtime_error("IR op '" + n.op + "': input '" + n.in.at(k) + "' is not defined");
2362 if (it->second.is_list != want_list)
2363 throw std::runtime_error("IR op '" + n.op + "': input '" + n.in.at(k) + "' is " +
2364 (want_list ? "a tensor, not a list" : "a list, not a tensor"));
2365 return it->second;
2366 };
2367 auto in = [&](const Node& n, size_t k) -> Buf& { return value(n, k, false); };
2368 auto list_in = [&](const Node& n, size_t k) -> Buf& { return value(n, k, true); };
2369 auto wname = [&](const Node& n) {
2370 std::string w = layer_name(n.attrs.at("weight").get<std::string>(), layer);
2371 if (!weights_.count(w) && has(n.attrs, "tied_to")) w = layer_name(n.attrs.at("tied_to").get<std::string>(), layer);
2372 return w;
2373 };
2374 for (const auto& n : nodes) {
2375 if (!applies(n, layer)) continue; // apply false in this application: reads, names and writes nothing
2376 AppIndex ti(app_index_, n);
2377 if (n.op == "embedding") {
2378 const auto& tokens = in(n, 0); // token ids carried as floats (exact below 2^24)
2379 const std::string wn = wname(n);
2380 const Weight& W = bound(wn);
2381 const int d = static_cast<int>(shapes_.at(wn).at(0));
2382 Buf out{std::vector<float>(static_cast<size_t>(T) * d), d};
2383 // Extended embedding (state_weight): an id in [vocab, vocab + k) reads row id - vocab of the
2384 // state rows; without one, an id past the table is refused. EXACT.
2385 const bool ext = has(n.attrs, "state_weight");
2386 for (int t = 0; t < T; ++t) {
2387 const int id = static_cast<int>(tokens.v[t]);
2388 const Weight* src = &W;
2389 int row = id;
2390 if (ext && id >= vocab_) {
2391 if (id >= vocab_ + state_k_)
2392 throw std::runtime_error("IR embedding: token id " + std::to_string(id) + " is past the state slots (vocab " +
2393 std::to_string(vocab_) + ", k " + std::to_string(state_k_) + ")");
2395 row = id - vocab_;
2396 } else if (id < 0 || id >= vocab_ || static_cast<size_t>(id + 1) * d > W.numel) { // IR_SPEC 5.1, 6.8.1
2397 throw std::runtime_error("IR embedding: token id " + std::to_string(id) + " out of range");
2398 }
2399 float* dst = out.v.data() + static_cast<size_t>(t) * d;
2400 if (src->quantised())
2401 dequant_span_to_f32(src->type, src->qp, static_cast<size_t>(row) * d, d, dst);
2402 else
2403 std::memcpy(dst, src->f32.data() + static_cast<size_t>(row) * d, d * sizeof(float));
2404 }
2405 env[n.out.at(0)] = std::move(out);
2406 } else if (n.op == "pos_embedding") { // x + position_embd[pos], pos absolute. EXACT.
2407 Buf x = in(n, 0);
2408 const std::string wn = wname(n);
2409 const auto& P = weight(wn);
2410 const int d = x.width;
2411 if (shapes_.at(wn).at(0) != d) throw std::runtime_error("IR pos_embedding '" + wn + "': width differs from x");
2412 if (static_cast<size_t>(start_pos + T) * d > P.size())
2413 throw std::runtime_error("IR pos_embedding: position " + std::to_string(start_pos + T - 1) + " exceeds '" + wn + "'");
2414 for (int t = 0; t < T; ++t)
2415 for (int i = 0; i < d; ++i) x.v[static_cast<size_t>(t) * d + i] += P[static_cast<size_t>(start_pos + t) * d + i];
2416 env[n.out.at(0)] = std::move(x);
2417 } else if (n.op == "layernorm") { // (x - mean) / sqrt(biased var + eps) * w + b. ROUNDING.
2418 const Buf& x = in(n, 0);
2419 const auto& w = weight(wname(n));
2420 const auto& b = weight(layer_name(n.attrs.at("bias").get<std::string>(), layer));
2421 const float eps = n.attrs.value("eps", 1e-5f);
2422 const int d = x.width;
2423 Buf out{std::vector<float>(x.v.size()), d};
2424 for (int t = 0; t < T; ++t) {
2425 const float* r = x.v.data() + static_cast<size_t>(t) * d;
2426 double mean = 0.0, var = 0.0;
2427 for (int i = 0; i < d; ++i) mean += r[i];
2428 mean /= d;
2429 for (int i = 0; i < d; ++i) var += (r[i] - mean) * (r[i] - mean);
2430 var /= d;
2431 const float inv = static_cast<float>(1.0 / std::sqrt(var + eps));
2432 for (int i = 0; i < d; ++i)
2433 out.v[static_cast<size_t>(t) * d + i] = (r[i] - static_cast<float>(mean)) * inv * w[i] + b[i];
2434 }
2435 env[n.out.at(0)] = std::move(out);
2436 } else if (n.op == "split") { // columns of [T, sum(sizes)] into the outputs, in order. EXACT.
2437 const Buf& x = in(n, 0);
2438 const auto sizes = n.attrs.at("sizes").get<std::vector<int>>();
2439 int total = 0;
2440 for (int s : sizes) total += s;
2441 if (sizes.size() != n.out.size()) throw std::runtime_error("IR split: sizes do not match the outputs");
2442 // groups g (revision 1.5): x is g groups of sum(sizes) columns, output k the k-th part of every group
2443 const int groups = n.attrs.contains("groups") ? n.attrs.at("groups").get<int>() : 1;
2444 if (groups < 1) throw std::runtime_error("IR_SPEC 5.5 (rule 10): split groups " + std::to_string(groups) + " is not positive");
2445 if (static_cast<int64_t>(total) * groups != x.width)
2446 throw std::runtime_error("IR_SPEC 5.5 (rule 37): split sizes sum to " + std::to_string(total) +
2447 (groups > 1 ? " times groups " + std::to_string(groups) : std::string()) +
2448 ", not the input width " + std::to_string(x.width));
2449 if (groups > 1) {
2450 std::vector<Buf> parts;
2451 std::vector<float*> ptrs;
2452 for (int s : sizes) parts.push_back(Buf{std::vector<float>(static_cast<size_t>(T) * groups * s), groups * s});
2453 for (auto& b : parts) ptrs.push_back(b.v.data());
2454 cpu::split_groups(x.v.data(), T, sizes, groups, ptrs);
2455 for (size_t o = 0; o < sizes.size(); ++o) env[n.out[o]] = std::move(parts[o]);
2456 continue;
2457 }
2458 int col = 0;
2459 for (size_t o = 0; o < sizes.size(); ++o) {
2460 Buf part{std::vector<float>(static_cast<size_t>(T) * sizes[o]), sizes[o]};
2461 for (int t = 0; t < T; ++t)
2462 std::memcpy(part.v.data() + static_cast<size_t>(t) * sizes[o], x.v.data() + static_cast<size_t>(t) * x.width + col,
2463 sizes[o] * sizeof(float));
2464 col += sizes[o];
2465 env[n.out[o]] = std::move(part);
2466 }
2467 } else if (n.op == "gelu") { // tanh: 0.5x(1 + tanh(sqrt(2/pi)(x + 0.044715x^3))); none: 0.5x(1 + erf(x/sqrt2)).
2468 const Buf& x = in(n, 0);
2469 const bool tanh_form = n.attrs.value("approx", "none") == "tanh";
2470 Buf out{std::vector<float>(x.v.size()), x.width};
2471 const float c = std::sqrt(2.0f / 3.14159265358979323846f);
2472 for (size_t i = 0; i < x.v.size(); ++i) {
2473 const float v = x.v[i];
2474 out.v[i] = tanh_form ? 0.5f * v * (1.0f + std::tanh(c * (v + 0.044715f * v * v * v)))
2475 : 0.5f * v * (1.0f + std::erf(v / std::sqrt(2.0f)));
2476 }
2477 env[n.out.at(0)] = std::move(out);
2478 } else if (n.op == "rmsnorm") {
2479 const Buf& x = in(n, 0);
2480 Buf out{{}, x.width};
2481 out.v.resize(x.v.size());
2482 // group g: each consecutive g columns normalised on their own, one
2483 // weight [g] shared (per-head q/k norm); a row of rows of width g.
2484 const int g = has(n.attrs, "group") ? n.attrs.at("group").get<int>() : x.width;
2485 const auto& w = weight(wname(n));
2486 if (g > 0 && x.width % g != 0)
2487 throw std::runtime_error("IR_SPEC 5.6 (rule 37): rmsnorm '" + wname(n) + "': group " + std::to_string(g) +
2488 " does not divide the input width " + std::to_string(x.width));
2489 if (g <= 0 || x.width % g != 0 || w.size() != static_cast<size_t>(g))
2490 throw std::runtime_error("IR rmsnorm '" + wname(n) + "': group " + std::to_string(g) + " does not fit width " +
2491 std::to_string(x.width) + " and weight [" + std::to_string(w.size()) + "]");
2492 rmsnorm_batch_cpu(x.v, w, out.v, T * (x.width / g), g, n.attrs.value("eps", 1e-5f));
2493 env[n.out.at(0)] = std::move(out);
2494 } else if (n.op == "linear") {
2495 const Buf& x = in(n, 0);
2496 const std::string wn = wname(n);
2497 const Weight& W = bound(wn);
2498 if (shapes_.at(wn).at(0) != x.width)
2499 throw std::runtime_error("IR linear '" + wn + "': input width " + std::to_string(x.width) +
2500 " does not match the weight's ne0 " + std::to_string(shapes_.at(wn).at(0)));
2501 const int in_dim = x.width, out_dim = static_cast<int>(W.numel / in_dim);
2502 Buf out{std::vector<float>(static_cast<size_t>(T) * out_dim), out_dim};
2503 if (W.quantised()) // one row of blocks at a time; equal to the f32 path (graph_ir.h)
2505 [&](int64_t o, float* row) {
2506 dequant_span_to_f32(W.type, W.qp, static_cast<size_t>(o) * in_dim, in_dim, row);
2507 },
2508 x.v.data(), out.v.data(), T, out_dim, in_dim);
2509 else
2511 if (has(n.attrs, "bias")) {
2512 const auto& b = weight(layer_name(n.attrs.at("bias").get<std::string>(), layer));
2513 for (int t = 0; t < T; ++t)
2514 for (int o = 0; o < out_dim; ++o) out.v[static_cast<size_t>(t) * out_dim + o] += b[o];
2515 }
2516 env[n.out.at(0)] = std::move(out);
2517 } else if (n.op == "rope") {
2518 Buf q = in(n, 0), k = in(n, 1);
2519 const int hd = n.attrs.at("head_dim").get<int>();
2520 rope_widths(n, layer, q.width, k.width);
2521 const auto& freqs = rope_table(layer);
2522 if (!rope_neox_ && rope_dim_ == head_dim_) { // full interleaved: the llama path's kernel
2523 apply_rope_batch_cpu(q.v, k.v, T, q.width / hd, k.width / hd, hd, start_pos, freqs, max_len_,
2524 /*use_adjacent_pairing=*/true);
2525 } else { // NEOX pairing or partial rotary
2527 throw std::runtime_error("IR rope: position " + std::to_string(start_pos + T - 1) + " exceeds the rope table (" +
2528 std::to_string(max_len_) + ")");
2529 if (q.width % hd != 0 || k.width % hd != 0) throw std::runtime_error("IR rope: q/k widths are not whole heads");
2530 rope_rows_cpu(q.v, T, q.width / hd, hd, rope_dim_, rope_neox_, start_pos, freqs);
2532 }
2533 env[n.out.at(0)] = std::move(q);
2534 env[n.out.at(1)] = std::move(k);
2535 } else if (n.op == "attention") {
2536 const Buf &q = in(n, 0), &k = in(n, 1), &v = in(n, 2);
2537 const int hd = n.attrs.at("head_dim").get<int>(), hq = n.attrs.at("heads").get<int>();
2538 const int hkv = n.attrs.at("kv_heads").get<int>();
2539 if (hd != head_dim_ || hkv != kv_heads_) throw std::runtime_error("IR attention shape differs from its kv state");
2540 // Under a recursion step (mor_step) the rows go to the application's compact cache, at
2541 // attn_row_, and the query reads the rows before and at it; under turn_state they go to the
2542 // compact cache at kv_row0_ and each query reads its visible rows (ts_vis_); elsewhere rows
2543 // sit at their positions.
2544 const bool ts = n.attrs.value("mask", "causal") == "turn_state";
2545 const int kpos = ts ? kv_row0_ : attn_row_ >= 0 ? attn_row_ : start_pos;
2546 if (kpos + T > max_len_)
2547 throw std::runtime_error("IR attention: position " + std::to_string(kpos + T) + " exceeds the kv state (" +
2548 std::to_string(max_len_) + ")");
2549 KVCacheLayer& cache = kv_.at(layer);
2550 const size_t row = static_cast<size_t>(hkv) * hd;
2551 std::memcpy(cache.k.data() + kpos * row, k.v.data(), static_cast<size_t>(T) * row * sizeof(float));
2552 std::memcpy(cache.v.data() + kpos * row, v.v.data(), static_cast<size_t>(T) * row * sizeof(float));
2553 if (kv_fmt_ != ember_kvq::kF32) { // EMBER_KV: the rows as the quantised cache gives them back
2554 ember_kvq::roundtrip_rows(kv_fmt_, cache.k.data() + kpos * row, static_cast<size_t>(T), row);
2555 ember_kvq::roundtrip_rows(kv_fmt_, cache.v.data() + kpos * row, static_cast<size_t>(T), row);
2556 }
2557 const float scale = n.attrs.contains("scale") ? n.attrs.at("scale").get<float>() : 1.0f / std::sqrt(float(hd));
2558 Buf out{{}, hq * hd};
2559 int window = 0, sinks = 0;
2561 // Gemma 2's soft cap of the scaled scores (IR_SPEC 5.15, revision 1.5), before any position bias; 0: none.
2562 const float softcap = has(n.attrs, "softcap") ? at_layer(n.attrs.at("softcap"), std::max(layer, 0)).get<float>() : 0.0f;
2563 if (has(n.attrs, "position_bias")) { // T5's relative position bias (IR_SPEC 5.15, revision 1.3)
2564 const json& pb = n.attrs.at("position_bias");
2565 const std::vector<int> buckets = pb.at("buckets").get<std::vector<int>>();
2566 const auto& W = weight(layer_name(pb.at("weight").get<std::string>(), layer));
2567 std::vector<int> qpos(static_cast<size_t>(T));
2568 for (int t = 0; t < T; ++t) qpos[t] = start_pos + t;
2569 // under turn_state the compact cache's row r holds position kv_pos_[r]; elsewhere row r is position r
2570 const std::vector<std::vector<int>> rows = ts ? ts_vis_ : attention_visible_rows(T, start_pos, window, sinks);
2571 attention_bias_batch_cpu(q.v, cache, out.v, T, rows, ts ? &kv_pos_ : nullptr, qpos, hq, hkv, hd, scale, W.data(),
2572 buckets, softcap);
2573 } else if (has(n.attrs, "relative_kv")) { // Shaw's relative key and value vectors (IR_SPEC 5.15, revision 1.4)
2574 const json& rk = n.attrs.at("relative_kv");
2575 const int K = rk.at("max_distance").get<int>();
2576 // a table [K + 1, w], ne {w, K + 1}, w head_dim or heads * head_dim (rule 40, checked at load)
2577 auto table = [&](const char* member, int& w) -> const float* {
2578 w = 0;
2579 if (!rk.contains(member) || !rk.at(member).is_string()) return nullptr;
2580 const std::string name = layer_name(rk.at(member).get<std::string>(), layer);
2581 const auto& s = shapes_.at(name);
2582 if (s.size() != 2 || s[1] != K + 1 || (s[0] != hd && s[0] != static_cast<int64_t>(hq) * hd))
2583 throw std::runtime_error("IR_SPEC 5.15 (rule 40): relative_kv " + std::string(member) + " '" + name +
2584 "' is not of ne {head_dim or heads * head_dim, max_distance + 1}");
2585 w = static_cast<int>(s[0]);
2586 return weight(name).data();
2587 };
2588 int rk_w = 0, rv_w = 0;
2589 const float* RK = table("key", rk_w);
2590 const float* RV = table("value", rv_w);
2591 std::vector<int> qpos(static_cast<size_t>(T));
2592 for (int t = 0; t < T; ++t) qpos[t] = start_pos + t;
2593 const std::vector<std::vector<int>> rows = ts ? ts_vis_ : attention_visible_rows(T, start_pos, window, sinks);
2595 RV, rv_w, K, softcap);
2596 } else if (ts)
2598 else if (window > 0 && attn_row_ >= 0) {
2599 // A window under depth_route (IR_SPEC 5.15 and 7.5, v1.4.2, Q16): the application's compact cache holds
2600 // positions mor_rows_[layer] at rows 0 .. attn_row_ - 1, and mor_step runs one token at a time, its row at
2601 // attn_row_ (position p = start_pos). A held row at position j is visible iff j < sink_hi or j >= win_lo
2602 // (swa_visible_ranges): the sinks and positions p - window + 1 .. p it holds, counted in positions, never the
2603 // last `window` rows; rows of tokens that exited are absent, not replaced.
2604 const std::vector<int>& held = mor_rows_.at(layer);
2605 if (T != 1 || static_cast<int>(held.size()) != attn_row_)
2606 throw std::runtime_error("IR depth_route: application " + std::to_string(layer) + " runs " +
2607 std::to_string(T) + " rows over " + std::to_string(held.size()) +
2608 " held positions for " + std::to_string(attn_row_) + " rows");
2609 int sink_hi = 0, win_lo = 0;
2611 std::vector<std::vector<int>> rows(1);
2612 for (int r = 0; r < attn_row_; ++r)
2613 if (held[r] < sink_hi || held[r] >= win_lo) rows[0].push_back(r);
2614 rows[0].push_back(attn_row_); // its own row, position p
2616 } else if (window > 0)
2618 else
2620 env[n.out.at(0)] = std::move(out);
2621 } else if (n.op == "mla_attention") { // multi-head latent attention (IR_SPEC 5.20). ROUNDING.
2622 const Buf &qn = in(n, 0), &qr = in(n, 1), &c = in(n, 2), &kr = in(n, 3);
2623 const int L = std::max(layer, 0);
2624 auto at = [&](const char* k) { return at_layer(n.attrs.at(k), L).get<int>(); };
2625 const int H = at("heads"), r = at("kv_rank"), dn = at("qk_nope"), dr = at("qk_rope"), dv = at("v_head_dim");
2626 if (qn.width != H * dn || qr.width != H * dr || c.width != r || kr.width != dr)
2627 throw std::runtime_error("IR_SPEC 5.20 (rule 37): mla_attention reads qn " + std::to_string(qn.width) + ", qr " +
2628 std::to_string(qr.width) + ", c " + std::to_string(c.width) + " and kr " +
2629 std::to_string(kr.width) + " wide, not heads x qk_nope " + std::to_string(H * dn) +
2630 ", heads x qk_rope " + std::to_string(H * dr) + ", kv_rank " + std::to_string(r) +
2631 " and qk_rope " + std::to_string(dr));
2632 if (start_pos + T > max_len_)
2633 throw std::runtime_error("IR mla_attention: position " + std::to_string(start_pos + T) + " exceeds the kv state (" +
2634 std::to_string(max_len_) + ")");
2635 // The slot's row of position p is [c_p | kr_p] (kv_heads 1, head_dim r + dr; rule 12 at load).
2636 KVCacheLayer& cache = kv_.at(layer);
2637 const size_t w = static_cast<size_t>(r) + dr;
2638 for (int t = 0; t < T; ++t) {
2639 float* dst = cache.k.data() + static_cast<size_t>(start_pos + t) * w;
2640 std::memcpy(dst, c.v.data() + static_cast<size_t>(t) * r, static_cast<size_t>(r) * sizeof(float));
2641 std::memcpy(dst + r, kr.v.data() + static_cast<size_t>(t) * dr, static_cast<size_t>(dr) * sizeof(float));
2642 }
2643 int window = 0, sinks = 0;
2645 const auto& Kb = weight(layer_name(n.attrs.at("k_b").get<std::string>(), layer));
2646 const auto& Vb = weight(layer_name(n.attrs.at("v_b").get<std::string>(), layer));
2647 Buf out{std::vector<float>(static_cast<size_t>(T) * H * dv), H * dv};
2648 cpu::mla_attention(qn.v.data(), qr.v.data(), cache.k.data(), out.v.data(), T,
2649 attention_visible_rows(T, start_pos, window, sinks), H, r, dn, dr, dv, Kb.data(), Vb.data(),
2650 n.attrs.at("scale").get<float>());
2651 env[n.out.at(0)] = std::move(out);
2652 } else if (n.op == "add" || n.op == "mul") {
2653 const Buf &a = in(n, 0), &b = in(n, 1);
2654 if (a.v.size() != b.v.size() || a.width != b.width) binary_shapes(n.op, a.width, b.width);
2655 Buf out{std::vector<float>(a.v.size()), a.width};
2656 if (n.op == "add")
2657 for (size_t i = 0; i < a.v.size(); ++i) out.v[i] = a.v[i] + b.v[i];
2658 else
2659 for (size_t i = 0; i < a.v.size(); ++i) out.v[i] = a.v[i] * b.v[i];
2660 env[n.out.at(0)] = std::move(out);
2661 } else if (n.op == "moe_ffn") {
2662 const Buf& x = in(n, 0);
2663 const auto& ex = n.attrs.at("experts");
2664 const std::string gn = layer_name(ex.at("gate").get<std::string>(), layer);
2665 const auto& R = weight(layer_name(n.attrs.at("router").get<std::string>(), layer));
2666 const auto& G = weight(gn);
2667 const auto& U = weight(layer_name(ex.at("up").get<std::string>(), layer));
2668 const auto& D = weight(layer_name(ex.at("down").get<std::string>(), layer));
2669 const int E = n.attrs.at("n_experts").get<int>(), k = n.attrs.at("top_k").get<int>();
2670 const auto& gs = shapes_.at(gn); // ggml order {d, d_ff, E}
2671 if (gs.size() != 3 || gs[0] != x.width || gs[2] != E)
2672 throw std::runtime_error("IR moe_ffn '" + gn + "': expert stack shape does not match d and n_experts");
2673 const int d = x.width, d_ff = static_cast<int>(gs[1]);
2674 const bool renorm = n.attrs.value("gate_norm", "renorm") == "renorm";
2675 // DeepSeek-V3's router (IR_SPEC 5.16, revision 1.6): sigmoid scores, a selection bias, group-limited routing.
2676 const bool sigmoid = has(n.attrs, "scoring") && at_layer(n.attrs.at("scoring"), std::max(layer, 0)) == "sigmoid";
2677 const float* bias =
2678 has(n.attrs, "selection_bias") ? weight(layer_name(n.attrs.at("selection_bias").get<std::string>(), layer)).data()
2679 : nullptr;
2680 int groups_n = 1, groups_top = 1;
2681 if (has(n.attrs, "groups")) {
2682 groups_n = n.attrs.at("groups").at("n").get<int>();
2683 groups_top = n.attrs.at("groups").at("top").get<int>();
2684 if (groups_n < 1 || E % groups_n != 0 || groups_top < 1 || groups_top > groups_n || groups_top * (E / groups_n) < k)
2685 throw std::runtime_error("IR_SPEC 5.16 (rule 10): moe_ffn groups {n " + std::to_string(groups_n) + ", top " +
2686 std::to_string(groups_top) + "} do not fit n_experts " + std::to_string(E) + " and top_k " +
2687 std::to_string(k));
2688 }
2689 Buf out{std::vector<float>(static_cast<size_t>(T) * d), d};
2690#pragma omp parallel for schedule(static)
2691 for (int t = 0; t < T; ++t)
2692 moe_ffn_routed_f32_cpu(x.v.data() + static_cast<size_t>(t) * d, d, d_ff, E, k, R.data(), G.data(), U.data(),
2693 D.data(), out.v.data() + static_cast<size_t>(t) * d, renorm, sigmoid, bias, groups_n, groups_top);
2694 env[n.out.at(0)] = std::move(out);
2695 } else if (n.op == "silu") {
2696 const Buf& x = in(n, 0);
2697 Buf out{std::vector<float>(x.v.size()), x.width};
2698 silu_cpu(x.v, out.v);
2699 env[n.out.at(0)] = std::move(out);
2700 } else if (n.op == "sub") { // a - b. EXACT.
2701 const Buf &a = in(n, 0), &b = in(n, 1);
2702 if (a.v.size() != b.v.size() || a.width != b.width) binary_shapes(n.op, a.width, b.width);
2703 Buf out{std::vector<float>(a.v.size()), a.width};
2704 cpu::sub(a.v.data(), b.v.data(), out.v.data(), a.v.size());
2705 env[n.out.at(0)] = std::move(out);
2706 } else if (n.op == "relu" || n.op == "sigmoid") { // relu EXACT; sigmoid ROUNDING.
2707 const Buf& x = in(n, 0);
2708 Buf out{std::vector<float>(x.v.size()), x.width};
2709 (n.op == "relu" ? cpu::relu : cpu::sigmoid)(x.v.data(), out.v.data(), x.v.size());
2710 env[n.out.at(0)] = std::move(out);
2711 } else if (n.op == "affine") { // (x * scale) + shift. EXACT.
2712 const Buf& x = in(n, 0);
2713 Buf out{std::vector<float>(x.v.size()), x.width};
2714 cpu::affine(x.v.data(), out.v.data(), x.v.size(), at_layer(n.attrs.at("scale"), layer).get<float>(),
2715 at_layer(n.attrs.at("shift"), layer).get<float>());
2716 env[n.out.at(0)] = std::move(out);
2717 } else if (n.op == "scale") { // (x * g) + b per feature, g and b [d]. EXACT.
2718 const Buf& x = in(n, 0);
2719 const std::string wn = wname(n);
2720 const auto& g = weight(wn);
2721 const int d = x.width;
2722 if (d <= 0 || g.size() != static_cast<size_t>(d))
2723 throw std::runtime_error("IR_SPEC 5.4 (rule 37): scale '" + wn + "' has " + std::to_string(g.size()) +
2724 " values for width " + std::to_string(d));
2725 const float* b = nullptr;
2726 if (has(n.attrs, "bias")) {
2727 const std::string bn = layer_name(n.attrs.at("bias").get<std::string>(), layer);
2728 const auto& B = weight(bn);
2729 if (B.size() != static_cast<size_t>(d))
2730 throw std::runtime_error("IR_SPEC 5.4 (rule 37): scale bias '" + bn + "' has " + std::to_string(B.size()) +
2731 " values for width " + std::to_string(d));
2732 b = B.data();
2733 }
2734 Buf out{std::vector<float>(x.v.size()), d};
2735 cpu::scale(x.v.data(), g.data(), b, out.v.data(), static_cast<int>(x.v.size() / d), d);
2736 env[n.out.at(0)] = std::move(out);
2737 } else if (n.op == "mul_col") { // x [T, w] * c [T, 1]. EXACT.
2738 const Buf &x = in(n, 0), &c = in(n, 1);
2739 if (c.width != 1 || c.v.size() != static_cast<size_t>(T) || x.v.size() != static_cast<size_t>(T) * x.width)
2740 throw std::runtime_error("IR_SPEC 5.12 (rule 37): mul_col: c must be [T, 1] beside x [T, w]");
2741 Buf out{std::vector<float>(x.v.size()), x.width};
2742 cpu::mul_col(x.v.data(), c.v.data(), out.v.data(), T, x.width);
2743 env[n.out.at(0)] = std::move(out);
2744 } else if (n.op == "rms_row") { // [T, w] -> [T, 1]: sqrt(ss / w + eps). ROUNDING.
2745 const Buf& x = in(n, 0);
2746 if (x.width <= 0 || x.v.size() != static_cast<size_t>(T) * x.width)
2747 throw std::runtime_error("IR rms_row: input is not [T, w]");
2748 Buf out{std::vector<float>(static_cast<size_t>(T)), 1};
2749 cpu::rms_row(x.v.data(), out.v.data(), T, x.width, n.attrs.at("eps").get<float>());
2750 env[n.out.at(0)] = std::move(out);
2751 } else if (n.op == "expr") { // runtime-defined elementwise op (graph_ir_expr.h). ROUNDING.
2752 std::vector<const float*> ptrs;
2753 const Buf& x0 = in(n, 0);
2754 for (size_t k = 0; k < n.in.size(); ++k) {
2755 const Buf& a = in(n, k);
2756 if (a.width != x0.width || a.v.size() != x0.v.size())
2757 throw std::runtime_error("IR_SPEC 5.19 (rule 37): expr inputs must share one shape [T, w]");
2758 ptrs.push_back(a.v.data());
2759 }
2760 if (x0.width <= 0 || x0.v.size() % x0.width) throw std::runtime_error("IR expr: input is not [T, w]");
2761 Buf out{std::vector<float>(x0.v.size()), x0.width};
2762 expr::eval(expr::cached(n.attrs.at("expr").get<std::string>(), n.in.size()), ptrs,
2763 static_cast<int>(x0.v.size() / x0.width), x0.width, out.v.data());
2764 env[n.out.at(0)] = std::move(out);
2765 } else if (n.op == "list") { // the inputs, in order, as one list value. EXACT.
2766 Buf out;
2767 out.is_list = true;
2768 for (size_t k = 0; k < n.in.size(); ++k) out.items.push_back(in(n, k));
2769 env[n.out.at(0)] = std::move(out);
2770 } else if (n.op == "append") { // the list with x added at the end. EXACT.
2771 const Buf& x = in(n, 1);
2772 // The usual form appends in place (out names the list): no copy.
2773 Buf out;
2774 if (n.out.at(0) == n.in.at(0))
2775 out = std::move(list_in(n, 0));
2776 else
2777 out = list_in(n, 0);
2778 out.items.push_back(x);
2779 env[n.out.at(0)] = std::move(out);
2780 } else if (n.op == "depth_attend") { // attention over depth (AttnRes block form). ROUNDING.
2781 const Buf& L = list_in(n, 0);
2782 std::vector<const float*> src;
2783 int d = 0;
2784 auto add_source = [&](const Buf& s) {
2785 if (d == 0) d = s.width;
2786 if (s.width != d || d <= 0 || s.v.size() != static_cast<size_t>(T) * d)
2787 throw std::runtime_error("IR_SPEC 5.18 (rule 37): depth_attend sources must all be one shape [T, d]");
2788 src.push_back(s.v.data());
2789 };
2790 for (const Buf& s : L.items) add_source(s);
2791 for (size_t k = 1; k < n.in.size(); ++k) add_source(in(n, k));
2792 if (src.empty()) throw std::runtime_error("IR depth_attend: no sources");
2793 const float* w = nullptr;
2794 if (src.size() > 1) { // one source: out = s_0 and the pseudo-query is not read
2795 const std::string wn = layer_name(n.attrs.at("weight").get<std::string>(), layer);
2796 if (!weights_.count(wn)) throw std::runtime_error("IR depth_attend: pseudo-query '" + wn + "' is not in the GGUF");
2797 const auto& W = weight(wn);
2798 if (W.size() != static_cast<size_t>(d))
2799 throw std::runtime_error("IR depth_attend: pseudo-query '" + wn + "' is not [d]");
2800 w = W.data();
2801 }
2802 Buf out{std::vector<float>(static_cast<size_t>(T) * d), d};
2803 cpu::depth_attend(src, w, out.v.data(), T, d, n.attrs.value("eps", 1e-5f));
2804 env[n.out.at(0)] = std::move(out);
2805 } else if (n.op == "linear_attention") { // elu+1 linear attention over the slot's num, den. ROUNDING.
2806 const Buf &q = in(n, 0), &k = in(n, 1), &v = in(n, 2);
2807 const int w = n.attrs.at("heads").get<int>() * n.attrs.at("head_dim").get<int>();
2808 if (q.width != w || k.width != w || v.width != w)
2809 throw std::runtime_error("IR linear_attention: q/k/v widths do not match heads * head_dim");
2810 const RecSlot& slot = rec_slot(n, "linear_attention");
2811 float* num = rec_tensor(slot, "num", layer, w);
2812 float* den = rec_tensor(slot, "den", layer, w);
2813 const float floor = n.attrs.value("den_floor", 1e-8f);
2814 Buf out{std::vector<float>(static_cast<size_t>(T) * w), w};
2815 for (int t = 0; t < T; ++t) { // the decode update for t = 0..T-1, in order
2816 const size_t r = static_cast<size_t>(t) * w;
2817 linear_attention_row_cpu(q.v.data() + r, k.v.data() + r, v.v.data() + r, num, den, out.v.data() + r, w, floor);
2818 rec_row_done(slot, layer, t);
2819 }
2820 env[n.out.at(0)] = std::move(out);
2821 } else if (n.op == "ssm_scan") { // the sequential state-space scan over the slot's s. ROUNDING.
2822 const Buf& u = in(n, 0);
2823 const int sn = n.attrs.at("n").get<int>();
2824 if (u.width != sn) throw std::runtime_error("IR ssm_scan: input width differs from n");
2825 const RecSlot& slot = rec_slot(n, "ssm_scan");
2826 float* s = rec_tensor(slot, "s", layer, sn);
2827 const auto& A = weight(layer_name(ssm_weight(n.attrs, "a"), layer));
2828 const auto& B = weight(layer_name(ssm_weight(n.attrs, "b"), layer));
2829 const auto& C = weight(layer_name(ssm_weight(n.attrs, "c"), layer));
2830 const float D = weight(layer_name(ssm_weight(n.attrs, "d"), layer)).at(0);
2831 std::vector<float> scratch(sn);
2832 Buf out{std::vector<float>(static_cast<size_t>(T) * sn), sn};
2833 for (int t = 0; t < T; ++t) {
2834 const size_t r = static_cast<size_t>(t) * sn;
2835 ssm_scan_row_cpu(u.v.data() + r, A.data(), B.data(), C.data(), D, s, scratch.data(), out.v.data() + r, sn);
2836 rec_row_done(slot, layer, t);
2837 }
2838 env[n.out.at(0)] = std::move(out);
2839 } else if (n.op == "conv1d_causal") { // depthwise causal conv over the slot's previous inputs. ROUNDING.
2840 const Buf& x = in(n, 0);
2841 const int C = n.attrs.at("channels").get<int>(), K = n.attrs.at("kernel").get<int>();
2842 if (x.width != C) throw std::runtime_error("IR conv1d_causal: input width differs from channels");
2843 const RecSlot& slot = rec_slot(n, "conv1d_causal");
2844 float* st = rec_tensor(slot, "x", layer, static_cast<size_t>(K - 1) * C);
2845 const auto& w = weight(layer_name(n.attrs.at("weight").get<std::string>(), layer));
2846 const float* bias =
2847 has(n.attrs, "bias") ? weight(layer_name(n.attrs.at("bias").get<std::string>(), layer)).data() : nullptr;
2848 Buf out{std::vector<float>(static_cast<size_t>(T) * C), C};
2849 for (int t = 0; t < T; ++t) {
2850 const size_t r = static_cast<size_t>(t) * C;
2851 cpu::conv1d_causal_row(x.v.data() + r, w.data(), bias, st, out.v.data() + r, C, K);
2852 rec_row_done(slot, layer, t);
2853 }
2854 env[n.out.at(0)] = std::move(out);
2855 } else if (n.op == "selective_scan") { // Mamba-1's scan over the slot's s. ROUNDING.
2856 const Buf &x = in(n, 0), &dt = in(n, 1), &bm = in(n, 2), &cm = in(n, 3);
2857 const int D = n.attrs.at("channels").get<int>(), N = n.attrs.at("d_state").get<int>();
2858 if (x.width != D || dt.width != D || bm.width != N || cm.width != N)
2859 throw std::runtime_error("IR selective_scan: x/dt must be [T, channels] and B/C [T, d_state]");
2860 const RecSlot& slot = rec_slot(n, "selective_scan");
2861 float* s = rec_tensor(slot, "s", layer, static_cast<size_t>(D) * N);
2862 const auto& A = weight(layer_name(n.attrs.at("a").get<std::string>(), layer));
2863 const float* d = has(n.attrs, "d") ? weight(layer_name(n.attrs.at("d").get<std::string>(), layer)).data() : nullptr;
2864 const bool softplus = n.attrs.value("dt_softplus", true);
2865 Buf out{std::vector<float>(static_cast<size_t>(T) * D), D};
2866 for (int t = 0; t < T; ++t) {
2867 cpu::selective_scan_row(x.v.data() + static_cast<size_t>(t) * D, dt.v.data() + static_cast<size_t>(t) * D,
2868 bm.v.data() + static_cast<size_t>(t) * N, cm.v.data() + static_cast<size_t>(t) * N,
2869 A.data(), d, s, out.v.data() + static_cast<size_t>(t) * D, D, N, softplus);
2870 rec_row_done(slot, layer, t);
2871 }
2872 env[n.out.at(0)] = std::move(out);
2873 } else if (n.op == "recurrence") { // a recurrent op the document defines (IR_SPEC 6.10). EXACT or ROUNDING.
2874 const rec::Recurrence& prog = *recs_.at(&n);
2875 const RecSlot& slot = rec_slot(n, "recurrence");
2876 std::vector<const Buf*> ins;
2877 for (size_t k = 0; k < n.in.size(); ++k) {
2878 const Buf& b = in(n, k);
2879 if (b.is_list || static_cast<size_t>(b.width) != prog.input_widths()[k] ||
2880 b.v.size() != static_cast<size_t>(T) * b.width)
2881 throw std::runtime_error("IR_SPEC 6.10 (rule 34): recurrence input '" + n.in[k] + "' is " +
2882 std::to_string(b.width) + " wide; its declaration is " +
2883 std::to_string(prog.input_widths()[k]));
2884 ins.push_back(&b);
2885 }
2886 std::vector<const float*> w;
2887 for (const auto& wd : prog.weights()) w.push_back(weight(layer_name(wd.tensor, layer)).data());
2888 std::vector<float*> st;
2889 for (const auto& s : prog.state())
2891 const size_t ow = prog.out_width();
2892 Buf out{std::vector<float>(static_cast<size_t>(T) * ow), static_cast<int>(ow)};
2893 std::vector<const float*> row(ins.size());
2894 for (int t = 0; t < T; ++t) { // token by token, in order (prefill = decode)
2895 for (size_t k = 0; k < ins.size(); ++k) row[k] = ins[k]->v.data() + static_cast<size_t>(t) * ins[k]->width;
2896 prog.step(row, w, st, out.v.data() + static_cast<size_t>(t) * ow);
2897 rec_row_done(slot, layer, t);
2898 }
2899 env[n.out.at(0)] = std::move(out);
2900 }
2901 }
2902}
void rec_row_done(const RecSlot &slot, int layer, int t)
const std::vector< std::pair< float, float > > & rope_table(int layer) const
std::vector< std::vector< int > > ts_vis_
Definition graph_ir.h:304
float * rec_tensor(const RecSlot &slot, const char *tensor, int layer, size_t floats)
void attention_window_batch_cpu(const std::vector< float > &q_batch_roped, KVCacheLayer &current_layer_kv_cache, std::vector< float > &batch_attn_output, int num_tokens_in_batch, int start_pos_in_sequence, int num_q_heads, int num_kv_heads, int head_dim, float attention_scale, int window, int sinks, float softcap)
std::vector< std::vector< int > > attention_visible_rows(int num_tokens_in_batch, int start_pos_in_sequence, int window, int sinks)
void attention_batch_cpu(const std::vector< float > &q_batch_roped, KVCacheLayer &current_layer_kv_cache, std::vector< float > &batch_attn_output, int num_tokens_in_batch, int start_pos_in_sequence, int num_q_heads, int num_kv_heads, int head_dim, float attention_scale, float softcap)
void attention_bias_batch_cpu(const std::vector< float > &q_batch_roped, const KVCacheLayer &current_layer_kv_cache, std::vector< float > &batch_attn_output, int num_tokens_in_batch, const std::vector< std::vector< int > > &rows, const std::vector< int > *key_pos, const std::vector< int > &query_pos, int num_q_heads, int num_kv_heads, int head_dim, float attention_scale, const float *bias, const std::vector< int > &buckets, float softcap)
void attention_rows_batch_cpu(const std::vector< float > &q_batch_roped, const KVCacheLayer &current_layer_kv_cache, std::vector< float > &batch_attn_output, int num_tokens_in_batch, const std::vector< std::vector< int > > &rows, int num_q_heads, int num_kv_heads, int head_dim, float attention_scale, float softcap)
void swa_visible_ranges(int pos, int window, int sinks, int &sink_hi, int &win_lo)
void attention_relkv_batch_cpu(const std::vector< float > &q_batch_roped, const KVCacheLayer &current_layer_kv_cache, std::vector< float > &batch_attn_output, int num_tokens_in_batch, const std::vector< std::vector< int > > &rows, const std::vector< int > *key_pos, const std::vector< int > &query_pos, int num_q_heads, int num_kv_heads, int head_dim, float attention_scale, const float *rk, int rk_width, const float *rv, int rv_width, int max_distance, float softcap)
const float * col[expr::kMaxColumns]
std::vector< int > sizes
node scale
node sigmoid
void relu(const float *x, float *out, size_t n)
void conv1d_causal_row(const float *x, const float *w, const float *bias, float *state, float *out, int C, int K)
void depth_attend(const std::vector< const float * > &sources, const float *w, float *out, int T, int d, float eps)
void rms_row(const float *x, float *out, int T, int w, float eps)
void sub(const float *a, const float *b, float *out, size_t n)
void sigmoid(const float *x, float *out, size_t n)
void selective_scan_row(const float *x, const float *dt, const float *B, const float *C, const float *A, const float *d, float *s, float *y, int D, int N, bool softplus)
void split_groups(const float *x, int T, const std::vector< int > &sizes, int g, const std::vector< float * > &outs)
void affine(const float *x, float *out, size_t n, float scale, float shift)
void mla_attention(const float *qn, const float *qr, const float *kv, float *out, int T, const std::vector< std::vector< int > > &rows, int H, int r, int dn, int dr, int dv, const float *Kb, const float *Vb, float scale)
void mul_col(const float *x, const float *c, float *out, int T, int w)
void scale(const float *x, const float *g, const float *b, float *out, int T, int w)
void eval(const Expr &e, const std::vector< const float * > &ins, int T, int w, float *out)
void linear_attention_row_cpu(const float *q, const float *k, const float *v, float *num, float *den, float *out, int width, float den_floor)
Definition graph_ir.cpp:213
void ssm_scan_row_cpu(const float *u, const float *A, const float *B, const float *C, float D, float *s, float *scratch, float *y, int n)
Definition graph_ir.cpp:224
void roundtrip_rows(int f, float *x, size_t rows, size_t row)
Definition kv_quant.h:138
Key-Value cache for a single transformer layer.
Definition model.h:136
std::vector< float > k
Definition model.h:137
void moe_ffn_routed_f32_cpu(const float *x, int d, int d_ff, int E, int k, const float *router, const float *gate_exps, const float *up_exps, const float *down_exps, float *out, bool renorm, bool sigmoid, const float *bias, int groups_n, int groups_top)
Definition utils.cpp:763
void matmul_rows_f32_batch_cpu(const std::function< void(int64_t, float *)> &row_fn, const float *batch_input_activations, float *batch_output_activations, int num_tokens, int output_dim, int input_dim)
Definition utils.cpp:916
void apply_rope_batch_cpu(std::vector< float > &q_batch, std::vector< float > &k_batch, int num_tokens, int num_q_heads, int num_kv_heads, int head_dim, int start_pos_in_sequence, const std::vector< std::pair< float, float > > &all_freqs_cis, int max_pos_embeddings, bool use_adjacent_pairing)
Definition utils.cpp:491
void silu_cpu(const std::vector< float > &x, std::vector< float > &out)
Definition utils.cpp:842
void rmsnorm_batch_cpu(const std::vector< float > &x_batch, const std::vector< float > &weight, std::vector< float > &out_batch, int num_tokens, int hidden_size, float eps)
Definition utils.cpp:613

References a, apply_rope_batch_cpu(), attention_batch_cpu(), attention_bias_batch_cpu(), attention_relkv_batch_cpu(), attention_rows_batch_cpu(), attention_visible_rows(), attention_window_batch_cpu(), c, col, dequant_span_to_f32(), id, in, ember_ir::Interpreter::Buf::is_list, j, k, KVCacheLayer::k, ember_kvq::kF32, ember_ir::linear_attention_row_cpu(), matmul_f32_f32_batch_cpu(), matmul_rows_f32_batch_cpu(), moe_ffn_routed_f32_cpu(), n, ember_ir::Interpreter::RecSlot::name, name, out, rmsnorm_batch_cpu(), ember_kvq::roundtrip_rows(), scale, sigmoid, silu_cpu(), sizes, ember_ir::ssm_scan_row_cpu(), swa_visible_ranges(), ember_ir::Interpreter::Buf::v, v, value, and ember_ir::Interpreter::Buf::width.

◆ save_state()

Interpreter::State ember_ir::Interpreter::save_state ( ) const

Definition at line 3156 of file graph_ir.cpp.

3156 {
3157 cpu_only(gpu_, "save_state");
3158 State s;
3159 s.seq_len = seq_len_;
3160 const size_t row = static_cast<size_t>(kv_heads_) * head_dim_;
3161 for (int l = 0; l < static_cast<int>(kv_.size()); ++l) {
3162 const size_t n = static_cast<size_t>(kv_rows_in_use(l)) * row;
3163 s.k.emplace_back(kv_[l].k.begin(), kv_[l].k.begin() + n);
3164 s.v.emplace_back(kv_[l].v.begin(), kv_[l].v.begin() + n);
3165 }
3166 s.ts_seg = ts_seg_;
3167 s.ts_sidx = ts_sidx_;
3168 s.kv_pos = kv_pos_;
3169 s.evicted_below = evicted_below_;
3170 s.mor_rows = mor_rows_;
3171 s.mor_depth = mor_depth_;
3172 s.rec = rec_;
3173 s.rec_pos = rec_pos_;
3174 s.rec_snaps = rec_snaps_;
3175 s.ids = ids_;
3176 s.rope_long = rope_long_;
3177 return s;
3178}
int kv_rows_in_use(int application) const

References ember_ir::cpu_only(), j, k, n, ember_ir::Interpreter::State::seq_len, and v.

◆ seq_len()

int ember_ir::Interpreter::seq_len ( ) const
inline

Definition at line 182 of file graph_ir.h.

182{ return seq_len_; }

References seq_len_.

◆ set_rope_long()

void ember_ir::Interpreter::set_rope_long ( bool  v)
private

Definition at line 1863 of file graph_ir.cpp.

1863 {
1864 if (v == rope_long_) return;
1865 rope_long_ = v;
1866#ifdef HAS_CUDA
1867 if (gpu_) ++alloc_epoch_; // the captured decode step reads the other set's tables: it is captured again
1868#endif
1869}

References j, and v.

◆ state_k()

int ember_ir::Interpreter::state_k ( ) const
inline

Definition at line 167 of file graph_ir.h.

167{ return state_k_; }

References state_k_.

◆ truncate()

void ember_ir::Interpreter::truncate ( int  n)

Definition at line 3112 of file graph_ir.cpp.

3112 {
3113 cpu_only(gpu_, "truncate");
3115 throw std::runtime_error("IR truncate to " + std::to_string(n) + ": the state covers " + std::to_string(seq_len_) +
3116 " positions");
3117 if (rec_floats_ > 0 && rec_pos_ < 0) throw std::runtime_error("IR truncate: the recurrent state is not usable (a call failed)");
3118 if (resume_point(n) != n)
3119 throw std::runtime_error("IR truncate to " + std::to_string(n) + ": the state can resume only at " +
3120 std::to_string(resume_point(n)) + " (re-prefill from there)");
3121 if (n == seq_len_) return;
3122 if (state_k_ > 0 || turn_state_) {
3123 // n is its segment's start when that segment's text rows are gone (resume_point): every row
3124 // of segments >= m lies at or past n, so dropping positions >= n leaves segments < m only.
3125 const int m = n > 0 ? ts_seg_after(n - 1) : 0;
3127 ts_seg_.resize(n);
3128 ts_sidx_.resize(n);
3129 while (!kv_pos_.empty() && kv_pos_.back() >= n) kv_pos_.pop_back();
3130 }
3131 for (auto& rows : mor_rows_)
3132 while (!rows.empty() && rows.back() >= n) rows.pop_back();
3133 if (rec_floats_ > 0 && n != rec_pos_) {
3134 if (n == 0) {
3135 rec_ = rec_init_; // every application's initial state
3136 rec_snaps_.clear();
3137 } else {
3138 rec_ = rec_snaps_.at(n);
3139 rec_snaps_.erase(rec_snaps_.upper_bound(n), rec_snaps_.end());
3140 }
3141 rec_pos_ = n;
3142 }
3143 seq_len_ = n;
3144 if (rope_ff_ctx_ > 0) { // LongRoPE with two sets (IR_SPEC 6.9, revision 1.5)
3145 ids_.resize(static_cast<size_t>(n));
3146 if (n > 0 && (n > rope_ff_ctx_) != rope_long_) {
3147 // The kept rows were rotated with the other set: the first n ids again, with the set n selects.
3148 const std::vector<int> prefix = ids_;
3150 Logger::info("[IR] LongRoPE: a truncate to " + std::to_string(n) + " changes the set; the rows recomputed");
3151 forward_impl(prefix, 0, false, 1);
3152 }
3153 }
3154}
int resume_point(int want) const

References ember_ir::cpu_only(), Logger::info(), j, and n.

◆ ts_begin()

void ember_ir::Interpreter::ts_begin ( const std::vector< int > &  tokens,
int  start_pos 
)
private

Definition at line 2146 of file graph_ir.cpp.

2146 {
2147 const int n = static_cast<int>(ts_seg_.size()), T = static_cast<int>(tokens.size());
2149 throw std::runtime_error("IR state tokens: a forward at position " + std::to_string(start_pos) +
2150 ", but the sequence holds " + std::to_string(n) + " positions");
2151 // The segment the first token lands in, and how much of a state block is open before it.
2152 const int m = ts_seg_after(start_pos - 1);
2153 int open = 0;
2154 if (start_pos > 0 && ts_sidx_[start_pos - 1] >= 0 && ts_sidx_[start_pos - 1] < state_k_ - 1)
2155 open = ts_sidx_[start_pos - 1] + 1;
2156 // Ids alone fix s(p) and c(p): a state id must be the next slot of an open block (S_0 opens
2157 // one), a text id must not interrupt one. Checked before anything changes.
2158 std::vector<int> segs(T), sidx(T);
2159 int seg = m;
2160 for (int t = 0; t < T; ++t) {
2161 const int id = tokens[t];
2162 if (id >= vocab_ && state_k_ > 0) {
2163 const int j = id - vocab_;
2164 if (j >= state_k_)
2165 throw std::runtime_error("IR state tokens: id " + std::to_string(id) + " is past the " + std::to_string(state_k_) +
2166 " state slots (vocab " + std::to_string(vocab_) + ")");
2167 if (j != open)
2168 throw std::runtime_error("IR state tokens: state id S_" + std::to_string(j) + " at position " +
2169 std::to_string(start_pos + t) + ", expected " +
2170 (open == 0 ? std::string("S_0 to open a block") : "S_" + std::to_string(open)));
2171 segs[t] = seg;
2172 sidx[t] = j;
2173 if (++open == state_k_) {
2174 open = 0;
2175 ++seg;
2176 }
2177 } else {
2178 if (open != 0)
2179 throw std::runtime_error("IR state tokens: text id " + std::to_string(id) + " at position " +
2180 std::to_string(start_pos + t) + " inside an incomplete state block (S_0 .. S_" +
2181 std::to_string(open - 1) + " of " + std::to_string(state_k_) + ")");
2182 segs[t] = seg;
2183 sidx[t] = -1;
2184 }
2185 }
2186 // A rewind into a segment whose text rows are gone can resume only at that segment's start
2187 // (resume_point); the rows of segments >= m all lie at or past it and are dropped below.
2188 if (turn_state_ && m < evicted_below_) {
2189 const int s0 = ts_segment_start(m);
2190 if (start_pos != s0)
2191 throw std::runtime_error("IR state tokens: position " + std::to_string(start_pos) + " is inside turn " +
2192 std::to_string(m) + ", whose text rows were evicted; resume from its start, position " +
2193 std::to_string(s0) + " (resume_point)");
2194 evicted_below_ = m;
2195 }
2196 ts_seg_.resize(start_pos);
2197 ts_sidx_.resize(start_pos);
2198 ts_seg_.insert(ts_seg_.end(), segs.begin(), segs.end());
2199 ts_sidx_.insert(ts_sidx_.end(), sidx.begin(), sidx.end());
2200 if (!turn_state_) return;
2201 while (!kv_pos_.empty() && kv_pos_.back() >= start_pos) kv_pos_.pop_back();
2202 // Rows no query of this call can see: earlier segments' text.
2203 if (evict_ && T > 0) ts_evict_below(segs[0]);
2204 kv_row0_ = static_cast<int>(kv_pos_.size());
2205 if (kv_row0_ + T > max_len_)
2206 throw std::runtime_error("IR attention: " + std::to_string(kv_row0_ + T) + " kv rows exceed the kv state (" +
2207 std::to_string(max_len_) + ")");
2208 // Visible rows per query (section 1.3), ascending: held rows, then this call's rows up to t. The
2209 // device path's kernel reads each row's code instead (ts_upload_codes).
2210 ts_vis_.assign(gpu_ ? 0 : T, {});
2211 for (int t = 0; t < T && !gpu_; ++t) {
2212 const int s = segs[t];
2213 auto visible = [&](int j) { return ts_seg_[j] == s || (ts_sidx_[j] >= 0 && ts_seg_[j] < s); };
2214 std::vector<int>& v = ts_vis_[t];
2215 for (int r = 0; r < kv_row0_; ++r)
2216 if (visible(kv_pos_[r])) v.push_back(r);
2217 for (int u = 0; u <= t; ++u)
2218 if (visible(start_pos + u)) v.push_back(kv_row0_ + u);
2219 }
2220}

References j, n, and v.

◆ ts_evict_below()

void ember_ir::Interpreter::ts_evict_below ( int  seg)
private

Definition at line 2101 of file graph_ir.cpp.

2101 {
2102 if (!evict_ || seg <= evicted_below_) return;
2103 // Keep every state row and the text rows of segments >= seg; move them down in every block's
2104 // cache, in order (row w < row r, so the copies never overlap).
2105 const size_t row = static_cast<size_t>(kv_heads_) * head_dim_;
2106#ifdef HAS_CUDA
2107 if (gpu_) { // the same rows kept, in the same order, compacted in each block's device cache
2108 std::vector<int> idx;
2109 std::vector<int> kept;
2110 for (size_t r = 0; r < kv_pos_.size(); ++r) {
2111 const int p = kv_pos_[r];
2112 if (ts_sidx_.at(p) < 0 && ts_seg_.at(p) < seg) continue;
2113 idx.push_back(static_cast<int>(r));
2114 kept.push_back(p);
2115 }
2116 int w0 = 0;
2117 while (w0 < static_cast<int>(idx.size()) && idx[w0] == w0) ++w0;
2118 if (w0 < static_cast<int>(idx.size())) {
2119 // Pageable source: the copy has read idx when cudaMemcpyAsync returns.
2120 gpuErrchk(cudaMemcpyAsync(dkv_idx_, idx.data(), idx.size() * sizeof(int), cudaMemcpyHostToDevice, stream_));
2121 for (int l = 0; l < n_layers_; ++l)
2122 ir_kv_compact_cuda(dk_[l], dv_[l], dkv_idx_, w0, static_cast<int>(idx.size()), static_cast<int>(row), stream_);
2123 }
2124 kv_pos_ = std::move(kept);
2126 return;
2127 }
2128#endif
2129 size_t w = 0;
2130 for (size_t r = 0; r < kv_pos_.size(); ++r) {
2131 const int p = kv_pos_[r];
2132 if (ts_sidx_.at(p) < 0 && ts_seg_.at(p) < seg) continue;
2133 if (w != r) {
2134 for (auto& layer : kv_) {
2135 std::memcpy(layer.k.data() + w * row, layer.k.data() + r * row, row * sizeof(float));
2136 std::memcpy(layer.v.data() + w * row, layer.v.data() + r * row, row * sizeof(float));
2137 }
2138 kv_pos_[w] = p;
2139 }
2140 ++w;
2141 }
2142 kv_pos_.resize(w);
2144}
void ir_kv_compact_cuda(float *K, float *V, const int *idx, int w0, int n, int row, cudaStream_t stream)

References ember_ir::ir_kv_compact_cuda(), j, and p.

◆ ts_seg_after()

int ember_ir::Interpreter::ts_seg_after ( int  p) const
private

Definition at line 2088 of file graph_ir.cpp.

2088 {
2089 if (p < 0) return 0;
2090 return ts_seg_.at(p) + (state_k_ > 0 && ts_sidx_.at(p) == state_k_ - 1 ? 1 : 0);
2091}

References p.

◆ ts_segment_start()

int ember_ir::Interpreter::ts_segment_start ( int  m) const
private

Definition at line 2093 of file graph_ir.cpp.

2093 {
2094 if (m <= 0) return 0;
2095 // The position after the last state id of block m - 1.
2096 for (int p = static_cast<int>(ts_seg_.size()) - 1; p >= 0; --p)
2097 if (ts_seg_[p] == m - 1 && ts_sidx_[p] == state_k_ - 1) return p + 1;
2098 return static_cast<int>(ts_seg_.size());
2099}

References j, and p.

◆ turn_state()

bool ember_ir::Interpreter::turn_state ( ) const
inline

Definition at line 168 of file graph_ir.h.

168{ return turn_state_; }

References turn_state_.

◆ vocab()

int ember_ir::Interpreter::vocab ( ) const
inline

Definition at line 135 of file graph_ir.h.

135{ return vocab_; }

References vocab_.

◆ weight()

const std::vector< float > & ember_ir::Interpreter::weight ( const std::string &  name) const
private

Definition at line 2351 of file graph_ir.cpp.

2351 {
2352 const Weight& w = bound(name);
2353 if (w.quantised()) throw std::runtime_error("IR weight '" + name + "' is held quantised but read whole");
2354 return w.f32;
2355}

References j, and name.

Member Data Documentation

◆ app_index_

bool ember_ir::Interpreter::app_index_ = false
private

Definition at line 255 of file graph_ir.h.

◆ attn_row_

int ember_ir::Interpreter::attn_row_ = -1
private

Definition at line 290 of file graph_ir.h.

◆ block_

std::vector<Node> ember_ir::Interpreter::block_
private

Definition at line 266 of file graph_ir.h.

Referenced by init().

◆ da_sources_

std::map<std::pair<const Node*, int>, int> ember_ir::Interpreter::da_sources_
private

Definition at line 269 of file graph_ir.h.

◆ dequant_all_

bool ember_ir::Interpreter::dequant_all_ = false
private

Definition at line 307 of file graph_ir.h.

◆ embed_

std::vector<Node> ember_ir::Interpreter::embed_
private

Definition at line 266 of file graph_ir.h.

Referenced by init().

◆ evict_

bool ember_ir::Interpreter::evict_ = false
private

Definition at line 302 of file graph_ir.h.

Referenced by evicts(), and init().

◆ evict_declared_

bool ember_ir::Interpreter::evict_declared_ = false
private

Definition at line 222 of file graph_ir.h.

Referenced by evict_declared(), and init().

◆ evicted_below_

int ember_ir::Interpreter::evicted_below_ = 0
private

Definition at line 305 of file graph_ir.h.

◆ family_hint_

std::string ember_ir::Interpreter::family_hint_
private

Definition at line 306 of file graph_ir.h.

Referenced by family_hint(), and init().

◆ gpu_

bool ember_ir::Interpreter::gpu_ = false
private

Definition at line 340 of file graph_ir.h.

Referenced by on_gpu().

◆ head_

std::vector<Node> ember_ir::Interpreter::head_
private

Definition at line 266 of file graph_ir.h.

Referenced by init().

◆ head_dim_

int ember_ir::Interpreter::head_dim_ = 0
private

Definition at line 311 of file graph_ir.h.

Referenced by init().

◆ ids_

std::vector<int> ember_ir::Interpreter::ids_
private

Definition at line 335 of file graph_ir.h.

◆ kv_

std::vector<KVCacheLayer> ember_ir::Interpreter::kv_
private

Definition at line 310 of file graph_ir.h.

◆ kv_fmt_

int ember_ir::Interpreter::kv_fmt_ = 0
private

Definition at line 312 of file graph_ir.h.

Referenced by init().

◆ kv_heads_

int ember_ir::Interpreter::kv_heads_ = 0
private

Definition at line 311 of file graph_ir.h.

Referenced by init().

◆ kv_pos_

std::vector<int> ember_ir::Interpreter::kv_pos_
private

Definition at line 303 of file graph_ir.h.

Referenced by kv_rows_held().

◆ kv_row0_

int ember_ir::Interpreter::kv_row0_ = 0
private

Definition at line 305 of file graph_ir.h.

◆ kv_slot_

std::string ember_ir::Interpreter::kv_slot_
private

Definition at line 221 of file graph_ir.h.

Referenced by init(), and kv_slot().

◆ max_len_

int ember_ir::Interpreter::max_len_ = 0
private

Definition at line 270 of file graph_ir.h.

Referenced by init(), and max_len().

◆ mor_depth_

std::vector<int> ember_ir::Interpreter::mor_depth_
private

Definition at line 288 of file graph_ir.h.

Referenced by last_depths().

◆ mor_g_

std::vector<float> ember_ir::Interpreter::mor_g_
private

Definition at line 289 of file graph_ir.h.

◆ mor_gate_score_

bool ember_ir::Interpreter::mor_gate_score_ = false
private

Definition at line 286 of file graph_ir.h.

Referenced by init().

◆ mor_out_

std::string ember_ir::Interpreter::mor_out_
private

Definition at line 287 of file graph_ir.h.

Referenced by init().

◆ mor_R_

int ember_ir::Interpreter::mor_R_ = 0
private

Definition at line 285 of file graph_ir.h.

Referenced by init().

◆ mor_router_

std::string ember_ir::Interpreter::mor_router_
private

Definition at line 287 of file graph_ir.h.

Referenced by init().

◆ mor_rows_

std::vector<std::vector<int> > ember_ir::Interpreter::mor_rows_
private

Definition at line 284 of file graph_ir.h.

Referenced by init().

◆ mor_step_

std::vector<int> ember_ir::Interpreter::mor_step_
private

Definition at line 283 of file graph_ir.h.

Referenced by has_depth_route(), and init().

◆ mor_x_

std::string ember_ir::Interpreter::mor_x_
private

Definition at line 287 of file graph_ir.h.

Referenced by init().

◆ mor_xin_

std::vector<float> ember_ir::Interpreter::mor_xin_
private

Definition at line 289 of file graph_ir.h.

◆ n_layers_

int ember_ir::Interpreter::n_layers_ = 0
private

Definition at line 270 of file graph_ir.h.

Referenced by init(), and n_layers().

◆ offset_applied_

std::set<std::string> ember_ir::Interpreter::offset_applied_
private

Definition at line 339 of file graph_ir.h.

◆ rec_

std::vector<float> ember_ir::Interpreter::rec_
private

Definition at line 364 of file graph_ir.h.

◆ rec_floats_

size_t ember_ir::Interpreter::rec_floats_ = 0
private

Definition at line 360 of file graph_ir.h.

Referenced by has_recurrent(), and init().

◆ rec_init_

std::vector<float> ember_ir::Interpreter::rec_init_
private

Definition at line 361 of file graph_ir.h.

◆ rec_max_snaps_

size_t ember_ir::Interpreter::rec_max_snaps_ = 0
private

Definition at line 367 of file graph_ir.h.

◆ rec_pos_

int ember_ir::Interpreter::rec_pos_ = 0
private

Definition at line 365 of file graph_ir.h.

◆ rec_rows_

std::vector<float> ember_ir::Interpreter::rec_rows_
private

Definition at line 369 of file graph_ir.h.

◆ rec_slots_

std::vector<RecSlot> ember_ir::Interpreter::rec_slots_
private

Definition at line 359 of file graph_ir.h.

Referenced by init().

◆ rec_snap_rows_

std::vector<int> ember_ir::Interpreter::rec_snap_rows_
private

Definition at line 368 of file graph_ir.h.

◆ rec_snaps_

std::map<int, std::vector<float> > ember_ir::Interpreter::rec_snaps_
private

Definition at line 366 of file graph_ir.h.

◆ recs_

std::map<const Node*, std::shared_ptr<rec::Recurrence> > ember_ir::Interpreter::recs_
private

Definition at line 363 of file graph_ir.h.

Referenced by init().

◆ rope_attn_layer_

std::vector<float> ember_ir::Interpreter::rope_attn_layer_
private

Definition at line 322 of file graph_ir.h.

Referenced by init().

◆ rope_dim_

int ember_ir::Interpreter::rope_dim_ = 0
private

Definition at line 315 of file graph_ir.h.

Referenced by init().

◆ rope_ff_

nlohmann::json ember_ir::Interpreter::rope_ff_
private

Definition at line 332 of file graph_ir.h.

Referenced by init().

◆ rope_ff_ctx_

int ember_ir::Interpreter::rope_ff_ctx_ = 0
private

Definition at line 333 of file graph_ir.h.

◆ rope_freq_scale_

float ember_ir::Interpreter::rope_freq_scale_ = 1.0f
private

Definition at line 317 of file graph_ir.h.

Referenced by init().

◆ rope_long_

bool ember_ir::Interpreter::rope_long_ = false
private

Definition at line 334 of file graph_ir.h.

◆ rope_neox_

bool ember_ir::Interpreter::rope_neox_ = false
private

Definition at line 316 of file graph_ir.h.

Referenced by init().

◆ rope_scale_layer_

std::vector<float> ember_ir::Interpreter::rope_scale_layer_
private

Definition at line 322 of file graph_ir.h.

Referenced by init().

◆ rope_tab_long_

std::vector<int> ember_ir::Interpreter::rope_tab_long_
private

Definition at line 327 of file graph_ir.h.

◆ rope_tab_short_

std::vector<int> ember_ir::Interpreter::rope_tab_short_
private

Definition at line 327 of file graph_ir.h.

◆ rope_tabs_

std::vector<std::vector<std::pair<float, float> > > ember_ir::Interpreter::rope_tabs_
private

Definition at line 326 of file graph_ir.h.

◆ rope_theta_

float ember_ir::Interpreter::rope_theta_ = 0.0f
private

Definition at line 314 of file graph_ir.h.

Referenced by init().

◆ rope_theta_layer_

std::vector<float> ember_ir::Interpreter::rope_theta_layer_
private

Definition at line 322 of file graph_ir.h.

Referenced by init().

◆ seq_len_

int ember_ir::Interpreter::seq_len_ = 0
private

Definition at line 223 of file graph_ir.h.

Referenced by seq_len().

◆ shapes_

std::unordered_map<std::string, std::vector<int64_t> > ember_ir::Interpreter::shapes_
private

Definition at line 309 of file graph_ir.h.

◆ sinks_

int ember_ir::Interpreter::sinks_ = 0
private

Definition at line 313 of file graph_ir.h.

Referenced by init().

◆ state_k_

int ember_ir::Interpreter::state_k_ = 0
private

Definition at line 300 of file graph_ir.h.

Referenced by state_k().

◆ state_weight_

std::string ember_ir::Interpreter::state_weight_
private

Definition at line 301 of file graph_ir.h.

Referenced by init().

◆ ts_seg_

std::vector<int> ember_ir::Interpreter::ts_seg_
private

Definition at line 303 of file graph_ir.h.

Referenced by kv_rows_held().

◆ ts_sidx_

std::vector<int> ember_ir::Interpreter::ts_sidx_
private

Definition at line 303 of file graph_ir.h.

◆ ts_vis_

std::vector<std::vector<int> > ember_ir::Interpreter::ts_vis_
private

Definition at line 304 of file graph_ir.h.

◆ turn_state_

bool ember_ir::Interpreter::turn_state_ = false
private

Definition at line 302 of file graph_ir.h.

Referenced by init(), kv_rows_held(), and turn_state().

◆ vocab_

int ember_ir::Interpreter::vocab_ = 0
private

Definition at line 270 of file graph_ir.h.

Referenced by init(), and vocab().

◆ weight_map_

std::vector<int> ember_ir::Interpreter::weight_map_
private

Definition at line 273 of file graph_ir.h.

Referenced by init().

◆ weights_

std::unordered_map<std::string, Weight> ember_ir::Interpreter::weights_
private

Definition at line 308 of file graph_ir.h.

◆ window_

int ember_ir::Interpreter::window_ = 0
private

Definition at line 313 of file graph_ir.h.

Referenced by init().

◆ window_layer_

std::vector<int> ember_ir::Interpreter::window_layer_
private

Definition at line 321 of file graph_ir.h.

Referenced by init().


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