42const std::set<std::string>
kCpuOps = {
"embedding",
"pos_embedding",
"rmsnorm",
"layernorm",
"linear",
"split",
"rope",
43 "attention",
"add",
"mul",
"silu",
"gelu",
"moe_ffn",
44 "linear_attention",
"ssm_scan",
46 "conv1d_causal",
"selective_scan",
48 "sub",
"relu",
"sigmoid",
"affine",
"mul_col",
"rms_row",
"list",
"append",
"depth_attend",
58const std::set<std::string>
kRecurrentOps = {
"linear_attention",
"ssm_scan",
"conv1d_causal",
"selective_scan",
61const std::set<std::string>
kCudaOps = {
"embedding",
"pos_embedding",
"rmsnorm",
"layernorm",
"linear",
"split",
"rope",
62 "attention",
"add",
"mul",
"silu",
"gelu",
"moe_ffn",
63 "linear_attention",
"ssm_scan",
"conv1d_causal",
"selective_scan",
64 "sub",
"relu",
"sigmoid",
"affine",
"mul_col",
"rms_row",
"list",
"append",
"depth_attend",
70const std::set<std::string>
kRowWiseOps = {
"rmsnorm",
"layernorm",
"linear",
"add",
"mul",
"silu",
"gelu",
71 "sub",
"relu",
"sigmoid",
"affine",
"mul_col",
"rms_row",
"list",
"append",
"depth_attend",
75 if (
j.is_string())
return {
j.get<std::string>()};
76 std::vector<std::string>
v;
82 std::vector<Node>
nodes;
83 for (
const auto&
n :
sub.
at(
"nodes")) {
86 throw std::runtime_error(std::string(
"IR op '") +
node.op +
"' (" +
where +
87 ") has no CPU kernel in ember's interpreter");
93bool has(
const json&
n,
const char* key) {
return n.contains(key) && !
n.at(key).is_null(); }
102 if (
layer < 0 || !
n.attrs.contains(
"apply"))
return true;
103 const json&
a =
n.attrs.at(
"apply");
105 ?
a.at(
"per_layer").at(
layer)
107 return !
v.is_boolean() ||
v.get<
bool>();
112 throw std::runtime_error(
"IR_SPEC 5.10 (rule 37): " + op +
" operands of two shapes, [T, " + std::to_string(
a) +
113 "] and [T, " + std::to_string(
b) +
"]");
119 const int hkv =
n.attrs.contains(
"kv_heads") ?
at_layer(
n.attrs.at(
"kv_heads"),
L).get<
int>() :
h;
121 throw std::runtime_error(
"IR_SPEC 5.14 (rule 37): rope q is " + std::to_string(
qw) +
" wide and k " +
122 std::to_string(
kw) +
", not heads x head_dim = " + std::to_string(
h *
hd) +
123 " and kv_heads x head_dim = " + std::to_string(
hkv *
hd));
128 if (!
has(
n, key))
return false;
129 const json&
v =
n.at(key);
131 for (
const auto&
e :
v.
at(
"per_layer"))
138 if (!
has(
n,
part))
throw std::runtime_error(std::string(
"IR ssm_scan names no '") +
part +
"' tensor");
139 return n.at(
part).get<std::string>();
148 const std::vector<std::pair<float, float>>&
freqs) {
150 for (
int t = 0;
t <
T; ++
t) {
153 float*
r =
x.data() + (
static_cast<size_t>(
t) *
heads +
h) *
hd;
154 for (
int i = 0;
i <
half; ++
i) {
168 if (
it == g.
metadata.end())
return std::nullopt;
170 [](
const auto&
v) -> std::optional<double> {
171 using V = std::decay_t<
decltype(
v)>;
172 if constexpr (std::is_arithmetic_v<V> && !std::is_same_v<V, bool>)
173 return static_cast<double>(
v);
182 if (
it == g.
metadata.end())
return std::nullopt;
183 if (
const auto*
s = std::get_if<std::string>(&
it->second))
return *
s;
189 if (
it == g.
metadata.end())
return std::nullopt;
190 if (
const auto*
b = std::get_if<bool>(&
it->second))
return *
b;
198 const auto r = std::to_chars(
buf,
buf +
sizeof(
buf),
static_cast<float>(
v));
199 return std::strtod(std::string(
buf,
r.ptr).c_str(),
nullptr);
206 if (
r <= 0.f)
r = std::exp(
r) - 1.f;
216 for (
int c = 0;
c < width; ++
c) {
227 for (
int i = 0;
i <
n; ++
i) {
233 std::memcpy(
s,
scratch,
static_cast<size_t>(
n) *
sizeof(
float));
239 if (!
j.contains(
"block"))
return false;
240 for (
const auto&
s :
j.at(
"block").value(
"state", json::array()))
241 if (
s.value(
"kind",
"") ==
"recurrent")
return true;
247 if (!
j.contains(
"block"))
return false;
249 for (
const auto&
s :
blk.value(
"state", json::array()))
251 for (
const auto&
n :
blk.value(
"nodes", json::array()))
252 if ((
n.value(
"op",
"") ==
"attention" ||
n.value(
"op",
"") ==
"mla_attention") &&
any_nonzero(
n,
"window"))
259 for (
const char*
sub : {
"embed",
"block",
"head"}) {
260 if (!
j.contains(
sub))
continue;
261 for (
const auto&
n :
j.at(
sub).value(
"nodes", json::array())) {
262 const std::string op =
n.value(
"op",
"");
263 if (op ==
"embedding" &&
has(
n,
"state_weight"))
return true;
264 if (op ==
"attention" &&
n.contains(
"mask") &&
n.at(
"mask") !=
"causal")
return true;
267 if (
j.contains(
"block"))
268 for (
const auto&
s :
j.at(
"block").value(
"state", json::array()))
269 if (
has(
s,
"evict"))
return true;
284 d_ =
m.at(
"d").get<
int>();
285 if (
d_ !=
cfg_.
hidden_size)
return fail(
"model.d " + std::to_string(
d_) +
" != the file's embedding_length");
286 if (
m.at(
"n_layers").get<
int>() !=
cfg_.
num_hidden_layers)
return fail(
"n_layers differs from the file's block_count");
289 if (
blk.contains(
"weight_index"))
return fail(
"weight_index");
291 if (
state.size() != 1 ||
state[0].at(
"kind") !=
"kv")
return fail(
"the block's state is not one kv slot");
293 if (!keys(
s, {
"name",
"kind",
"heads",
"kv_heads",
"head_dim",
"max_len",
"window",
"sinks",
"dtype"},
"kv slot"))
295 H_ =
s.at(
"heads").is_number_integer() ?
s.at(
"heads").get<
int>() : -1;
296 KV_ =
s.at(
"kv_heads").is_number_integer() ?
s.at(
"kv_heads").get<
int>() : -1;
297 hd_ =
s.at(
"head_dim").is_number_integer() ?
s.at(
"head_dim").get<
int>() : -1;
300 return fail(
"kv slot heads / kv_heads / head_dim " + std::to_string(
H_) +
" / " + std::to_string(
KV_) +
" / " +
303 if (
s.at(
"window") != 0 ||
s.at(
"sinks") != 0)
return fail(
"a windowed kv slot");
304 slot_ =
s.at(
"name").get<std::string>();
307 if (
emb.size() != 1)
return fail(
"embed is not one embedding");
308 if (!
node(
emb[0],
"embedding", {
"tokens"}, {
"x_in"}, {
"weight"}) || !weight(
emb[0],
"weight",
"token_embd.weight"))
310 x_ =
emb[0].at(
"out").get<std::string>();
314 if (
n.size() != (
moe ? 11u : 15u))
return fail(
"the block has " + std::to_string(
n.size()) +
" nodes");
315 if (!
norm(
n[0],
"x_in",
"n1",
"blk.{i}.attn_norm.weight") || !
lin(
n[1],
"n1",
"q",
"blk.{i}.attn_q.weight") ||
316 !
lin(
n[2],
"n1",
"k",
"blk.{i}.attn_k.weight") || !
lin(
n[3],
"n1",
"v",
"blk.{i}.attn_v.weight") ||
318 !
node(
n[7],
"add", {
"x_in",
"o"}, {
"x_mid"}, {},
true) || !
norm(
n[8],
"x_mid",
"n2",
"blk.{i}.ffn_norm.weight"))
321 if (!
moe_ffn(
n[9]) || !
node(
n[10],
"add", {
"x_mid",
"f"}, {
"x_out"}, {},
true))
return false;
322 }
else if (!
lin(
n[9],
"n2",
"g",
"blk.{i}.ffn_gate.weight") || !
lin(
n[10],
"n2",
"u",
"blk.{i}.ffn_up.weight") ||
323 !
node(
n[11],
"silu", {
"g"}, {
"g_act"}, {}) || !
node(
n[12],
"mul", {
"g_act",
"u"}, {
"gu"}, {},
true) ||
324 !
lin(
n[13],
"gu",
"f",
"blk.{i}.ffn_down.weight") ||
325 !
node(
n[14],
"add", {
"x_mid",
"f"}, {
"x_out"}, {},
true)) {
328 if (
n.back().at(
"out") !=
x_)
return fail(
"the block does not write the residual stream back to '" +
x_ +
"'");
332 const json&
h =
doc_.at(
"head").at(
"nodes");
333 if (
h.size() != 2 || !
norm(
h[0],
"x_in",
"n3",
"output_norm.weight"))
return h.size() == 2 ?
false : fail(
"head");
334 if (!
node(
h[1],
"linear", {
"n3"}, {
"logits"}, {
"weight",
"bias",
"tied_to"}) || !weight(
h[1],
"weight",
"output.weight") ||
336 return fail(
"head linear: output.weight, no bias, tied to token_embd.weight or not");
337 if (
h[1].
at(
"out") !=
"logits")
return fail(
"head output is not logits");
342 bool fail(
const std::string&
m) {
346 bool keys(
const json&
o, std::initializer_list<const char*>
allowed,
const std::string&
where) {
348 if (!
kv.key().empty() &&
kv.key()[0] ==
'_')
continue;
350 for (
const char*
a :
allowed) ok = ok ||
kv.key() ==
a;
351 if (!ok)
return fail(
where +
": '" +
kv.key() +
"'");
355 bool weight(
const json&
n,
const char* key,
const std::string&
name) {
356 return n.value(key,
json()) ==
name ?
true : fail(
n.at(
"op").get<std::string>() +
" " + key +
" is not " +
name);
361 : fail(
"an rmsnorm eps differs from the file's layer_norm_rms_epsilon");
364 bool node(
const json&
n,
const char* op, std::vector<std::string>
ins, std::vector<std::string>
outs,
365 std::initializer_list<const char*> attrs,
bool commutative =
false) {
366 const std::string
where = std::string(op) +
" node";
367 if (
n.at(
"op") != op)
return fail(
"expected " +
where +
", found " +
n.at(
"op").get<std::string>());
368 std::vector<const char*>
allowed = {
"op",
"in",
"out"};
371 if (!
kv.key().empty() &&
kv.key()[0] ==
'_')
continue;
373 for (
const char*
a :
allowed) ok = ok ||
kv.key() ==
a;
374 if (!ok)
return fail(
where +
": '" +
kv.key() +
"'");
376 std::vector<std::string>
got;
377 for (
const auto&
x :
n.
at(
"in")) {
378 const auto it =
role_.find(
x.get<std::string>());
382 if (!
same)
return fail(
where +
" reads the wrong values");
383 const std::vector<std::string>
on =
names(
n.at(
"out"));
384 if (
on.size() !=
outs.size())
return fail(
where +
" writes the wrong number of values");
385 for (
size_t i = 0;
i <
on.size(); ++
i) role_[
on[
i]] =
outs[
i];
388 bool norm(
const json&
n,
const char*
in,
const char*
out,
const std::string&
w) {
389 return node(
n,
"rmsnorm", {
in}, {
out}, {
"weight",
"eps"}) && weight(
n,
"weight",
w) &&
eps(
n);
391 bool lin(
const json&
n,
const char*
in,
const char*
out,
const std::string&
w) {
392 if (!
node(
n,
"linear", {
in}, {
out}, {
"weight",
"bias"}) || !weight(
n,
"weight",
w))
return false;
393 return n.value(
"bias",
json()).is_null() ?
true : fail(
"a biased linear");
396 if (!
node(
n,
"rope", {
"q",
"k",
"pos"}, {
"q_rot",
"k_rot"},
397 {
"heads",
"kv_heads",
"head_dim",
"theta",
"rope_heads",
"pairing",
"n_rot",
"freq_scale"}))
399 if (
n.at(
"heads") != H_ ||
n.at(
"head_dim") != hd_ ||
n.value(
"kv_heads",
json(KV_)) != KV_ ||
400 n.value(
"n_rot",
json(hd_)) != hd_ ||
n.at(
"rope_heads") !=
"all" ||
n.at(
"pairing") !=
"interleaved" ||
402 return fail(
"rope is not interleaved on every head, n_rot = head_dim, freq_scale 1");
403 if (!
n.at(
"theta").is_number() ||
n.at(
"theta").get<
float>() !=
cfg_.
rope_theta)
404 return fail(
"rope theta differs from the file's rope.freq_base");
408 if (!
node(
n,
"attention", {
"q_rot",
"k_rot",
"v"}, {
"a"},
409 {
"state",
"heads",
"kv_heads",
"head_dim",
"scale",
"causal",
"window",
"sinks",
"mask",
"softmax"}))
411 if (
n.at(
"state") != slot_ ||
n.at(
"heads") != H_ ||
n.at(
"kv_heads") != KV_ ||
n.at(
"head_dim") != hd_ ||
412 n.at(
"causal") !=
true ||
n.at(
"window") != 0 ||
n.at(
"sinks") != 0 ||
n.value(
"mask",
json(
"causal")) !=
"causal")
413 return fail(
"attention is not causal over the kv slot with window 0, sinks 0");
414 if (!
n.at(
"scale").is_number() ||
n.at(
"scale").get<
float>() != 1.0f / std::sqrt(
static_cast<float>(hd_)))
415 return fail(
"attention scale is not 1/sqrt(head_dim)");
419 if (!
node(
n,
"moe_ffn", {
"n2"}, {
"f"}, {
"router",
"experts",
"n_experts",
"top_k",
"gate_norm",
"activation",
"routing"}))
421 const json&
e =
n.at(
"experts");
422 if (
n.at(
"router") !=
"blk.{i}.ffn_gate_inp.weight" ||
e.value(
"gate",
"") !=
"blk.{i}.ffn_gate_exps.weight" ||
423 e.value(
"up",
"") !=
"blk.{i}.ffn_up_exps.weight" ||
e.value(
"down",
"") !=
"blk.{i}.ffn_down_exps.weight")
424 return fail(
"moe_ffn tensors are not llama.cpp's ffn_gate_inp / ffn_*_exps");
426 n.at(
"activation") !=
"silu")
427 return fail(
"moe_ffn: n_experts / top_k differ from the file's expert_count / expert_used_count, or not renorm silu");
433 std::map<std::string, std::string>
role_ = {{
"tokens",
"tokens"}, {
"pos",
"pos"}};
441 auto no = [&](
const std::string&
m) {
448 }
catch (
const std::exception&
e) {
449 return no(std::string(
"the document does not parse: ") +
e.what());
451 if (!
j.is_object() ||
j.value(
"family_hint",
json()) !=
"llama")
return no(
"family_hint is not llama");
455 if (!
m.run())
return no(
m.why);
456 }
catch (
const std::exception&
e) {
474 for (
const char*
sub : {
"embed",
"block",
"head"}) {
475 if (!
j.contains(
sub))
continue;
476 const size_t times = std::string(
sub) ==
"block" ?
j.at(
sub).value(
"repeat", 1) : 1;
477 for (
const auto&
n :
j.at(
sub).value(
"nodes", json::array())) {
478 const std::string op =
n.value(
"op",
"");
479 if (!
kCudaOps.count(op))
return false;
480 if (std::string(
sub) ==
"block") {
481 mor =
mor || op ==
"depth_route";
484 if (op ==
"attention" &&
n.contains(
"position_bias"))
return false;
485 if (op ==
"attention" &&
n.contains(
"relative_kv"))
return false;
487 if (
n.contains(
"apply"))
return false;
488 if (op ==
"moe_ffn" && (
n.contains(
"scoring") ||
n.contains(
"selection_bias") ||
n.contains(
"groups")))
return false;
490 if (op ==
"attention" &&
n.contains(
"softcap"))
softcap =
true;
491 if (op ==
"attention" &&
n.contains(
"mask") &&
n.at(
"mask") !=
"causal") turn_state =
true;
495 }
catch (
const std::exception&) {
499 const size_t ins =
n.contains(
"in") &&
n.at(
"in").is_array() ?
n.at(
"in").size() : 1;
504 if (op ==
"ssm_scan" &&
n.value(
"n", 0) >
kIrSsmMax)
return false;
512 if (
softcap && (
mor || turn_state))
return false;
520 static const std::set<std::string>
kBuilt = {
"llama",
"mistral",
"qwen2",
"qwen3",
"gpt2",
"gptneox",
"gemma3",
521 "mamba",
"qwen2moe",
"qwen3moe",
"deepseek2",
"phi3",
"gemma2"};
522 return kBuilt.count(arch) > 0;
526 if (
const char*
path = std::getenv(
"EMBER_IR")) {
527 std::ifstream
f(
path);
528 if (!
f)
throw std::runtime_error(std::string(
"EMBER_IR: cannot read ") +
path);
529 std::stringstream
ss;
533 auto it =
gguf.metadata.find(
"paperkiln.ir");
534 if (
it !=
gguf.metadata.end()) {
535 if (
const auto*
s = std::get_if<std::string>(&
it->second))
return *
s;
540 const std::string arch =
meta_string(
gguf,
"general.architecture").value_or(
"llama");
541 if (arch ==
"llama" || arch ==
"mistral") {
542 const char*
b = std::getenv(
"EMBER_IR_BUILD");
547 throw std::runtime_error(
"ember cannot run general.architecture = '" + arch +
548 "': the GGUF has no paperkiln.ir and ember has no IR builder for that architecture "
549 "(built: llama, mistral, qwen2, qwen3, gpt2, gptneox, gemma3, mamba, qwen2moe, qwen3moe, "
550 "deepseek2, phi3, gemma2)");
554 const std::string arch =
meta_string(g,
"general.architecture").value_or(
"");
555 if (!
has_ir_builder(arch))
throw std::runtime_error(
"no IR builder for general.architecture = '" + arch +
"'");
556 const std::string
p = arch +
".";
557 auto get = [&](
const std::string& key, std::optional<double>
dflt = std::nullopt) ->
double {
560 if (!
v)
throw std::runtime_error(
"IR builder: " + arch +
" file has no " +
p + key);
567 throw std::runtime_error(
"IR builder: " + arch +
" with RoPE scaling is not built yet");
569 const int d =
static_cast<int>(get(
"embedding_length"));
570 const int n_layers =
static_cast<int>(get(
"block_count"));
575 throw std::runtime_error(
"IR builder: no tokens and no token_embd.weight");
576 vocab =
static_cast<int>(
it->second.shape[1]);
578 const std::string
B =
"blk.{i}.";
580 auto linear = [](
const std::string&
x,
const std::string&
w,
const std::string&
out,
const std::string&
b =
"",
581 const std::string&
tied =
"") {
582 json n = {{
"op",
"linear"}, {
"in", {
x}}, {
"weight",
w}, {
"bias",
nullptr}, {
"out",
out}};
583 if (!
b.empty())
n[
"bias"] =
b;
587 auto rmsnorm = [](
const std::string&
x,
const std::string&
w,
double eps,
const std::string&
out,
int group = 0) {
592 auto layernorm = [](
const std::string&
x,
const std::string&
name,
double eps,
const std::string&
out) {
593 return json{{
"op",
"layernorm"}, {
"in", {
x}}, {
"weight",
name +
".weight"},
596 auto op = [](
const std::string&
name, std::vector<std::string>
in,
const json&
out,
json attrs = json::object()) {
604 json n = {{
"op",
"rope"}, {
"in", {
"q",
"k",
"pos"}}, {
"heads",
H}, {
"kv_heads",
KV},
605 {
"head_dim",
hd}, {
"n_rot",
n_rot}, {
"theta",
theta}, {
"rope_heads",
"all"},
606 {
"pairing",
pairing}, {
"out", {
"q",
"k"}}};
611 return json{{
"op",
"attention"}, {
"in", {
"q",
"k",
"v"}}, {
"state",
"kv"}, {
"heads",
H},
613 {
"window",
window}, {
"sinks", 0}, {
"softmax",
"max-shifted, masked entries exactly zero"},
617 return json{{
"name",
"kv"}, {
"kind",
"kv"}, {
"heads",
H}, {
"kv_heads",
KV}, {
"head_dim",
hd},
618 {
"max_len", ctx}, {
"window",
window}, {
"sinks", 0}, {
"dtype",
"f32"}};
622 norm_nodes.push_back(
linear(
"h",
"output.weight",
"logits",
"",
tied ?
"token_embd.weight" :
""));
625 json embed = json::array({{{
"op",
"embedding"}, {
"in", {
"tokens"}}, {
"weight",
"token_embd.weight"}, {
"out",
"x"}}});
631 auto truth = [&](
const std::string& key,
bool dflt) {
638 return op(
"moe_ffn", {
"h2"},
"f",
639 {{
"router",
B +
"ffn_gate_inp.weight"},
640 {
"experts", {{
"gate",
B +
"ffn_gate_exps.weight"}, {
"up",
B +
"ffn_up_exps.weight"},
641 {
"down",
B +
"ffn_down_exps.weight"}}},
642 {
"n_experts",
static_cast<int>(get(
"expert_count"))},
643 {
"top_k",
static_cast<int>(get(
"expert_used_count"))},
645 {
"activation",
"silu"}});
649 for (
json n : {
linear(
"h2",
B +
"ffn_gate_shexp.weight",
"sg"),
linear(
"h2",
B +
"ffn_up_shexp.weight",
"su"),
650 op(
"silu", {
"sg"},
"sg"), op(
"mul", {
"sg",
"su"},
"sgu"),
651 linear(
"sgu",
B +
"ffn_down_shexp.weight",
"sf")})
654 const bool qwen_moe = arch ==
"qwen2moe" || arch ==
"qwen3moe";
656 if (arch ==
"llama" || arch ==
"mistral" || arch ==
"qwen2" || arch ==
"qwen3" ||
qwen_moe) {
657 const int H =
static_cast<int>(get(
"attention.head_count"));
658 const int KV =
static_cast<int>(get(
"attention.head_count_kv",
H));
659 const int hd =
static_cast<int>(get(
"attention.key_length", d /
H));
660 const int n_rot =
static_cast<int>(get(
"rope.dimension_count",
hd));
661 ctx =
static_cast<int>(get(
"context_length"));
662 const double eps = get(
"attention.layer_norm_rms_epsilon");
663 const double theta = get(
"rope.freq_base", 10000.0);
668 if (arch ==
"llama" || arch ==
"mistral")
669 window =
static_cast<int>(
meta_number(g,
p +
"attention.sliding_window").value_or(0));
671 const std::string
pairing = arch ==
"qwen2" || arch ==
"qwen3" ||
qwen_moe ?
"neox" :
"interleaved";
672 auto proj = [&](
const std::string&
name,
const std::string&
out) {
677 const bool moe = arch ==
"llama" &&
meta_number(g,
p +
"expert_count").value_or(0) > 0 &&
680 proj(
"attn_v",
"v")});
687 linear(
"a",
B +
"attn_output.weight",
"o"), op(
"add", {
"x",
"o"},
"x"),
693 const bool shexp = arch ==
"qwen2moe" &&
shape_has(
"blk.0.ffn_gate_inp_shexp.weight");
697 nodes.push_back(
moe_node(arch ==
"qwen2moe" ?
"raw" :
"renorm"));
700 for (
json n : {
linear(
"h2",
B +
"ffn_gate_inp_shexp.weight",
"sgate"), op(
"sigmoid", {
"sgate"},
"sgate"),
701 op(
"mul_col", {
"sf",
"sgate"},
"sf"), op(
"add", {
"f",
"sf"},
"f")})
705 for (
json n : {
linear(
"h2",
B +
"ffn_gate.weight",
"g"),
linear(
"h2",
B +
"ffn_up.weight",
"u"),
706 op(
"silu", {
"g"},
"g"), op(
"mul", {
"g",
"u"},
"gu"),
linear(
"gu",
B +
"ffn_down.weight",
"f")})
709 nodes.push_back(op(
"add", {
"x",
"f"},
"x"));
712 summary =
"heads " + std::to_string(
H) +
"/" + std::to_string(
KV) +
" x " + std::to_string(
hd) +
", n_rot " +
714 (
bias ?
", q/k/v biases" :
"") + (
qk_norm ?
", per-head q/k RMSNorm" :
"") +
715 (
moe ||
qwen_moe ?
", moe_ffn " + std::to_string(
static_cast<int>(get(
"expert_count"))) +
" experts" :
"") +
716 (
shexp ?
" + a gated shared expert" :
"");
717 }
else if (arch ==
"gpt2" || arch ==
"gptneox") {
720 const bool neox = arch ==
"gptneox";
721 const int H =
static_cast<int>(get(
"attention.head_count"));
722 const int KV =
static_cast<int>(get(
"attention.head_count_kv",
H));
723 const int hd =
static_cast<int>(get(
"attention.key_length", d /
H));
724 ctx =
static_cast<int>(get(
"context_length"));
725 const double eps = get(
"attention.layer_norm_epsilon");
727 embed.push_back({{
"op",
"pos_embedding"}, {
"in", {
"x",
"pos"}}, {
"weight",
"position_embd.weight"}, {
"out",
"x"}});
729 linear(
"h",
B +
"attn_qkv.weight",
"qkv",
B +
"attn_qkv.bias"),
730 op(
"split", {
"qkv"}, {
"q",
"k",
"v"}, {{
"sizes", {
H *
hd,
KV *
hd,
KV *
hd}}})});
734 n_rot =
static_cast<int>(get(
"rope.dimension_count",
hd));
738 nodes.push_back(
linear(
"a",
B +
"attn_output.weight",
"o",
B +
"attn_output.bias"));
740 linear(
"h2",
B +
"ffn_up.weight",
"u",
B +
"ffn_up.bias"),
741 op(
"gelu", {
"u"},
"u", {{
"approx",
neox ?
"none" :
"tanh"}}),
742 linear(
"u",
B +
"ffn_down.weight",
"f",
B +
"ffn_down.bias")});
747 nodes.push_back(op(
"add", {
"f",
"x"},
"x"));
748 nodes.push_back(op(
"add", {
"x",
"o"},
"x"));
750 nodes.push_back(op(
"add", {
"x",
"o"},
"x"));
752 nodes.push_back(op(
"add", {
"x",
"f"},
"x"));
756 head.push_back(
linear(
"h",
"output.weight",
"logits"));
760 summary =
"heads " + std::to_string(
H) +
"/" + std::to_string(
KV) +
" x " + std::to_string(
hd) +
", LayerNorm" +
761 (
neox ?
", NEOX RoPE n_rot " + std::to_string(
n_rot) + (
parallel ?
", parallel residual" :
"")
762 : std::string(
", learned positions"));
763 }
else if (arch ==
"gemma3") {
768 const int H =
static_cast<int>(get(
"attention.head_count"));
769 const int KV =
static_cast<int>(get(
"attention.head_count_kv",
H));
770 const int hd =
static_cast<int>(get(
"attention.key_length", d /
H));
771 const int n_rot =
static_cast<int>(get(
"rope.dimension_count",
hd));
772 ctx =
static_cast<int>(get(
"context_length"));
773 const double eps = get(
"attention.layer_norm_rms_epsilon");
774 if (
shape_has(
"rope_freqs.weight"))
throw std::runtime_error(
"IR builder: gemma3 with rope_freqs is not built yet");
777 throw std::runtime_error(
"IR builder: gemma3 RoPE scaling '" + *
stype +
"' is not built yet");
783 const int win =
static_cast<int>(get(
"attention.sliding_window"));
786 for (
int i = 0;
i < n_layers; ++
i) {
794 scales = {{
"per_layer", scales}};
796 const double scale = n_layers == 62 ? 1.0 / std::sqrt(
static_cast<double>(d) /
H)
797 : 1.0 / std::sqrt(
static_cast<double>(
hd));
798 embed.push_back(op(
"affine", {
"x"},
"x", {{
"scale",
f32_value(std::sqrt(
static_cast<double>(d)))}, {
"shift", 0.0}}));
800 linear(
"h",
B +
"attn_k.weight",
"k"),
linear(
"h",
B +
"attn_v.weight",
"v"),
805 rmsnorm(
"o",
B +
"post_attention_norm.weight",
eps,
"o"), op(
"add", {
"x",
"o"},
"x"),
806 rmsnorm(
"x",
B +
"ffn_norm.weight",
eps,
"h2"),
linear(
"h2",
B +
"ffn_gate.weight",
"g"),
807 linear(
"h2",
B +
"ffn_up.weight",
"u"), op(
"gelu", {
"g"},
"g", {{
"approx",
"tanh"}}),
808 op(
"mul", {
"g",
"u"},
"gu"),
linear(
"gu",
B +
"ffn_down.weight",
"f"),
809 rmsnorm(
"f",
B +
"post_ffw_norm.weight",
eps,
"f"), op(
"add", {
"x",
"f"},
"x")});
812 summary =
"heads " + std::to_string(
H) +
"/" + std::to_string(
KV) +
" x " + std::to_string(
hd) +
813 ", sandwich RMSNorms, GeGLU, window " + std::to_string(
win) +
" on 5 of every 6 layers" +
814 (
factor != 1.0 ?
", global RoPE scaled 1/" + std::to_string(
factor) :
"");
815 }
else if (arch ==
"deepseek2") {
822 const int H =
static_cast<int>(get(
"attention.head_count"));
823 ctx =
static_cast<int>(get(
"context_length"));
824 const double eps = get(
"attention.layer_norm_rms_epsilon");
826 throw std::runtime_error(
"IR builder: deepseek2 file without attn_k_b / attention.key_length_mla (converted "
827 "before llama.cpp's MLA layout) builds no graph");
828 double gating = get(
"expert_gating_func", 1.0);
831 throw std::runtime_error(
"IR builder: deepseek2 with expert_gating_func " + std::to_string(
gating) +
832 " (neither softmax 1 nor sigmoid 2) builds no graph");
835 throw std::runtime_error(
"IR builder: deepseek2 with RoPE scaling other than YaRN builds no graph");
837 const int k =
static_cast<int>(get(
"leading_dense_block_count", 0.0));
839 throw std::runtime_error(
"IR builder: deepseek2 leading_dense_block_count " + std::to_string(
k) +
" exceeds block_count");
840 const std::string
M =
"blk." + std::to_string(
k) +
".";
841 const int dr =
static_cast<int>(get(
"rope.dimension_count"));
842 const int dn =
static_cast<int>(get(
"attention.key_length_mla")) -
dr;
843 const int dv =
static_cast<int>(get(
"attention.value_length_mla"));
844 const int r =
static_cast<int>(get(
"attention.kv_lora_rank"));
845 const double theta = get(
"rope.freq_base", 10000.0);
846 const std::string
gate_norm =
truth(
"expert_weights_norm",
false) ?
"renorm" :
"raw";
847 double s = get(
"expert_weights_scale", 1.0);
848 if (
s == 0.0)
s = 1.0;
856 const double c = get(
"rope.scaling.factor"),
o = get(
"rope.scaling.original_context_length");
857 const double pi = 3.14159265358979323846;
859 double lo = std::max(std::floor(
corr(32.0)), 0.0),
hi = std::min(std::ceil(
corr(1.0)),
static_cast<double>(
dr - 1));
860 if (
lo ==
hi)
hi += 0.001;
862 for (
int j = 0;
j <
dr / 2; ++
j) {
863 const double ramp = std::min(std::max((
j -
lo) / (
hi -
lo), 0.0), 1.0);
864 const double e = 1.0 -
ramp;
867 mscale = 1.0 + get(
"rope.scaling.yarn_log_multiplier", 0.0) * std::log(
c);
873 linear(
"qa",
B +
"attn_q_b.weight",
"q")})
878 json rope_node =
json{{
"op",
"rope"}, {
"in", {
"qr",
"kr",
"pos"}}, {
"heads",
H}, {
"kv_heads", 1}, {
"head_dim",
dr},
879 {
"n_rot",
dr}, {
"theta",
f32_value(
theta)}, {
"rope_heads",
"all"}, {
"pairing",
"interleaved"},
880 {
"out", {
"qr",
"kr"}}};
882 for (
json n : {op(
"split", {
"q"}, {
"qn",
"qr"}, {{
"sizes", {
dn,
dr}}, {
"groups",
H}}),
883 linear(
"h",
B +
"attn_kv_a_mqa.weight",
"ckr"), op(
"split", {
"ckr"}, {
"c",
"kr"}, {{
"sizes", {
r,
dr}}}),
885 json{{
"op",
"mla_attention"}, {
"in", {
"qn",
"qr",
"c",
"kr"}}, {
"state",
"kv"}, {
"heads",
H},
886 {
"kv_rank",
r}, {
"qk_nope",
dn}, {
"qk_rope",
dr}, {
"v_head_dim",
dv},
887 {
"k_b",
B +
"attn_k_b.weight"}, {
"v_b",
B +
"attn_v_b.weight"},
889 {
"causal",
true}, {
"window", 0}, {
"sinks", 0}, {
"out",
"a"}},
890 linear(
"a",
B +
"attn_output.weight",
"o"), op(
"add", {
"x",
"o"},
"x"),
899 n[
"apply"] = {{
"per_layer",
pl}};
904 for (
json n : {
linear(
"h2",
B +
"ffn_gate.weight",
"g"),
linear(
"h2",
B +
"ffn_up.weight",
"u"),
905 op(
"silu", {
"g"},
"g"), op(
"mul", {
"g",
"u"},
"gu"),
linear(
"gu",
B +
"ffn_down.weight",
"f")})
911 if (
gating == 2.0)
moe[
"scoring"] =
"sigmoid";
912 if (
shape_has(
M +
"exp_probs_b.bias"))
moe[
"selection_bias"] =
B +
"exp_probs_b.bias";
913 const int gcount =
static_cast<int>(get(
"expert_group_count", 1.0));
914 if (
gcount > 1)
moe[
"groups"] = {{
"n",
gcount}, {
"top",
static_cast<int>(get(
"expert_group_used_count", 1.0))}};
915 router_note = (
moe.contains(
"scoring") ?
", sigmoid scores" :
"") +
916 std::string(
moe.contains(
"selection_bias") ?
", selection bias" :
"") +
917 (
moe.contains(
"groups") ?
", " + std::to_string(
gcount) +
" groups" : std::string());
919 if (
s != 1.0)
nodes.push_back(
applied(op(
"affine", {
"f"},
"f", {{
"scale",
f32_value(
s)}, {
"shift", 0.0}}),
false));
924 nodes.push_back(
applied(op(
"add", {
"f",
"sf"},
"f"),
false));
927 nodes.push_back(op(
"add", {
"x",
"f"},
"x"));
929 state = json::array({{{
"name",
"kv"}, {
"kind",
"kv"}, {
"heads",
H}, {
"kv_heads", 1}, {
"head_dim",
r +
dr},
930 {
"max_len", ctx}, {
"window", 0}, {
"sinks", 0}, {
"dtype",
"f32"}}});
931 summary =
"MLA: heads " + std::to_string(
H) +
", kv_rank " + std::to_string(
r) +
", qk_nope " + std::to_string(
dn) +
932 ", qk_rope " + std::to_string(
dr) +
", v_head_dim " + std::to_string(
dv) +
933 (
q_lora ?
", low-rank query" :
"") + (
yarn ?
", YaRN" :
"") +
934 (
k > 0 ?
", " + std::to_string(
k) +
" leading dense blocks" : std::string()) +
935 (
k < n_layers ?
", moe_ffn " + std::to_string(
static_cast<int>(get(
"expert_count"))) +
" experts (" +
937 ")" + (
shexp ?
" + shared experts" :
"")
939 }
else if (arch ==
"phi3") {
943 const int H =
static_cast<int>(get(
"attention.head_count"));
944 const int KV =
static_cast<int>(get(
"attention.head_count_kv",
H));
945 const int hd =
static_cast<int>(get(
"attention.key_length", d /
H));
946 const int n_rot =
static_cast<int>(get(
"rope.dimension_count",
hd));
947 const int ff =
static_cast<int>(get(
"feed_forward_length"));
948 ctx =
static_cast<int>(get(
"context_length"));
949 const double eps = get(
"attention.layer_norm_rms_epsilon");
950 const double theta = get(
"rope.freq_base", 10000.0);
951 const int window =
static_cast<int>(get(
"attention.sliding_window", 0.0));
953 if (
shape_has(
"blk.0.ffn_gate_inp.weight"))
954 throw std::runtime_error(
"IR builder: phi3 with a mixture of experts (Phi-3.5-MoE's layout) is not built");
957 throw std::runtime_error(
"IR builder: phi3 with one LongRoPE factor set (rope_factors_short / _long) builds no graph");
961 r[
"freq_factors"] = {{
"short",
"rope_factors_short.weight"},
962 {
"long",
"rope_factors_long.weight"},
963 {
"original_ctx",
static_cast<int>(get(
"rope.scaling.original_context_length"))}};
964 m =
meta_number(g,
p +
"rope.scaling.attn_factor").value_or(1.0);
965 if (
m == 0.0)
m = 1.0;
970 if (
shape_has(
"blk.0.attn_qkv.weight")) {
971 nodes.push_back(
linear(
"h",
B +
"attn_qkv.weight",
"qkv",
bias(
"attn_qkv")));
972 nodes.push_back(op(
"split", {
"qkv"}, {
"q",
"k",
"v"}, {{
"sizes", {
H *
hd,
KV *
hd,
KV *
hd}}}));
974 for (
const char*
t : {
"q",
"k",
"v"})
975 nodes.push_back(
linear(
"h",
B +
"attn_" +
t +
".weight",
t,
bias(std::string(
"attn_") +
t)));
978 linear(
"a",
B +
"attn_output.weight",
"o",
bias(
"attn_output")), op(
"add", {
"x",
"o"},
"x"),
979 rmsnorm(
"x",
B +
"ffn_norm.weight",
eps,
"h2"),
linear(
"h2",
B +
"ffn_up.weight",
"gu2"),
980 op(
"split", {
"gu2"}, {
"g",
"u"}, {{
"sizes", {
ff,
ff}}}), op(
"silu", {
"g"},
"g"),
981 op(
"mul", {
"g",
"u"},
"gu"),
linear(
"gu",
B +
"ffn_down.weight",
"f"), op(
"add", {
"x",
"f"},
"x")})
985 summary =
"heads " + std::to_string(
H) +
"/" + std::to_string(
KV) +
" x " + std::to_string(
hd) +
", NEOX RoPE n_rot " +
986 std::to_string(
n_rot) + (
fs ?
", LongRoPE (original_ctx " +
r[
"freq_factors"][
"original_ctx"].dump() +
")" :
"") +
987 (
m != 1.0 ?
", attn_factor " + std::to_string(
m) :
"") + (
window ?
", window " + std::to_string(
window) :
"");
988 }
else if (arch ==
"gemma2") {
992 const int H =
static_cast<int>(get(
"attention.head_count"));
993 const int KV =
static_cast<int>(get(
"attention.head_count_kv",
H));
994 const int hd =
static_cast<int>(get(
"attention.key_length", d /
H));
995 const int n_rot =
static_cast<int>(get(
"rope.dimension_count",
hd));
996 ctx =
static_cast<int>(get(
"context_length"));
997 const double eps = get(
"attention.layer_norm_rms_epsilon");
998 const double theta = get(
"rope.freq_base", 10000.0);
1000 auto nonzero = [&](
const std::string& key,
double dflt) {
1001 const double v = get(key,
dflt);
1002 return v == 0.0 ?
dflt :
v;
1004 const int win =
static_cast<int>(
nonzero(
"attention.sliding_window", 4096.0));
1005 const double cap =
nonzero(
"attn_logit_softcapping", 50.0),
fcap =
nonzero(
"final_logit_softcapping", 30.0);
1007 for (
int i = 0;
i < n_layers; ++
i)
windows.push_back(
i % 2 == 0 ?
win : 0);
1009 const double scale = n_layers == 46 ? 1.0 / std::sqrt(
static_cast<double>(d) /
H)
1010 : 1.0 / std::sqrt(
static_cast<double>(
hd));
1020 if (
C.find_first_of(
".eEn") == std::string::npos)
C +=
".0";
1022 embed.push_back(op(
"affine", {
"x"},
"x", {{
"scale",
f32_value(std::sqrt(
static_cast<double>(d)))}, {
"shift", 0.0}}));
1024 linear(
"h",
B +
"attn_k.weight",
"k"),
linear(
"h",
B +
"attn_v.weight",
"v"),
1026 linear(
"a",
B +
"attn_output.weight",
"o"),
1027 rmsnorm(
"o",
B +
"post_attention_norm.weight",
eps,
"o"), op(
"add", {
"x",
"o"},
"x"),
1028 rmsnorm(
"x",
B +
"ffn_norm.weight",
eps,
"h2"),
linear(
"h2",
B +
"ffn_gate.weight",
"g"),
1029 linear(
"h2",
B +
"ffn_up.weight",
"u"), op(
"gelu", {
"g"},
"g", {{
"approx",
"tanh"}}),
1030 op(
"mul", {
"g",
"u"},
"gu"),
linear(
"gu",
B +
"ffn_down.weight",
"f"),
1031 rmsnorm(
"f",
B +
"post_ffw_norm.weight",
eps,
"f"), op(
"add", {
"x",
"f"},
"x")});
1032 head = json::array({
rmsnorm(
"x",
"output_norm.weight",
eps,
"h"),
1033 linear(
"h",
"output.weight",
"z",
"",
tied ?
"token_embd.weight" :
""),
1034 op(
"expr", {
"z"},
"logits", {{
"expr",
"tanh(x / " +
C +
") * " +
C}})});
1036 summary =
"heads " + std::to_string(
H) +
"/" + std::to_string(
KV) +
" x " + std::to_string(
hd) +
1037 ", sandwich RMSNorms, GeGLU, window " + std::to_string(
win) +
" on even layers, attention softcap " +
1038 std::to_string(
cap) +
", final softcap " +
C;
1040 const int K =
static_cast<int>(get(
"ssm.conv_kernel")),
Di =
static_cast<int>(get(
"ssm.inner_size"));
1041 const int N =
static_cast<int>(get(
"ssm.state_size")),
R =
static_cast<int>(get(
"ssm.time_step_rank"));
1042 const double eps = get(
"attention.layer_norm_rms_epsilon");
1043 ctx =
static_cast<int>(get(
"context_length", 0.0));
1044 if (ctx == 0) ctx = 1048576;
1046 throw std::runtime_error(
"IR builder: mamba file lacks ssm_dt.weight or ssm_d");
1047 if (
meta_bool(g,
p +
"ssm.dt_b_c_rms").value_or(
false) ||
shape_has(
"blk.0.ssm_dt_norm.weight"))
1048 throw std::runtime_error(
"IR builder: mamba with dt/B/C RMSNorms (Falcon-Mamba) is not built yet");
1049 nodes = json::array(
1050 {
rmsnorm(
"x",
B +
"attn_norm.weight",
eps,
"h"),
linear(
"h",
B +
"ssm_in.weight",
"xz"),
1051 op(
"split", {
"xz"}, {
"xs",
"z"}, {{
"sizes", {
Di,
Di}}}),
1052 op(
"conv1d_causal", {
"xs"},
"xs",
1053 {{
"state",
"conv"}, {
"weight",
B +
"ssm_conv1d.weight"}, {
"bias",
B +
"ssm_conv1d.bias"},
1054 {
"channels",
Di}, {
"kernel", K}}),
1055 op(
"silu", {
"xs"},
"xs"),
linear(
"xs",
B +
"ssm_x.weight",
"xdb"),
1056 op(
"split", {
"xdb"}, {
"dt",
"bm",
"cm"}, {{
"sizes", {
R,
N,
N}}}),
1057 linear(
"dt",
B +
"ssm_dt.weight",
"dt",
B +
"ssm_dt.bias"),
1058 op(
"selective_scan", {
"xs",
"dt",
"bm",
"cm"},
"y",
1059 {{
"state",
"ssm"}, {
"a",
B +
"ssm_a"}, {
"d",
B +
"ssm_d"}, {
"channels",
Di}, {
"d_state",
N},
1060 {
"dt_softplus",
true}}),
1061 op(
"silu", {
"z"},
"z"), op(
"mul", {
"y",
"z"},
"y"),
linear(
"y",
B +
"ssm_out.weight",
"o"),
1062 op(
"add", {
"x",
"o"},
"x")});
1063 state = json::array({{{
"name",
"conv"}, {
"kind",
"recurrent"}, {
"op",
"conv1d_causal"}, {
"dtype",
"f32"},
1064 {
"tensors", json::array({{{
"name",
"x"}, {
"shape", {K - 1,
Di}}, {
"init", 0}}})}},
1065 {{
"name",
"ssm"}, {
"kind",
"recurrent"}, {
"op",
"selective_scan"}, {
"dtype",
"f32"},
1066 {
"tensors", json::array({{{
"name",
"s"}, {
"shape", {
Di,
N}}, {
"init", 0}}})}}});
1068 summary =
"selective scan d_inner " + std::to_string(
Di) +
", d_state " + std::to_string(
N) +
", dt_rank " +
1069 std::to_string(
R) +
", conv kernel " + std::to_string(K);
1073 const auto bos =
meta_number(g,
"tokenizer.ggml.bos_token_id");
1075 doc[
"_comment"] =
"built by ember's build_ir_from_gguf (graph_ir.cpp; paperkiln tools/gguf_ir.py's rules) from "
1076 "general.architecture = " + arch +
" and its metadata";
1077 doc[
"ir_version"] = 1;
1078 doc[
"family_hint"] = arch;
1079 doc[
"model"] = {{
"vocab", vocab}, {
"d", d}, {
"n_layers", n_layers}, {
"ctx", ctx}, {
"dtype",
"f32"}};
1080 doc[
"tokenizer"] = {{
"type",
"gguf"},
1082 {
"specials",
eos ?
json{{
"eot",
static_cast<int64_t>(*eos)}} : json::object()},
1084 doc[
"inputs"] = json::array({{{
"name",
"tokens"}, {
"kind",
"token_ids"}}, {{
"name",
"pos"}, {
"kind",
"positions"}}});
1085 if (arch ==
"mamba")
doc[
"inputs"] = json::array({{{
"name",
"tokens"}, {
"kind",
"token_ids"}}});
1087 doc[
"block"] = {{
"repeat", n_layers}, {
"state",
state}, {
"nodes",
nodes}};
1088 doc[
"head"] = {{
"nodes",
head}};
1089 doc[
"outputs"] = json::array({{{
"name",
"logits"}, {
"kind",
"logits"}}});
1090 doc[
"modes"] = {{
"prefill",
"all nodes over T tokens"},
1091 {
"decode",
"the same graph with T = 1, attention reads and appends to the kv state, a recurrent op "
1092 "reads and replaces its recurrent state"}};
1094 Logger::info(
"[IR] built the IR for general.architecture = " + arch +
" from its metadata: d " + std::to_string(d) +
1095 ", " + std::to_string(n_layers) +
" layers, " +
summary + (
tied ?
", tied head" :
""));
1103 }
catch (
const json::exception&) {
1112 const int version =
j.at(
"ir_version").get<
int>();
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)");
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");
1128 if (
j.at(
"block").contains(
"weight_index")) {
1129 const json&
wi =
j.at(
"block").at(
"weight_index");
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");
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");
1141 std::vector<bool>
used(
static_cast<size_t>(
stored),
false);
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(
"");
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)));
1150 for (
int v :
weight_map_)
m += (
m.empty() ?
"" :
", ") + std::to_string(
v);
1152 std::to_string(
stored) +
" stored layers, weight_index [" +
m +
"]");
1154 vocab_ =
j.at(
"model").at(
"vocab").get<
int>();
1155 max_len_ =
j.at(
"model").value(
"ctx", 0);
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>(), {}};
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")) {
1170 rt.
name =
t.at(
"name").get<std::string>();
1171 rt.shape =
t.at(
"shape").get<std::vector<int64_t>>();
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);
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)");
1184 rt.init =
t.value(
"init", 0.0f);
1188 slot.tensors.push_back(std::move(
rt));
1191 if (
other.name == slot.name)
throw std::runtime_error(
"IR recurrent state '" + slot.name +
"' is declared twice");
1195 if (
kind !=
"kv")
throw std::runtime_error(
"IR state kind '" +
kind +
"' has no CPU implementation in ember");
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");
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)");
1209 for (
const char*
k : {
"heads",
"kv_heads",
"head_dim",
"max_len"})
1211 throw std::runtime_error(std::string(
"IR kv state: a per-layer '") +
k +
"' is not implemented in ember");
1219 window_layer_ =
s.at(
"window").at(
"per_layer").get<std::vector<int>>();
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");
1228 throw std::runtime_error(
"IR kv state: per-layer sinks are not implemented in ember");
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");
1240 if (
w < 0 ||
s < 0)
throw std::runtime_error(
"IR attention window/sinks must be non-negative");
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_) +
")");
1249 Logger::info(
"[IR] sliding-window attention: block " + std::to_string(
l) +
" window " + std::to_string(
w) +
1250 ", sinks " + std::to_string(
s));
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");
1260 for (
const auto&
n : *
sub) {
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");
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()) {
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");
1285 if (ok &&
kv.key() ==
"apply")
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")
1291 throw std::runtime_error(
"IR_SPEC 3.8 (rule 6): rope freq_factors is structural and cannot be per-layer");
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 " +
1299 if (
n.op ==
"expr") {
1301 if (!
n.attrs.contains(
"expr") || !
n.attrs.at(
"expr").is_string())
1302 throw std::runtime_error(
"IR expr needs its string attribute expr");
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());
1312 throw std::runtime_error(
"IR uses an op or a shape that has no CUDA kernel (EMBER_IR_DEVICE=cpu runs it)");
1317 for (
const auto&
n :
block_)
mla =
mla ||
n.op ==
"mla_attention";
1318 for (
const auto&
n :
block_) {
1319 if (
n.op ==
"mla_attention") {
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>();
1328 throw std::runtime_error(
"IR_SPEC 5.20 (rule 12): mla_attention reads a kv slot of kv_heads " +
1330 "; it holds c and kr: kv_heads 1, head_dim kv_rank + qk_rope = " + std::to_string(
r +
dr));
1333 for (
const auto&
n :
block_) {
1334 if (
n.op ==
"rope") {
1335 const std::string
pairing =
n.attrs.value(
"pairing",
"interleaved");
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");
1341 throw std::runtime_error(
"IR rope kv_heads differs from its kv state's");
1345 const int hd =
n.attrs.at(
"head_dim").get<
int>();
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");
1354 std::vector<float> scales(
static_cast<size_t>(
n_layers_), 1.0f),
attn(
static_cast<size_t>(
n_layers_), 1.0f);
1358 if (!(scales[
l] > 0.0f))
throw std::runtime_error(
"IR rope freq_scale must be positive");
1360 if (
has(
n.attrs,
"attn_factor") &&
at_layer(
n.attrs.at(
"attn_factor"),
l).is_number())
1363 const json ff =
has(
n.attrs,
"freq_factors") ?
n.attrs.at(
"freq_factors") :
json();
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");
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>();
1386 throw std::runtime_error(
"IR attention: heads " + std::to_string(
hq) +
" is not a multiple of kv_heads " +
1387 std::to_string(
hkv));
1392 if (
n.op ==
"depth_route")
1393 throw std::runtime_error(
"IR mla_attention under depth_route is not implemented in ember");
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) {
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>>();
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");
1427 if (
first < 0 ||
span %
mor_R_ != 0)
throw std::runtime_error(
"IR depth_route step: the recursion is not R equal steps");
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)");
1440 throw std::runtime_error(
"IR depth_route weight names its tensor in full (no {i})");
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");
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");
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");
1463 throw std::runtime_error(
"IR embedding state_weight names its tensor in full (no {i})");
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)");
1474 for (
const auto&
n :
block_) {
1475 if (
n.op !=
"attention" ||
n.attrs.value(
"mask",
"causal") !=
"turn_state")
continue;
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) +
")");
1485 throw std::runtime_error(
"IR attention mask turn_state with a windowed kv state is not allowed");
1487 throw std::runtime_error(
"IR attention mask turn_state with depth_route / kv_rows active is not implemented in ember");
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;
1501 throw std::runtime_error(
"EMBER_EVICT=1: eviction needs every attention node to have mask turn_state");
1506 throw std::runtime_error(
"IR kv state evict turn_state with recurrent state is not implemented in ember");
1511 if (
const char*
c = std::getenv(
"EMBER_IR_CTX")) {
1512 const int cap = std::atoi(
c);
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");
1525 if (
n.op ==
"mla_attention")
1527 ": mla_attention's latent cache is not quantised");
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");
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");
1539 for (
const auto&
n :
block_) {
1540 if (
n.op ==
"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"}) {
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]");
1557 if (!
found)
throw std::runtime_error(
"IR recurrent state '" + slot.
name +
"' has no tensor '" +
t +
"'");
1559 }
else if (
n.op ==
"ssm_scan") {
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") {
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");
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") {
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") {
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;
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);
1594 throw std::runtime_error(
"IR_SPEC 6.10 (rule 33): the slot '" +
name +
"' of a recurrence is read by another node (" +
1599 if (rec_floats_ > 0) {
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_)
1606 std::
fill(rec_init_.
begin() +
l * rec_floats_ +
rt.offset, rec_init_.
begin() +
l * rec_floats_ +
rt.offset +
rt.floats,
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()));
1624 const bool device =
false;
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";
1634 std::unordered_map<std::string, int> entries;
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;
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;
1646 if (
n.op ==
"depth_attend") da_sources_[{&
n,
layer}] =
held(
n.in.at(0)) +
static_cast<int>(
n.in.size()) - 1;
1652 for (
int l = 0;
l < n_layers_; ++
l)
walk(block_,
l);
1655 bind(embed_, -1,
gguf);
1656 for (
int l = 0;
l < n_layers_; ++
l) bind(block_,
l,
gguf);
1657 bind(head_, -1,
gguf);
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;
1665 std::vector<float*>
st;
1667 const RecTensor*
rt =
nullptr;
1668 for (
const auto&
t : slot.tensors)
1670 st.push_back(rec_init_.data() +
static_cast<size_t>(
l) * rec_floats_ +
rt->offset);
1675 if (!recs_.empty()) rec_ = rec_init_;
1680 if (!state_weight_.empty()) {
1681 bind_tensor(state_weight_,
gguf,
true);
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));
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));
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_) +
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()));
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");
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");
1719 for (
const auto&
kv : weights_) {
1721 n_mapped +=
kv.second.quantised() &&
kv.second.q.empty();
1726 build_rope_tables(
gguf);
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);
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" +
1747 std::
to_string(
bytes_f32 >> 20) +
" MiB" + (dequant_all_ ?
"; dequantised at load" :
"") +
"), device " +
1748 (gpu_ ?
"cuda" :
"cpu"));
1751 Logger::info(d.ok ?
"[IR] document sha256:" + d.sha256 :
"[IR] the document has no digest (" + d.refused +
")");
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");
1759Interpreter::~Interpreter() {
1767size_t Interpreter::kv_cache_bytes()
const {
1771 :
static_cast<size_t>(n_layers_) * 2 * max_len_ * kv_heads_ * head_dim_ *
sizeof(
float);
1776size_t Interpreter::kv_scratch_bytes()
const {
1785 : (window_layer_.empty() ? window_ : window_layer_.at(
layer));
1789const std::vector<std::pair<float, float>>& Interpreter::rope_table(
int layer)
const {
1790 const std::vector<int>& idx = rope_long_ && !rope_tab_long_.empty() ? rope_tab_long_ : rope_tab_short_;
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]];
1804 rope_tab_short_.clear();
1805 rope_tab_long_.clear();
1807 if (rope_theta_layer_.empty())
return;
1808 const int half = rope_dim_ / 2;
1810 std::vector<float>
values;
1811 if (rope_ff_.is_array()) {
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));
1817 }
else if (rope_ff_.is_string()) {
1819 }
else if (rope_ff_.is_object()) {
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>();
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;
1831 std::vector<float>
f;
1834 }
else if (!
name.empty()) {
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));
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_);
1849 rope_tabs_.push_back(std::move(
tab));
1850 index[key] =
static_cast<int>(rope_tabs_.size()) - 1;
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)");
1863void Interpreter::set_rope_long(
bool v) {
1864 if (
v == rope_long_)
return;
1871std::string Interpreter::layer_name(
const std::string&
pattern,
int layer)
const {
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");
1877 s.replace(
p, 3, std::to_string(weight_map_.empty() || app_index_ ?
layer : weight_map_.at(
layer)));
1883 if (
auto bw = weights_.find(
name);
bw != weights_.end()) {
1887 w.
f32.resize(
w.numel);
1889 std::vector<uint8_t>().swap(
w.q);
1895 if (
it ==
gguf.tensor_infos_map.end())
throw std::runtime_error(
"IR weight '" +
name +
"' is not in the GGUF");
1898 ?
static_cast<const uint8_t*
>(
gguf.mapped_tensor_data) +
gguf.offset_diff_for_mmap
1899 :
gguf.tensor_data.data();
1914 throw std::runtime_error(
"IR weight '" +
name +
"' is shorter than its element count implies");
1916 if (
gguf.mapped_tensor_data) {
1926 weights_[
name] = std::move(
w);
1929 std::vector<float>
out;
1939 " is not supported by ember's interpreter");
1944 w.f32 = std::move(
out);
1945 weights_[
name] = std::move(
w);
1949 for (
const auto&
n :
nodes) {
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");
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");
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);
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;
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")) {
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) +
"}");
2004 if (
n.op ==
"mla_attention") {
2005 const auto at = [&](
const char*
k) {
return static_cast<int64_t>(
at_layer(
n.attrs.at(
k), std::max(
layer, 0)).get<
int>()); };
2007 const std::pair<const char*, std::vector<int64_t>>
parts[] = {{
"k_b", {
dn,
r,
H}}, {
"v_b", {
r,
dv,
H}}};
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);
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]) +
"}");
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}};
2024 bind_tensor(
w,
gguf);
2026 throw std::runtime_error(
"IR ssm_scan '" +
w +
"' has " + std::to_string(bound(
w).
numel) +
2027 " elements, expected " + std::to_string(
numel));
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));
2035 if (
n.op ==
"conv1d_causal") {
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);
2040 if (
n.op ==
"selective_scan") {
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);
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);
2051 if (
n.op ==
"recurrence") {
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);
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));
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) {
2072 throw std::runtime_error(
"IR recurrent state '" +
name +
"' belongs to op '" + slot.op +
"', not " + op);
2075 throw std::runtime_error(std::string(
"IR ") + op +
": no recurrent state named '" +
name +
"'");
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;
2085 throw std::runtime_error(
"IR recurrent state '" + slot.
name +
"' has no tensor '" +
tensor +
"'");
2088int Interpreter::ts_seg_after(
int p)
const {
2089 if (
p < 0)
return 0;
2090 return ts_seg_.at(
p) + (state_k_ > 0 && ts_sidx_.at(
p) == state_k_ - 1 ? 1 : 0);
2093int Interpreter::ts_segment_start(
int m)
const {
2094 if (
m <= 0)
return 0;
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());
2101void Interpreter::ts_evict_below(
int seg) {
2102 if (!evict_ ||
seg <= evicted_below_)
return;
2105 const size_t row =
static_cast<size_t>(kv_heads_) * head_dim_;
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));
2117 while (
w0 <
static_cast<int>(idx.size()) && idx[
w0] ==
w0) ++
w0;
2118 if (
w0 <
static_cast<int>(idx.size())) {
2121 for (
int l = 0;
l < n_layers_; ++
l)
2124 kv_pos_ = std::move(
kept);
2125 evicted_below_ =
seg;
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;
2134 for (
auto&
layer : kv_) {
2143 evicted_below_ =
seg;
2146void Interpreter::ts_begin(
const std::vector<int>& tokens,
int start_pos) {
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");
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_;
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_) +
")");
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)));
2173 if (++
open == state_k_) {
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_) +
")");
2188 if (turn_state_ &&
m < evicted_below_) {
2189 const int s0 = ts_segment_start(
m);
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)");
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();
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_) +
")");
2210 ts_vis_.assign(gpu_ ? 0 :
T, {});
2211 for (
int t = 0;
t <
T && !gpu_; ++
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)
2217 for (
int u = 0;
u <=
t; ++
u)
2222int Interpreter::resume_point(
int want)
const {
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);
2227 if (rec_floats_ == 0 ||
want <= 0)
return std::max(
want, 0);
2231 return it ==
snaps.begin() ? 0 : std::prev(
it)->first;
2236 return below(rec_snaps_);
2244 const size_t bytes = rec_.size() *
sizeof(
float);
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)");
2271 if (
it == rec_snaps_.end())
throw missing();
2281 rec_snaps_.erase(rec_snaps_.upper_bound(
start_pos), rec_snaps_.end());
2285 rec_snap_rows_.assign(
T, -1);
2287 for (
int t = 0;
t + 1 <
T; ++
t)
2293 for (
int k = 0;
k <
kept; ++
k)
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());
2303 const int k = rec_snap_rows_[
t];
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;
2315 std::copy(rec_.begin() +
off, rec_.begin() +
off + floats, rec_rows_.begin() +
k * rec_.size() +
off);
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;
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());
2340 while (rec_snaps_.size() > rec_max_snaps_) rec_snaps_.erase(rec_snaps_.begin());
2342 std::vector<float>().swap(rec_rows_);
2346 auto it = weights_.find(
name);
2347 if (
it == weights_.end())
throw std::runtime_error(
"IR weight '" +
name +
"' was not bound");
2351const std::vector<float>& Interpreter::weight(
const std::string&
name)
const {
2353 if (
w.quantised())
throw std::runtime_error(
"IR weight '" +
name +
"' is held quantised but read whole");
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");
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"));
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);
2374 for (
const auto&
n :
nodes) {
2377 if (
n.op ==
"embedding") {
2378 const auto& tokens =
in(
n, 0);
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};
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]);
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_) +
")");
2394 src = &bound(state_weight_);
2396 }
else if (
id < 0 || id >= vocab_ ||
static_cast<size_t>(
id + 1) * d >
W.numel) {
2397 throw std::runtime_error(
"IR embedding: token id " + std::to_string(
id) +
" out of range");
2399 float*
dst =
out.v.data() +
static_cast<size_t>(
t) * d;
2400 if (
src->quantised())
2403 std::memcpy(
dst,
src->f32.data() +
static_cast<size_t>(
row) * d, d *
sizeof(
float));
2405 env[
n.out.at(0)] = std::move(
out);
2406 }
else if (
n.op ==
"pos_embedding") {
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") {
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;
2427 for (
int i = 0;
i < d; ++
i)
mean +=
r[
i];
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];
2435 env[
n.out.at(0)] = std::move(
out);
2436 }
else if (
n.op ==
"split") {
2438 const auto sizes =
n.attrs.at(
"sizes").get<std::vector<int>>();
2441 if (
sizes.size() !=
n.out.size())
throw std::runtime_error(
"IR split: sizes do not match the outputs");
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");
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));
2450 std::vector<Buf>
parts;
2451 std::vector<float*>
ptrs;
2459 for (
size_t o = 0;
o <
sizes.size(); ++
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));
2467 }
else if (
n.op ==
"gelu") {
2469 const bool tanh_form =
n.attrs.value(
"approx",
"none") ==
"tanh";
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];
2475 : 0.5f *
v * (1.0f + std::erf(
v / std::sqrt(2.0f)));
2477 env[
n.out.at(0)] = std::move(
out);
2478 }
else if (
n.op ==
"rmsnorm") {
2481 out.v.resize(
x.v.size());
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()) +
"]");
2493 env[
n.out.at(0)] = std::move(
out);
2494 }
else if (
n.op ==
"linear") {
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)));
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)
2516 env[
n.out.at(0)] = std::move(
out);
2517 }
else if (
n.op ==
"rope") {
2519 const int hd =
n.attrs.at(
"head_dim").get<
int>();
2522 if (!rope_neox_ && rope_dim_ == head_dim_) {
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");
2533 env[
n.out.at(0)] = std::move(q);
2534 env[
n.out.at(1)] = std::move(
k);
2535 }
else if (
n.op ==
"attention") {
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");
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_) +
")");
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));
2557 const float scale =
n.attrs.contains(
"scale") ?
n.attrs.at(
"scale").get<
float>() : 1.0f / std::sqrt(
float(
hd));
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")) {
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));
2571 attention_bias_batch_cpu(q.
v,
cache,
out.v,
T,
rows,
ts ? &kv_pos_ :
nullptr,
qpos,
hq,
hkv,
hd,
scale,
W.data(),
2573 }
else if (
has(
n.attrs,
"relative_kv")) {
2574 const json&
rk =
n.attrs.at(
"relative_kv");
2575 const int K =
rk.at(
"max_distance").get<
int>();
2577 auto table = [&](
const char*
member,
int&
w) ->
const float* {
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();
2591 std::vector<int>
qpos(
static_cast<size_t>(
T));
2594 attention_relkv_batch_cpu(q.
v,
cache,
out.v,
T,
rows,
ts ? &kv_pos_ :
nullptr,
qpos,
hq,
hkv,
hd,
scale,
RK,
rk_w,
2598 else if (
window > 0 && attn_row_ >= 0) {
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");
2611 std::vector<std::vector<int>>
rows(1);
2612 for (
int r = 0;
r < attn_row_; ++
r)
2614 rows[0].push_back(attn_row_);
2617 attention_window_batch_cpu(q.
v,
cache,
out.v,
T,
start_pos,
hq,
hkv,
hd,
scale,
window,
sinks,
softcap);
2620 env[
n.out.at(0)] = std::move(
out);
2621 }
else if (
n.op ==
"mla_attention") {
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));
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_) +
")");
2637 const size_t w =
static_cast<size_t>(
r) +
dr;
2638 for (
int t = 0;
t <
T; ++
t) {
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));
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") {
2657 for (
size_t i = 0;
i <
a.v.size(); ++
i)
out.v[
i] =
a.v[
i] +
b.v[
i];
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") {
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);
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";
2678 has(
n.attrs,
"selection_bias") ? weight(layer_name(
n.attrs.at(
"selection_bias").get<std::string>(),
layer)).data()
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>();
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 " +
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)
2694 env[
n.out.at(0)] = std::move(
out);
2695 }
else if (
n.op ==
"silu") {
2699 env[
n.out.at(0)] = std::move(
out);
2700 }
else if (
n.op ==
"sub") {
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") {
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") {
2714 cpu::affine(
x.v.data(),
out.v.data(),
x.v.size(),
at_layer(
n.attrs.at(
"scale"),
layer).get<
float>(),
2716 env[
n.out.at(0)] = std::move(
out);
2717 }
else if (
n.op ==
"scale") {
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));
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") {
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]");
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") {
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") {
2752 std::vector<const float*>
ptrs;
2754 for (
size_t k = 0;
k <
n.in.size(); ++
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());
2760 if (
x0.width <= 0 ||
x0.v.size() %
x0.width)
throw std::runtime_error(
"IR expr: input is not [T, w]");
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") {
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") {
2774 if (
n.out.at(0) ==
n.in.at(0))
2778 out.items.push_back(
x);
2779 env[
n.out.at(0)] = std::move(
out);
2780 }
else if (
n.op ==
"depth_attend") {
2782 std::vector<const float*>
src;
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());
2792 if (
src.empty())
throw std::runtime_error(
"IR depth_attend: no sources");
2793 const float*
w =
nullptr;
2794 if (
src.size() > 1) {
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]");
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") {
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) {
2816 const size_t r =
static_cast<size_t>(
t) *
w;
2818 rec_row_done(slot,
layer,
t);
2820 env[
n.out.at(0)] = std::move(
out);
2821 }
else if (
n.op ==
"ssm_scan") {
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);
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;
2836 rec_row_done(slot,
layer,
t);
2838 env[
n.out.at(0)] = std::move(
out);
2839 }
else if (
n.op ==
"conv1d_causal") {
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));
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);
2854 env[
n.out.at(0)] = std::move(
out);
2855 }
else if (
n.op ==
"selective_scan") {
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,
2870 rec_row_done(slot,
layer,
t);
2872 env[
n.out.at(0)] = std::move(
out);
2873 }
else if (
n.op ==
"recurrence") {
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) {
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]));
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())
2890 st.push_back(rec_tensor(slot,
s.
name.c_str(),
layer,
s.numel));
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) {
2895 for (
size_t k = 0;
k <
ins.size(); ++
k)
row[
k] =
ins[
k]->
v.data() +
static_cast<size_t>(
t) *
ins[
k]->width;
2897 rec_row_done(slot,
layer,
t);
2899 env[
n.out.at(0)] = std::move(
out);
2910 const int s = mor_step_[
layer];
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");
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_);
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};
2930 for (
int t = 0;
t <
T; ++
t)
2931 if (mor_depth_.at(
t) >
s)
active.push_back(
t);
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;
2939 attn_row_ =
static_cast<int>(mor_rows_[
layer].size());
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);
2952 if (
last && mor_gate_score_)
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]);
2958 const int T =
static_cast<int>(tokens.size());
2959 if (
T == 0)
return {};
2964 throw std::runtime_error(
"IR forward at position " + std::to_string(
start_pos) +
": the sequence holds " +
2965 std::to_string(ids_.size()) +
" ids");
2968 set_rope_long(
want);
2971 ids_.insert(ids_.end(), tokens.begin(), tokens.end());
2976 std::vector<int>
all(ids_.begin(), ids_.begin() +
start_pos);
2977 all.insert(
all.end(), tokens.begin(), tokens.end());
2978 set_rope_long(
want);
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;
2984 out = forward_impl(
all, 0,
true, 0);
2985 const size_t width =
out.size() /
all.size();
2986 out.erase(
out.begin(),
out.end() -
static_cast<size_t>(
T) * width);
2990 ids_ = std::move(
all);
2994std::vector<float> Interpreter::forward_impl(
const std::vector<int>& tokens,
int start_pos,
bool all_rows,
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_) +
")");
3008 if (state_k_ > 0 || turn_state_) ts_begin(tokens,
start_pos);
3011 env[
"tokens"] =
Buf{std::vector<float>(tokens.begin(), tokens.end()), 1};
3013 if (!mor_step_.empty()) {
3016 for (
auto&
rows : mor_rows_)
3020 for (
int l = 0;
l < n_layers_; ++
l) {
3021 if (!mor_step_.empty() && mor_step_[
l] >= 0)
3030 if (evict_) ts_evict_below(ts_seg_.back());
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);
3040 for (
auto&
kv :
env) {
3046 auto it =
env.find(
"logits");
3047 if (
it ==
env.end())
throw std::runtime_error(
"IR head produced no 'logits'");
3049 return std::move(
it->second.v);
3054 if (
gpu)
throw std::runtime_error(std::string(
"IR ") +
what +
" is implemented for the CPU path only");
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());
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_;
3072 std::vector<int>
p(
static_cast<size_t>(seq_len_));
3073 for (
int i = 0;
i < seq_len_; ++
i)
p[
i] =
i;
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_;
3084 std::vector<int>
p(
static_cast<size_t>(seq_len_));
3085 for (
int i = 0;
i < seq_len_; ++
i)
p[
i] =
i;
3089std::vector<Interpreter::RecurrentTensorInfo> Interpreter::recurrent_tensors()
const {
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});
3096std::vector<float> Interpreter::recurrent_values(
const std::string& slot,
const std::string&
tensor,
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_)
3104 for (
const auto&
rt :
s.tensors)
3106 const auto b = rec_.begin() +
static_cast<size_t>(
application) * rec_floats_ +
rt.offset;
3107 return std::vector<float>(
b,
b +
rt.floats);
3109 throw std::runtime_error(
"IR recurrent_values: no tensor '" +
tensor +
"' in a slot '" + slot +
"'");
3112void Interpreter::truncate(
int n) {
3115 throw std::runtime_error(
"IR truncate to " + std::to_string(
n) +
": the state covers " + std::to_string(seq_len_) +
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_) {
3125 const int m =
n > 0 ? ts_seg_after(
n - 1) : 0;
3126 if (turn_state_ &&
m < evicted_below_) evicted_below_ =
m;
3129 while (!kv_pos_.empty() && kv_pos_.back() >=
n) kv_pos_.pop_back();
3131 for (
auto&
rows : mor_rows_)
3133 if (rec_floats_ > 0 &&
n != rec_pos_) {
3138 rec_ = rec_snaps_.at(
n);
3139 rec_snaps_.erase(rec_snaps_.upper_bound(
n), rec_snaps_.end());
3144 if (rope_ff_ctx_ > 0) {
3145 ids_.resize(
static_cast<size_t>(
n));
3146 if (
n > 0 && (
n > rope_ff_ctx_) != rope_long_) {
3148 const std::vector<int>
prefix = ids_;
3149 set_rope_long(
n > rope_ff_ctx_);
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);
3160 const size_t row =
static_cast<size_t>(kv_heads_) * head_dim_;
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);
3167 s.ts_sidx = ts_sidx_;
3169 s.evicted_below = evicted_below_;
3170 s.mor_rows = mor_rows_;
3171 s.mor_depth = mor_depth_;
3173 s.rec_pos = rec_pos_;
3174 s.rec_snaps = rec_snaps_;
3176 s.rope_long = rope_long_;
3180void Interpreter::load_state(
const State&
s) {
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());
3191 seq_len_ =
s.seq_len;
3193 ts_sidx_ =
s.ts_sidx;
3195 evicted_below_ =
s.evicted_below;
3196 mor_rows_ =
s.mor_rows;
3197 mor_depth_ =
s.mor_depth;
3199 rec_pos_ =
s.rec_pos;
3200 rec_snaps_ =
s.rec_snaps;
3202 rope_long_ =
s.rope_long;
3209void Interpreter::init_device() {
3216 for (
auto&
w : weights_) {
3217 if (
w.second.quantised())
3218 throw std::runtime_error(
"IR CUDA: weight '" +
w.first +
"' is still quantised");
3219 std::vector<float>&
host =
w.second.f32;
3224 floats +=
host.size();
3225 std::vector<float>().swap(
host);
3227 const size_t kv_floats =
static_cast<size_t>(max_len_) * kv_heads_ * head_dim_;
3228 dk_.assign(n_layers_,
nullptr);
3229 dv_.assign(n_layers_,
nullptr);
3231 if (turn_state_ || !mor_step_.empty())
3233 ": the CUDA path quantises plain causal attention only (no turn_state, depth_route)");
3234 const size_t row =
static_cast<size_t>(kv_heads_) * head_dim_;
3238 for (
int l = 0;
l < n_layers_; ++
l) {
3244 if (
sb == 0)
continue;
3251 for (
const auto&
n : block_)
3258 std::to_string(
kv_dev_bytes_ >> 20) +
" MiB (f32 would be " +
3259 std::to_string((
static_cast<size_t>(n_layers_) * 2 *
kv_floats *
sizeof(
float)) >> 20) +
3268 for (
const auto&
tab : rope_tabs_) {
3269 std::vector<float>
flat(
tab.size() * 2);
3270 for (
size_t i = 0;
i <
tab.size(); ++
i) {
3279 if (rec_floats_ > 0) {
3280 const size_t bytes = rec_.size() *
sizeof(
float);
3296 std::to_string(floats *
sizeof(
float) / (1024 * 1024)) +
" MiB f32), " +
3297 (
kv_floats > 0 ? std::to_string(n_layers_) +
" kv slots of " + std::to_string(max_len_) +
" positions"
3298 : std::
string(
"no kv slots")) +
3299 (rec_floats_ > 0 ?
" and " + std::
to_string(rec_floats_) +
" recurrent floats per block" :
"") +
3304int Interpreter::slot(
const std::string&
name) {
3313std::vector<Interpreter::DStep> Interpreter::compile_nodes(
const std::vector<Node>&
nodes,
int layer) {
3314 static const std::unordered_map<std::string, DOp>
kOps = {
3315 {
"embedding", DOp::Embedding}, {
"pos_embedding", DOp::PosEmbedding}, {
"layernorm", DOp::LayerNorm},
3316 {
"split", DOp::Split}, {
"gelu", DOp::Gelu}, {
"rmsnorm", DOp::RmsNorm},
3317 {
"linear", DOp::Linear}, {
"rope", DOp::Rope}, {
"attention", DOp::Attention},
3318 {
"add", DOp::Add}, {
"mul", DOp::Mul}, {
"moe_ffn", DOp::MoeFfn},
3319 {
"silu", DOp::Silu}, {
"sub", DOp::Sub}, {
"relu", DOp::Relu},
3320 {
"sigmoid", DOp::Sigmoid}, {
"affine", DOp::Affine}, {
"mul_col", DOp::MulCol},
3321 {
"rms_row", DOp::RmsRow}, {
"list", DOp::List}, {
"append", DOp::Append},
3322 {
"depth_attend", DOp::DepthAttend}, {
"linear_attention", DOp::LinearAttention}, {
"ssm_scan", DOp::SsmScan},
3323 {
"expr", DOp::Expr}, {
"conv1d_causal", DOp::Conv1dCausal}, {
"selective_scan", DOp::SelectiveScan},
3324 {
"scale", DOp::Scale}, {
"mla_attention", DOp::MlaAttention}};
3325 auto wname = [&](
const Node&
n) {
3326 std::string
w = layer_name(
n.attrs.at(
"weight").get<std::string>(),
layer);
3327 if (!weights_.count(
w) &&
has(
n.attrs,
"tied_to"))
w = layer_name(
n.attrs.at(
"tied_to").get<std::string>(),
layer);
3330 auto elems = [&](
const std::string&
wn) {
3335 std::vector<DStep>
steps;
3336 for (
const auto&
n :
nodes) {
3337 if (
n.op ==
"depth_route")
continue;
3338 AppIndex
ti(app_index_,
n);
3340 auto op =
kOps.find(
n.op);
3341 if (op ==
kOps.end())
throw std::runtime_error(
"IR op '" +
n.op +
"' has no CUDA kernel in ember's interpreter");
3347 case DOp::Embedding:
3348 case DOp::PosEmbedding:
3350 case DOp::LayerNorm:
3355 s.w_ne0 =
static_cast<int>(shapes_.at(
s.wname).at(0));
3356 if (
has(
n.attrs,
"bias") && (
s.op == DOp::LayerNorm ||
s.op == DOp::Linear))
3357 s.b =
dweight(layer_name(
n.attrs.at(
"bias").get<std::string>(),
layer));
3358 if (
s.op == DOp::LayerNorm && !
s.b)
throw std::runtime_error(
"IR layernorm '" +
s.wname +
"' has no bias");
3359 s.eps =
n.attrs.value(
"eps", 1e-5f);
3360 if (
s.op == DOp::RmsNorm &&
has(
n.attrs,
"group"))
s.n =
n.attrs.at(
"group").get<
int>();
3361 if (
s.op == DOp::Embedding &&
has(
n.attrs,
"state_weight"))
s.state_w =
dweight(state_weight_);
3364 s.sizes =
n.attrs.at(
"sizes").get<std::vector<int>>();
3365 if (
s.sizes.size() !=
n.out.size())
throw std::runtime_error(
"IR split: sizes do not match the outputs");
3366 s.groups =
n.attrs.contains(
"groups") ?
n.attrs.at(
"groups").get<
int>() : 1;
3367 if (
s.groups < 1)
throw std::runtime_error(
"IR_SPEC 5.5 (rule 10): split groups is not positive");
3369 case DOp::MlaAttention: {
3371 s.hq =
at_layer(
n.attrs.at(
"heads"),
L).get<
int>();
3372 s.kv_rank =
at_layer(
n.attrs.at(
"kv_rank"),
L).get<
int>();
3373 s.qk_nope =
at_layer(
n.attrs.at(
"qk_nope"),
L).get<
int>();
3374 s.qk_rope =
at_layer(
n.attrs.at(
"qk_rope"),
L).get<
int>();
3375 s.v_dim =
at_layer(
n.attrs.at(
"v_head_dim"),
L).get<
int>();
3376 s.scale =
n.attrs.at(
"scale").get<
float>();
3377 s.w =
dweight(layer_name(
n.attrs.at(
"k_b").get<std::string>(),
layer));
3378 s.b =
dweight(layer_name(
n.attrs.at(
"v_b").get<std::string>(),
layer));
3379 attention_window(
n,
L,
s.window,
s.sinks);
3380 if (kv_heads_ != 1 || head_dim_ !=
s.kv_rank +
s.qk_rope)
3381 throw std::runtime_error(
"IR_SPEC 5.20 (rule 12): mla_attention's kv slot is not kv_heads 1, head_dim kv_rank + qk_rope");
3383 Logger::info(
"[IR] CUDA multi-head latent attention (ir_mla_attention_cuda, absorbed form): heads " +
3384 std::to_string(
s.hq) +
", kv_rank " + std::to_string(
s.kv_rank) +
", qk_rope " +
3385 std::to_string(
s.qk_rope));
3389 s.flag =
n.attrs.value(
"approx",
"none") ==
"tanh";
3392 s.hd =
n.attrs.at(
"head_dim").get<
int>();
3393 s.n_rot = rope_dim_;
3394 s.neox = rope_neox_;
3403 case DOp::Attention:
3404 s.hd =
n.attrs.at(
"head_dim").get<
int>();
3405 s.hq =
n.attrs.at(
"heads").get<
int>();
3406 s.hkv =
n.attrs.at(
"kv_heads").get<
int>();
3407 if (
s.hd != head_dim_ ||
s.hkv != kv_heads_)
throw std::runtime_error(
"IR attention shape differs from its kv state");
3408 s.scale =
n.attrs.contains(
"scale") ?
n.attrs.at(
"scale").get<
float>() : 1.0f / std::
sqrt(
float(
s.
hd));
3409 s.ts =
n.attrs.value(
"mask",
"causal") ==
"turn_state";
3412 if (
s.window > 0 &&
layer <= 0)
3413 Logger::info(
"[IR] CUDA sliding-window attention (ir_window_attention_cuda): window " +
3414 std::to_string(
s.window) +
", sinks " + std::to_string(
s.sinks) +
3419 const auto&
ex =
n.attrs.at(
"experts");
3420 s.wname = layer_name(
ex.at(
"gate").get<std::string>(),
layer);
3421 s.w =
dweight(layer_name(
n.attrs.at(
"router").get<std::string>(),
layer));
3424 s.down =
dweight(layer_name(
ex.at(
"down").get<std::string>(),
layer));
3425 s.E =
n.attrs.at(
"n_experts").get<
int>();
3426 s.top_k =
n.attrs.at(
"top_k").get<
int>();
3427 const auto&
gs = shapes_.at(
s.wname);
3428 if (
gs.size() != 3 ||
gs[2] !=
s.E)
3429 throw std::runtime_error(
"IR moe_ffn '" +
s.wname +
"': expert stack shape does not match n_experts");
3430 s.w_ne0 =
static_cast<int>(
gs[0]);
3431 s.d_ff =
static_cast<int>(
gs[1]);
3432 s.flag =
n.attrs.value(
"gate_norm",
"renorm") ==
"renorm";
3440 s.eps =
n.attrs.at(
"eps").get<
float>();
3442 case DOp::DepthAttend:
3445 s.eps =
n.attrs.value(
"eps", 1e-5f);
3446 s.wname = layer_name(
n.attrs.at(
"weight").get<std::string>(),
layer);
3452 case DOp::LinearAttention: {
3453 s.rec = &rec_slot(
n,
"linear_attention");
3454 s.hq =
n.attrs.at(
"heads").get<
int>();
3455 s.hd =
n.attrs.at(
"head_dim").get<
int>();
3456 const size_t w =
static_cast<size_t>(
s.hq) *
s.hd;
3458 s.rec_a =
drec_ + (rec_tensor(*
s.rec,
"num",
layer,
w) - rec_.data());
3459 s.rec_b =
drec_ + (rec_tensor(*
s.rec,
"den",
layer,
w) - rec_.data());
3460 s.eps =
n.attrs.value(
"den_floor", 1e-8f);
3464 s.rec = &rec_slot(
n,
"ssm_scan");
3465 s.n =
n.attrs.at(
"n").get<
int>();
3466 s.rec_a =
drec_ + (rec_tensor(*
s.rec,
"s",
layer,
s.n) - rec_.data());
3476 if (
has(
n.attrs,
"bias"))
s.b =
dweight(layer_name(
n.attrs.at(
"bias").get<std::string>(),
layer));
3479 s.expr = expr::lower(expr::cached(
n.attrs.at(
"expr").get<std::string>(),
n.in.size()));
3481 case DOp::Conv1dCausal:
3482 s.rec = &rec_slot(
n,
"conv1d_causal");
3483 s.n =
n.attrs.at(
"channels").get<
int>();
3484 s.kernel =
n.attrs.at(
"kernel").get<
int>();
3485 s.rec_a =
drec_ + (rec_tensor(*
s.rec,
"x",
layer,
static_cast<size_t>(
s.kernel - 1) *
s.n) - rec_.data());
3486 s.wname = layer_name(
n.attrs.at(
"weight").get<std::string>(),
layer);
3488 if (
elems(
s.wname) !=
static_cast<size_t>(
s.n) *
s.kernel)
3489 throw std::runtime_error(
"IR conv1d_causal '" +
s.wname +
"' is not [channels, kernel]");
3490 if (
has(
n.attrs,
"bias"))
s.b =
dweight(layer_name(
n.attrs.at(
"bias").get<std::string>(),
layer));
3492 case DOp::SelectiveScan:
3493 s.rec = &rec_slot(
n,
"selective_scan");
3494 s.n =
n.attrs.at(
"channels").get<
int>();
3495 s.d_state =
n.attrs.at(
"d_state").get<
int>();
3496 s.rec_a =
drec_ + (rec_tensor(*
s.rec,
"s",
layer,
static_cast<size_t>(
s.n) *
s.d_state) - rec_.data());
3497 s.wname = layer_name(
n.attrs.at(
"a").get<std::string>(),
layer);
3499 if (
elems(
s.wname) !=
static_cast<size_t>(
s.n) *
s.d_state)
3500 throw std::runtime_error(
"IR selective_scan '" +
s.wname +
"' is not [channels, d_state]");
3501 if (
has(
n.attrs,
"d"))
s.ssm_d =
dweight(layer_name(
n.attrs.at(
"d").get<std::string>(),
layer));
3502 s.flag =
n.attrs.value(
"dt_softplus",
true);
3515 if (
s.op == DOp::Embedding ||
s.op == DOp::PosEmbedding) {
3517 rows =
static_cast<int>(std::min<size_t>(
rows,
s.w_elems /
s.w_ne0));
3518 if (
s.op == DOp::Embedding)
rows = std::min(
rows, vocab_);
3520 steps.push_back(std::move(
s));
3525void Interpreter::compile_device() {
3532 if (!mor_step_.empty()) {
3538 for (
const auto&
n : block_) {
3539 if (
n.op ==
"depth_route")
continue;
3540 for (
const auto&
name :
n.
in) {
3541 const int sl = slot(
name);
3558 Logger::info(
"[IR] CUDA device plan: " + std::to_string(
plan_embed_.size()) +
" + " + std::to_string(n_layers_) +
3559 " x " + std::to_string(block_.size()) +
" + " + std::to_string(
plan_head_.size()) +
" steps over " +
3560 std::to_string(
slot_names_.size()) +
" buffer slots");
3563void Interpreter::free_device() {
3565 for (
auto&
b :
bufs)
3618const float* Interpreter::dweight(
const std::string&
name)
const {
3620 if (
it ==
dweights_.end())
throw std::runtime_error(
"IR weight '" +
name +
"' was not bound");
3624void Interpreter::grow(
DBuf&
b,
size_t need) {
3625 if (
b.cap >=
need)
return;
3628 if (
capturing_)
throw std::runtime_error(
"IR CUDA graph: a buffer must grow during capture");
3635Interpreter::DBuf& Interpreter::dout(
int slot,
int rows,
int width, std::initializer_list<const DBuf*>
inputs) {
3636 return dout(slot,
rows, width, std::vector<const DBuf*>(
inputs));
3639Interpreter::DBuf& Interpreter::dout(
int slot,
int rows,
int width,
const std::vector<const DBuf*>&
inputs) {
3647 grow(*
b,
static_cast<size_t>(
rows) * width);
3656 grow(
dst,
static_cast<size_t>(
x.rows) *
x.width);
3659 dst.width =
x.width;
3664float* Interpreter::dsnap_take(
int key) {
3678void Interpreter::dsnap_drop(std::map<int, float*>::iterator
it) {
3683void Interpreter::commit() {
3688void Interpreter::run_nodes_device(
const std::vector<DStep>&
steps,
int layer,
int T,
int start_pos) {
3693 throw std::runtime_error(
"IR op '" +
s.node->op +
"': input '" +
s.node->in.at(
k) +
"' is not defined");
3695 throw std::runtime_error(
"IR op '" +
s.node->op +
"': input '" +
s.node->in.at(
k) +
"' is " +
3696 (
want_list ?
"a tensor, not a list" :
"a list, not a tensor"));
3705 for (
int t0 = 0,
t = 0;
t <
T; ++
t) {
3706 const bool kept =
t + 1 <
T && rec_snap_rows_[
t] >= 0;
3707 if (!
kept &&
t + 1 <
T)
continue;
3715 case DOp::Embedding: {
3716 const int d =
s.w_ne0;
3717 const size_t rows = std::min<size_t>(
s.w_elems / d,
static_cast<size_t>(vocab_));
3718 for (
int t = 0;
t <
T; ++
t) {
3720 if (
s.state_w &&
id >= vocab_) {
3721 if (
id >= vocab_ + state_k_)
3722 throw std::runtime_error(
"IR embedding: token id " + std::to_string(
id) +
" is past the state slots (vocab " +
3723 std::to_string(vocab_) +
", k " + std::to_string(state_k_) +
")");
3724 }
else if (
id < 0 ||
static_cast<size_t>(
id) >=
rows) {
3725 throw std::runtime_error(
"IR embedding: token id " + std::to_string(
id) +
" out of range");
3735 case DOp::PosEmbedding: {
3737 const int d =
x.width;
3738 if (
drowpos_)
throw std::runtime_error(
"IR pos_embedding inside a recursion step has no CUDA form");
3739 if (
s.w_ne0 != d)
throw std::runtime_error(
"IR pos_embedding '" +
s.wname +
"': width differs from x");
3740 if (
static_cast<size_t>(
start_pos +
T) * d >
s.w_elems)
3741 throw std::runtime_error(
"IR pos_embedding: position " + std::to_string(
start_pos +
T - 1) +
" exceeds '" +
3747 case DOp::LayerNorm: {
3758 throw std::runtime_error(
"IR_SPEC 5.5 (rule 37): split sizes sum to " + std::to_string(
total) +
3759 (
s.groups > 1 ?
" times groups " + std::to_string(
s.groups) : std::
string()) +
3760 ", not the input width " + std::
to_string(
x.width));
3764 for (
size_t o = 0;
o <
s.sizes.size(); ++
o) {
3767 s.sizes[
o] *
sizeof(
float),
static_cast<size_t>(
T) *
s.groups,
3773 case DOp::MlaAttention: {
3775 const int H =
s.hq,
r =
s.kv_rank,
dn =
s.qk_nope,
dr =
s.qk_rope,
dv =
s.v_dim;
3776 if (
qn.width !=
H *
dn ||
qr.width !=
H *
dr ||
c.width !=
r ||
kr.width !=
dr)
3777 throw std::runtime_error(
"IR_SPEC 5.20 (rule 37): mla_attention input widths do not fit its attributes");
3778 if (attn_row_ >= 0 ||
s.ts)
throw std::runtime_error(
"IR mla_attention under depth_route or turn_state");
3780 throw std::runtime_error(
"IR mla_attention: position " + std::to_string(
start_pos +
T) +
3781 " exceeds the kv state (" + std::to_string(max_len_) +
")");
3785 ir_mla_attention_cuda(
qn.p,
qr.p,
KV,
s.w,
s.b,
out.p,
T,
start_pos,
H,
r,
dn,
dr,
dv,
s.scale,
s.window,
3795 case DOp::RmsNorm: {
3797 const int g =
s.n ?
s.n :
x.width;
3798 if (g > 0 &&
x.width % g != 0)
3799 throw std::runtime_error(
"IR_SPEC 5.6 (rule 37): rmsnorm '" +
s.wname +
"': group " + std::to_string(g) +
3800 " does not divide the input width " + std::to_string(
x.width));
3801 if (g <= 0 ||
x.width % g != 0 ||
s.w_elems !=
static_cast<size_t>(g))
3802 throw std::runtime_error(
"IR rmsnorm '" +
s.wname +
"': group " + std::to_string(g) +
" does not fit width " +
3803 std::to_string(
x.width) +
" and weight [" + std::to_string(
s.w_elems) +
"]");
3810 if (
s.w_ne0 !=
x.width)
3811 throw std::runtime_error(
"IR linear '" +
s.wname +
"': input width " + std::to_string(
x.width) +
3812 " does not match the weight's ne0 " + std::to_string(
s.w_ne0));
3813 const int out_dim =
static_cast<int>(
s.w_elems /
x.width);
3821 const float*
tab = rope_long_ &&
s.rope_long ?
s.rope_long :
s.rope;
3823 throw std::runtime_error(
"IR rope: position " + std::to_string(
start_pos +
T - 1) +
" exceeds the rope table");
3828 for (
size_t o = 0;
o < 2; ++
o) {
3830 if (
s.out.at(
o) ==
s.in.at(
o))
continue;
3838 if (!
s.neox &&
s.n_rot ==
s.hd) {
3841 if (q->width %
s.hd != 0 ||
k->width %
s.hd != 0)
3842 throw std::runtime_error(
"IR rope: q/k widths are not whole heads");
3846 }
else if (!
s.neox &&
s.n_rot ==
s.hd) {
3849 if (q->width %
s.hd != 0 ||
k->width %
s.hd != 0)
3850 throw std::runtime_error(
"IR rope: q/k widths are not whole heads");
3856 case DOp::Attention: {
3860 throw std::runtime_error(
"IR attention: q/k/v widths do not match heads * head_dim");
3861 if (attn_row_ >= 0) {
3865 if (attn_row_ +
T > max_len_)
3866 throw std::runtime_error(
"IR attention: " + std::to_string(attn_row_ +
T) +
" kv rows exceed the kv state (" +
3867 std::to_string(max_len_) +
")");
3869 throw std::runtime_error(
"EMBER_KV: a recursion step's compact cache is not quantised on CUDA");
3875 attention_decode_at_cuda(q.p, K,
V,
out.p,
hq, attn_row_ + 1,
hd,
s.scale, max_len_,
hkv,
st);
3877 attention_batch_prefill_cuda(q.p,
nullptr,
nullptr, K,
V,
out.p,
T, attn_row_, max_len_,
hq,
hkv,
hd,
3878 s.scale,
st,
nullptr);
3882 throw std::runtime_error(
"IR attention: position " + std::to_string(
start_pos +
T) +
" exceeds the kv state (" +
3883 std::to_string(max_len_) +
")");
3884 const size_t row =
static_cast<size_t>(
hkv) *
hd;
3886 if (
s.ts ||
s.window > 0)
3887 throw std::runtime_error(
"EMBER_KV: turn_state and windowed attention are not quantised on CUDA");
3889 ember_kvq::write_cuda(kv_fmt_,
Kd,
Ks,
Vd,
Vs,
k.p,
v.p,
T,
static_cast<int>(
row),
start_pos,
st,
dpos_);
3892 ember_kvq::decode_cuda(kv_fmt_, q.p,
Kd,
Ks,
Vd,
Vs,
out.p,
hq,
hkv,
hd,
s.scale, max_len_,
start_pos + 1,
3896 float*
V =
dkv_f32_ +
static_cast<size_t>(max_len_) *
row;
3898 attention_batch_prefill_cuda(q.p,
nullptr,
nullptr, K,
V,
out.p,
T,
start_pos, max_len_,
hq,
hkv,
hd,
3899 s.scale,
st,
nullptr);
3909 ir_turn_state_attention_cuda(q.p, K,
V,
out.p,
T, kv_row0_,
dts_code_,
hq,
hkv,
hd,
s.scale,
st,
drow);
3914 if (
s.window > 0 ||
s.softcap > 0.0f)
3916 ir_window_attention_cuda(q.p, K,
V,
out.p,
T,
start_pos,
hq,
hkv,
hd,
s.scale,
s.window > 0 ?
s.window : max_len_,
3918 else if (
T == 1 &&
dpos_)
3919 attention_devpos_cuda(q.p, K,
V,
out.p,
hq,
hd,
s.scale, max_len_,
hkv,
dstep_,
st);
3921 attention_decode_at_cuda(q.p, K,
V,
out.p,
hq,
start_pos + 1,
hd,
s.scale, max_len_,
hkv,
st);
3923 attention_batch_prefill_cuda(q.p,
nullptr,
nullptr, K,
V,
out.p,
T,
start_pos, max_len_,
hq,
hkv,
hd,
s.scale,
3937 if (
s.w_ne0 !=
x.width)
3938 throw std::runtime_error(
"IR moe_ffn '" +
s.wname +
"': expert stack shape does not match d and n_experts");
3939 const int d =
x.width;
3942 moe_ffn_cuda(
cublas_,
x.p,
T, d,
s.d_ff,
s.E,
s.top_k,
s.flag,
s.w,
s.gate,
s.up,
s.down,
dmoe_.alloc,
out.p,
3964 const size_t n =
static_cast<size_t>(
x.rows) *
x.width;
3965 if (
s.op == DOp::Relu)
3967 else if (
s.op == DOp::Sigmoid)
3975 if (
s.w_elems !=
static_cast<size_t>(
x.width))
3976 throw std::runtime_error(
"IR_SPEC 5.4 (rule 37): scale '" +
s.wname +
"' has " + std::to_string(
s.w_elems) +
3977 " values for width " + std::to_string(
x.width));
3984 if (
c.width != 1 ||
c.rows !=
T ||
x.rows !=
T)
3985 throw std::runtime_error(
"IR_SPEC 5.12 (rule 37): mul_col: c must be [T, 1] beside x [T, w]");
3992 if (
x.width <= 0 ||
x.rows !=
T)
throw std::runtime_error(
"IR rms_row: input is not [T, w]");
3999 if (
items.size() <
s.in.size())
items.resize(
s.in.size());
4004 L.width =
items[0].width;
4005 L.items =
static_cast<int>(
s.in.size());
4011 const int o =
s.out[0];
4014 if (
items.size() <
static_cast<size_t>(
src.items))
items.resize(
src.items);
4019 L.width =
src.width;
4020 L.items =
src.items;
4024 if (
items.size() <
static_cast<size_t>(
L.items) + 1)
items.resize(
L.items + 1);
4029 case DOp::DepthAttend: {
4031 std::vector<const float*>
src;
4032 std::vector<const DBuf*> tensors;
4035 if (d == 0) d =
b.width;
4036 if (
b.width != d || d <= 0 ||
b.rows !=
T)
4037 throw std::runtime_error(
"IR_SPEC 5.18 (rule 37): depth_attend sources must all be one shape [T, d]");
4041 for (
size_t k = 1;
k <
s.in.size(); ++
k) {
4042 tensors.push_back(&
in(
s,
k));
4045 if (
src.empty())
throw std::runtime_error(
"IR depth_attend: no sources");
4047 if (
src.size() == 1) {
4052 if (!
s.w)
throw std::runtime_error(
"IR depth_attend: pseudo-query '" +
s.wname +
"' is not in the GGUF");
4053 if (
s.w_elems !=
static_cast<size_t>(d))
4054 throw std::runtime_error(
"IR depth_attend: pseudo-query '" +
s.wname +
"' is not [d]");
4058 case DOp::LinearAttention: {
4060 const int w =
s.hq *
s.hd;
4061 if (q.width !=
w ||
k.width !=
w ||
v.width !=
w || q.rows !=
T ||
k.rows !=
T ||
v.rows !=
T)
4062 throw std::runtime_error(
"IR linear_attention: q/k/v widths do not match heads * head_dim");
4065 const size_t r =
static_cast<size_t>(
t0) *
w;
4072 std::vector<const float*>
ins;
4073 std::vector<const DBuf*>
bufs;
4074 for (
size_t k = 0;
k <
s.in.size(); ++
k) {
4076 if (
a.width !=
x0.width ||
a.rows !=
x0.rows)
4077 throw std::runtime_error(
"IR_SPEC 5.19 (rule 37): expr inputs must share one shape [T, w]");
4081 if (
x0.width <= 0 ||
x0.rows <= 0)
throw std::runtime_error(
"IR expr: input is not [T, w]");
4087 case DOp::Conv1dCausal: {
4089 if (
x.width !=
s.n ||
x.rows !=
T)
throw std::runtime_error(
"IR conv1d_causal: input width differs from channels");
4092 const size_t r =
static_cast<size_t>(
t0) *
s.n;
4097 case DOp::SelectiveScan: {
4099 const int D =
s.n,
N =
s.d_state;
4100 if (
x.width !=
D ||
dt.width !=
D ||
bm.width !=
N ||
cm.width !=
N ||
x.rows !=
T ||
dt.rows !=
T ||
4101 bm.rows !=
T ||
cm.rows !=
T)
4102 throw std::runtime_error(
"IR selective_scan: x/dt must be [T, channels] and B/C [T, d_state]");
4105 const size_t rd =
static_cast<size_t>(
t0) *
D,
rn =
static_cast<size_t>(
t0) *
N;
4106 ir_selective_scan_cuda(
x.p +
rd,
dt.p +
rd,
bm.p +
rn,
cm.p +
rn,
s.w,
s.ssm_d,
s.rec_a,
out.p +
rd,
n,
D,
N,
4111 case DOp::SsmScan: {
4113 if (
u.width !=
s.n ||
u.rows !=
T)
throw std::runtime_error(
"IR ssm_scan: input width differs from n");
4116 const size_t r =
static_cast<size_t>(
t0) *
s.n;
4126void Interpreter::run_device(
int T,
int start_pos) {
4130 for (
int l = 0;
l < n_layers_; ++
l) {
4131 if (!mor_step_.empty() && mor_step_[
l] >= 0)
4142 const int s = mor_step_[
layer];
4144 const bool last =
layer == n_layers_ - 1 || mor_step_[
layer + 1] !=
s;
4146 grow(
b,
static_cast<size_t>(
rows) * width);
4155 if (
x.rows !=
T ||
x.is_list)
throw std::runtime_error(
"IR depth_route: input '" + mor_x_ +
"' is not a tensor");
4156 const int d =
x.width;
4157 const float*
W =
dweight(mor_router_);
4160 if (shapes_.at(mor_router_).at(0) != d ||
numel !=
static_cast<size_t>(mor_R_) * d)
4161 throw std::runtime_error(
"IR depth_route '" + mor_router_ +
"' is not [n_rec, d]");
4164 std::vector<float>
logits(
static_cast<size_t>(
T) * mor_R_);
4168 mor_depth_.assign(
T, 0);
4169 mor_g_.assign(
T, 0.0f);
4170 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]);
4173 const std::vector<float>
depth(mor_depth_.begin(), mor_depth_.end());
4182 for (
int t = 0;
t <
T; ++
t)
4187 if (
xf.rows !=
T ||
xf.is_list)
throw std::runtime_error(
"IR depth_route: input '" + mor_x_ +
"' is not a tensor");
4188 const int d =
xf.width;
4195 std::vector<int>
h(
static_cast<size_t>(2) *
mor_A_);
4214 if (mor_gate_score_) {
4218 std::vector<float> g(
static_cast<size_t>(
mor_A_));
4223 }
else if (
mor_A_ == 0) {
4232 b.rows =
b.items = 0;
4234 if (
full.is_list ||
full.rows !=
T ||
full.width <= 0)
continue;
4242 attn_row_ =
static_cast<int>(mor_rows_[
layer].size());
4259 const int d =
full.width;
4260 if (
y.is_list ||
y.rows !=
A ||
y.width != d)
4261 throw std::runtime_error(
"IR depth_route: the block changes the width of '" + mor_x_ +
"'");
4277bool Interpreter::capture_decode(
int start_pos) {
4283 Logger::warning(
"[IR] CUDA graph: stream capture unavailable; decode runs per-launch.");
4293 err =
"the head produced no [1, vocab] 'logits'";
4294 }
catch (
const std::exception&
e) {
4310 (
graph_off_ ?
"; decode runs per-launch from now on." :
"; this step runs per-launch."));
4318 Logger::info(
"[IR] CUDA graph: captured the T = 1 step (" + std::to_string(n_layers_) +
" blocks, capture " +
4319 std::to_string(
graph_captures_) +
"); decode replays it with the position in device memory");
4324 static const bool enabled = [] {
4325 const char*
v = std::getenv(
"EMBER_IR_GRAPH");
4326 return !(
v && std::string(
v) ==
"0");
4346void Interpreter::ts_upload_codes(
int start_pos,
int T) {
4349 const int n = kv_row0_ +
T;
4350 std::vector<int>
code(
static_cast<size_t>(
n));
4351 for (
int r = 0;
r <
n; ++
r) {
4352 const int p =
r < kv_row0_ ? kv_pos_[
r] :
start_pos + (
r - kv_row0_);
4353 code[
r] = 2 * ts_seg_[
p] + (ts_sidx_[
p] >= 0 ? 1 : 0);
4358std::vector<float> Interpreter::forward_device(
const std::vector<int>& tokens,
int start_pos,
bool all_rows,
4360 const int T =
static_cast<int>(tokens.size());
4364 if (state_k_ > 0 || turn_state_) ts_begin(tokens,
start_pos);
4367 if (!turn_state_)
return;
4369 if (evict_) ts_evict_below(ts_seg_.back());
4372 if (!mor_step_.empty()) {
4375 for (
auto&
rows : mor_rows_)
4400 if (
b.rows ==
T &&
b.alloc) {
4401 b.p =
b.alloc +
static_cast<size_t>(
T -
rows) *
b.width;
4407 if (
b.is_list &&
b.rows ==
T) {
4417 if (
logits.rows == 0)
throw std::runtime_error(
"IR head produced no 'logits'");
4418 std::vector<float>
out(
static_cast<size_t>(
logits.rows) *
logits.width);
static void warning(const std::string &message)
static void info(const std::string &message)
std::vector< RecSlot > rec_slots_
void attention_window(const Node &n, int layer, int &window, int &sinks) const
std::vector< int > mor_step_
std::vector< std::vector< int > > mor_rows_
std::map< const Node *, std::shared_ptr< rec::Recurrence > > recs_
bool evict_declared() const
std::vector< float > rope_scale_layer_
std::vector< Node > head_
std::vector< Node > block_
std::vector< Node > embed_
std::vector< float > rope_theta_layer_
std::vector< int > window_layer_
void init(const nlohmann::json &j, const std::string &ir_json, const GGUFData &gguf, bool use_gpu)
const RecSlot & rec_slot(const Node &n, const char *op) const
std::string state_weight_
std::unordered_map< std::string, Buf > Env
std::vector< int > weight_map_
Interpreter(const std::string &ir_json, const GGUFData &gguf, bool use_gpu=false)
std::vector< float > rope_attn_layer_
static Recurrence compile(const nlohmann::json &node)
void attention_window_batch_cpu(const std::vector< float > &q_batch_roped, KVCacheLayer ¤t_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 ¤t_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 ¤t_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 ¤t_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 ¤t_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)
void gemm_rm_f32_cuda(cublasHandle_t handle, const float *X, const float *W, float *Y, int T, int O, int I, cudaStream_t stream)
void moe_ffn_cuda(cublasHandle_t handle, const float *x, int T, int d, int d_ff, int E, int k, bool renorm, const float *router, const float *gate_exps, const float *up_exps, const float *down_exps, float *scratch, float *out, cudaStream_t stream)
size_t moe_ffn_scratch_floats(int T, int d, int d_ff, int E, int k)
void attention_devpos_cuda(const float *Q_current_dev, const float *K_layer_cache_base, const float *V_layer_cache_base, float *out_dev, int num_heads, int head_dim, float scale, int cache_max_seq_len, int cache_num_kv_heads, const int *step_dev, cudaStream_t stream)
void attention_decode_at_cuda(const float *Q_current_dev, const float *K_layer_cache_base, const float *V_layer_cache_base, float *out_dev, int num_heads, int seq_len, int head_dim, float scale, int cache_max_seq_len, int cache_num_kv_heads, cudaStream_t stream)
GGMLType
Enumeration of GGML tensor data types.
constexpr size_t GGML_QK_K
Block size constants for different quantization formats.
Data structures for GGUF (GPT-Generated Unified Format) file format.
std::map< std::string, std::string > role_
const float * in[expr::Expr::kMaxInputs]
const float * p[kIrDepthSourcesMax]
expr::Instr code[expr::kMaxCode]
const float * col[expr::kMaxColumns]
enum ember_ir::expr::@33::Value::Shape shape
std::vector< Value > items
Logging utilities for the TinyLlama implementation.
ModelConfig parse_model_config_from_gguf(const GGUFData &gguf)
Constants for attention mechanism calculations.
std::vector< Stage > lower(const Expr &e)
const Expr & cached(const std::string &text, size_t n_inputs)
std::string build_ir_from_gguf(const GGUFData &g, bool log)
void ir_linear_attention_cuda(const float *q, const float *k, const float *v, float *num, float *den, float *out, int T, int width, float den_floor, cudaStream_t stream)
void ir_conv1d_causal_cuda(const float *x, const float *w, const float *bias, float *state, float *out, int T, int C, int K, cudaStream_t stream)
void ir_turn_state_attention_cuda(const float *q, const float *K, const float *V, float *out, int T, int row0, const int *codes, int hq, int hkv, int hd, float scale, cudaStream_t stream, const int *drow)
bool has_recurrent_state(const std::string &ir_json)
void ir_binary_cuda(const float *a, const float *b, float *out, size_t n, int op, cudaStream_t stream)
void ir_bias_add_cuda(float *y, const float *b, int T, int O, cudaStream_t stream)
bool has_state_tokens(const std::string &ir_json)
void ir_rope_rows_at_cuda(float *x, int T, int heads, int hd, int n_rot, bool neox, const int *pos, const float *freqs, cudaStream_t stream)
void ir_rows_gather_cuda(const float *src, const int *idx, float *dst, int n, int w, cudaStream_t stream)
void ir_rows_scatter_cuda(const float *src, const int *idx, float *dst, int n, int w, cudaStream_t stream)
void ir_kv_write_cuda(float *K, float *V, const float *k, const float *v, int T, int row, int start_pos, cudaStream_t stream, const int *dpos)
size_t ir_expr_scratch_floats(const std::vector< expr::Stage > &stages, int T, int w)
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)
bool has_windowed_attention(const std::string &ir_json)
void ir_embedding_cuda(const float *table, const int *tokens, float *out, int T, int d, cudaStream_t stream)
static void cpu_only(bool gpu, const char *what)
bool tuned_llama_match(const std::string &ir_json, const GGUFData &gguf, std::string *why)
void ir_gelu_cuda(const float *x, float *out, size_t n, bool tanh_form, cudaStream_t stream)
void ir_silu_cuda(const float *x, float *out, size_t n, cudaStream_t stream)
json parse_ir_document(const std::string &text)
void ir_depth_attend_cuda(const float *const *sources, int S, const float *w, float *out, int T, int d, float eps, cudaStream_t stream)
DigestResult ir_digest(const std::string &text, bool prose)
void ir_rope_rows_cuda(float *x, int T, int heads, int hd, int n_rot, bool neox, int start_pos, const float *freqs, cudaStream_t stream, const int *dpos)
bool device_supported(const std::string &ir_json)
void validate_ir(const json &doc, const GGUFData &gguf)
void ir_kv_compact_cuda(float *K, float *V, const int *idx, int w0, int n, int row, cudaStream_t stream)
void ir_pos_embedding_cuda(const float *x, const float *P, float *out, int T, int d, int start_pos, cudaStream_t stream, const int *dpos)
void ir_window_attention_cuda(const float *q, const float *K, const float *V, float *out, int T, int start_pos, int hq, int hkv, int hd, float scale, int window, int sinks, cudaStream_t stream, const int *dpos, float softcap)
std::string find_ir(const GGUFData &gguf)
void ir_scale_cuda(const float *x, const float *g, const float *b, float *out, int T, int w, cudaStream_t stream)
void ir_expr_cuda(const std::vector< expr::Stage > &stages, const float *const *ins, int n_ins, float *scratch, float *out, int T, int w, cudaStream_t stream)
void ir_rope_at_cuda(float *q, float *k, int T, int hq, int hk, int hd, const int *pos, const float *freqs, cudaStream_t stream)
void ir_rms_row_cuda(const float *x, float *out, int T, int w, float eps, cudaStream_t stream)
void ir_layernorm_cuda(const float *x, const float *w, const float *b, float *out, int T, int d, float eps, cudaStream_t stream)
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)
void ir_relu_cuda(const float *x, float *out, size_t n, cudaStream_t stream)
void ir_affine_cuda(const float *x, float *out, size_t n, float scale, float shift, cudaStream_t stream)
void ir_selective_scan_cuda(const float *x, const float *dt, const float *B, const float *C, const float *A, const float *d, float *s, float *y, int T, int D, int N, bool softplus, cudaStream_t stream)
void ir_mla_attention_cuda(const float *qn, const float *qr, const float *KV, const float *Kb, const float *Vb, float *out, int T, int start_pos, int H, int r, int dn, int dr, int dv, float scale, int window, int sinks, cudaStream_t stream, const int *dpos)
void ir_mul_col_cuda(const float *x, const float *c, float *out, int T, int w, cudaStream_t stream)
void ir_embedding_state_cuda(const float *table, const float *state, int vocab, const int *tokens, float *out, int T, int d, cudaStream_t stream)
void ir_mor_gate_cuda(const float *x_in, float *x, const float *g, int n, int w, cudaStream_t stream)
void ir_mla_kv_write_cuda(float *KV, const float *c, const float *kr, int T, int r, int dr, int start_pos, cudaStream_t stream, const int *dpos)
void ir_rope_cuda(float *q, float *k, int T, int hq, int hk, int hd, int start_pos, const float *freqs, cudaStream_t stream, const int *dpos)
void ir_ssm_scan_cuda(const float *u, const float *A, const float *B, const float *C, const float *D, float *s, float *y, int T, int n, cudaStream_t stream)
bool has_ir_builder(const std::string &arch)
void ir_sigmoid_cuda(const float *x, float *out, size_t n, cudaStream_t stream)
const char * format_name(int f)
void write_cuda(int f, void *Kd, void *Ks, void *Vd, void *Vs, const float *k, const float *v, int T, int row, int start_pos, cudaStream_t st, const int *dpos)
size_t partial_floats(int hq, int hd, int max_len)
void decode_cuda(int f, const float *q, const void *Kd, const void *Ks, const void *Vd, const void *Vs, float *out, int hq, int hkv, int hd, float scale, int max_len, int seq_len, float *part, cudaStream_t st, const int *dpos)
size_t data_bytes(int f, size_t row)
void dequant_rows_cuda(int f, const void *Kd, const void *Ks, const void *Vd, const void *Vs, float *K, float *V, int n, int row, cudaStream_t st)
std::string shape_error(int f, int head_dim)
size_t scale_bytes(int f, size_t row)
Format parse_format(const char *s)
void roundtrip_rows(int f, float *x, size_t rows, size_t row)
void deterministic_cublas_env()
Template get(const std::string &id)
Constants for Rotary Position Embedding (RoPE)
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.
Weight quantization structures and functions for model compression.
Complete representation of a GGUF file's contents.
std::vector< std::string > tokenizer_tokens
std::map< std::string, GGUFMetadataValue > metadata
std::map< std::string, GGUFTensorInfo > tensor_infos_map
Information about a tensor stored in a GGUF file.
std::vector< uint64_t > shape
Key-Value cache for a single transformer layer.
Model configuration structure holding architecture and hyperparameters.
Simple 8-bit quantized block structure.
std::vector< RecTensor > tensors
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)
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)
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)
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)
void silu_cpu(const std::vector< float > &x, std::vector< float > &out)
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)
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)