27std::string E(
const std::string&
section,
const std::string&
msg) {
return "IR_SPEC " +
section +
": " +
msg; }
30 std::string
s =
v.dump(-1,
' ',
false, json::error_handler_t::replace);
31 return s.size() > 60 ?
s.substr(0, 60) :
s;
37 static const json s = [] {
40 return json::parse(
text);
47 if (
a.is_boolean() ||
b.is_boolean())
return a.is_boolean() &&
b.is_boolean() &&
a ==
b;
53 for (
const auto&
x :
t)
57 const std::string
n =
t.get<std::string>();
58 if (
n ==
"object")
return v.is_object();
59 if (
n ==
"array")
return v.is_array();
60 if (
n ==
"string")
return v.is_string();
61 if (
n ==
"boolean")
return v.is_boolean();
62 if (
n ==
"null")
return v.is_null();
63 if (
n ==
"integer")
return v.is_number_integer();
64 if (
n ==
"number")
return v.is_number();
65 throw std::logic_error(
"IR schema: unknown type " +
n);
69 static std::map<std::string, std::regex>
cache;
72 return std::regex_search(key,
it->second);
75using Failures = std::vector<std::pair<std::string, std::string>>;
88 if (!
s.get<
bool>())
out.emplace_back(
path,
"is not allowed");
91 if (
s.empty())
return;
92 if (
s.contains(
"$ref")) {
93 const std::string
ref =
s.at(
"$ref").get<std::string>();
94 const std::string pre =
"#/$defs/";
95 if (
ref.compare(0, pre.size(), pre) != 0)
throw std::logic_error(
"IR schema: $ref " +
ref);
98 if (
s.contains(
"type") && !
type_ok(
v,
s.at(
"type"))) {
99 out.emplace_back(
path,
"must be of type " +
s.at(
"type").dump() +
", not " +
short_dump(
v));
102 if (
s.contains(
"enum")) {
104 for (
const auto&
e :
s.
at(
"enum")) any = any ||
json_eq(
v,
e);
107 if (
s.contains(
"const") && !
json_eq(
v,
s.at(
"const")))
out.emplace_back(
path,
"must be " +
s.at(
"const").dump());
108 if (
v.is_number() &&
s.contains(
"minimum") &&
v.get<
double>() <
s.at(
"minimum").get<
double>())
109 out.emplace_back(
path,
v.dump() +
" is below the minimum " +
s.at(
"minimum").dump());
110 if (
v.is_string() &&
s.contains(
"minLength") &&
v.get<std::string>().size() <
s.at(
"minLength").get<
size_t>())
111 out.emplace_back(
path,
"must not be empty");
113 if (
s.contains(
"minItems") &&
v.size() <
s.at(
"minItems").get<
size_t>())
114 out.emplace_back(
path,
"needs at least " +
s.at(
"minItems").dump() +
" item(s)");
115 if (
s.contains(
"items"))
116 for (
size_t i = 0;
i <
v.size(); ++
i)
eval(
s.at(
"items"),
v[
i],
path +
"[" + std::to_string(
i) +
"]",
out);
119 if (
s.contains(
"required"))
120 for (
const auto&
k :
s.
at(
"required"))
124 const json&
pats =
s.contains(
"patternProperties") ?
s.at(
"patternProperties") :
kEmpty;
127 if (
props.contains(
kv.key())) {
136 if (!
matched &&
s.contains(
"additionalProperties")) {
137 const json&
ap =
s.at(
"additionalProperties");
138 if (
ap.is_boolean() && !
ap.get<
bool>()) {
140 const std::string op =
v.contains(
"op") &&
v.at(
"op").is_string() ?
" (" +
v.at(
"op").get<std::string>() +
")" :
"";
141 out.emplace_back(
path + op,
"has the key '" +
kv.key() +
"', which its specification does not define");
148 if (
s.contains(
"allOf"))
150 if (
s.contains(
"anyOf")) {
157 for (
const auto&
sub :
s.
at(
"anyOf")) {
162 if (
best.empty())
best.emplace_back(
path,
"matches no alternative");
166 if (
s.contains(
"if")) {
169 }
else if (
s.contains(
"else")) {
175void add_unique(std::vector<std::string>&
out, std::set<std::string>&
seen,
const std::string&
m) {
176 if (
seen.insert(
m).second)
out.push_back(
m);
181using Errs = std::vector<std::string>;
188 static const json o = json::object();
191const json&
get(
const json&
o,
const std::string&
k) {
return o.is_object() &&
o.contains(
k) ?
o.at(
k) :
kNull(); }
192bool is_int(
const json&
v) {
return v.is_number_integer(); }
193bool is_pl(
const json&
v) {
return v.is_object() &&
v.contains(
"per_layer"); }
196 if (
v.is_null())
return false;
197 if (
v.is_boolean())
return v.get<
bool>();
198 if (
v.is_number())
return v.get<
double>() != 0.0;
199 if (
v.is_string())
return !
v.get<std::string>().empty();
204 if (
v.is_string())
return v.get<std::string>();
205 if (
v.is_null())
return "None";
208std::string q(
const std::string&
s) {
return "'" +
s +
"'"; }
211std::optional<std::vector<json>> layers(
const json&
v,
int L) {
213 const json&
pl =
v.at(
"per_layer");
214 if (!
pl.is_array() ||
static_cast<int>(
pl.size()) !=
L)
return std::nullopt;
215 return std::vector<json>(
pl.begin(),
pl.end());
217 return std::vector<json>(
static_cast<size_t>(std::max(
L, 0)),
v);
227 std::vector<Sect>
out;
228 for (
const char*
sect : {
"embed",
"block",
"head"}) {
230 if (!
nodes.is_array())
continue;
231 for (
size_t j = 0;
j <
nodes.size(); ++
j)
237const std::set<std::string>
kNotPerLayer = {
"op",
"in",
"out",
"state",
"tensor_index",
"sizes",
"expr",
"weight",
"bias",
241 "selection_bias",
"groups",
242 "router",
"experts",
"state_weight",
"tied_to",
"a",
"b",
"c",
"d",
244 "k_b",
"v_b",
"groups",
246 "index",
"inputs",
"weights",
"init",
"let",
"update",
"readout"};
247const std::set<std::string>
kSlotNotPerLayer = {
"name",
"kind",
"op",
"dtype",
"tensors",
"evict",
"kv_rows"};
248const std::map<std::string, size_t>
kArity = {
249 {
"embedding", 1}, {
"pos_embedding", 2}, {
"linear", 1}, {
"scale", 1}, {
"split", 1}, {
"rmsnorm", 1},
250 {
"layernorm", 1}, {
"gelu", 1}, {
"silu", 1}, {
"sigmoid", 1}, {
"relu", 1}, {
"add", 2}, {
"mul", 2}, {
"sub", 2},
251 {
"affine", 1}, {
"mul_col", 2}, {
"rms_row", 1}, {
"rope", 3}, {
"attention", 3}, {
"moe_ffn", 1}, {
"append", 2},
252 {
"linear_attention", 3}, {
"ssm_scan", 1}, {
"conv1d_causal", 1}, {
"selective_scan", 4}, {
"depth_route", 1},
253 {
"mla_attention", 4}};
254const std::set<std::string>
kAtLeastOne = {
"list",
"depth_attend",
"expr",
"recurrence"};
255const std::map<std::string, std::string>
kStateful = {{
"attention",
"kv"},
256 {
"mla_attention",
"kv"},
257 {
"linear_attention",
"recurrent"},
258 {
"ssm_scan",
"recurrent"},
259 {
"conv1d_causal",
"recurrent"},
260 {
"selective_scan",
"recurrent"},
261 {
"recurrence",
"recurrent"}};
262const std::set<std::string>
kRecurrent = {
"linear_attention",
"ssm_scan",
"conv1d_causal",
"selective_scan",
265std::string
op_of(
const json&
n) {
return get(
n,
"op").is_string() ?
get(
n,
"op").get<std::string>() :
""; }
268std::vector<std::pair<std::string, std::string>> tensor_names(
const json&
n) {
269 std::vector<std::pair<std::string, std::string>>
out;
270 for (
const char*
k : {
"weight",
"bias",
"router",
"state_weight",
"tied_to"})
271 if (
get(
n,
k).is_string())
out.emplace_back(
k,
get(
n,
k).get<std::string>());
272 const std::string op =
op_of(
n);
273 if (op ==
"mla_attention")
274 for (
const char*
k : {
"k_b",
"v_b"})
275 if (
get(
n,
k).is_string())
out.emplace_back(
k,
get(
n,
k).get<std::string>());
276 if (op ==
"ssm_scan" || op ==
"selective_scan")
277 for (
const char*
k : {
"a",
"b",
"c",
"d"})
278 if (
get(
n,
k).is_string())
out.emplace_back(
k,
get(
n,
k).get<std::string>());
279 if (
get(
n,
"experts").is_object())
280 for (
const auto&
kv :
n.
at(
"experts").
items())
283 for (
const auto&
kv :
n.
at(
"weights").
items())
288size_t count_i(
const std::string&
s) {
290 for (
size_t p =
s.find(
"{i}");
p != std::string::npos;
p =
s.find(
"{i}",
p + 3)) ++
c;
300 const std::string op =
op_of(
n);
302 std::vector<json>
ins;
307 if (!
is_pl(
kv.value()))
continue;
308 const json&
pl =
kv.value().at(
"per_layer");
310 errs.push_back(E(
"3.8",
where +
": per-layer " + q(
kv.key()) +
" outside the block"));
312 errs.push_back(E(
"3.8",
where +
": " + q(
kv.key()) +
" is structural and cannot be per-layer"));
313 else if (!
pl.is_array() ||
static_cast<int>(
pl.size()) !=
L)
314 errs.push_back(E(
"3.8",
where +
": per-layer " + q(
kv.key()) +
" has " +
315 (
pl.is_array() ? std::to_string(
pl.size()) : std::
string(
"no list")) +
316 " entries for block.repeat " + std::
to_string(
L)));
320 errs.push_back(E(
"10.1 rule 7",
where +
" reads " + std::to_string(
ins.size()) +
" value(s), not " +
321 std::to_string(
it->second)));
323 if (op ==
"expr" &&
ins.size() > 23)
324 errs.push_back(E(
"5.19",
where +
" reads " + std::to_string(
ins.size()) +
" values (at most 23)"));
325 if (op ==
"split" || op ==
"rope") {
326 if (!
outs.is_array())
327 errs.push_back(E(
"10.1 rule 7",
where +
" writes a list of names"));
328 else if (op ==
"rope" &&
outs.size() != 2)
329 errs.push_back(E(
"5.14",
where +
" writes " + std::to_string(
outs.size()) +
" values, not [q, k]"));
330 }
else if (!
get(
n,
"op").is_null() && !
outs.is_string()) {
331 errs.push_back(E(
"10.1 rule 7",
where +
" writes one value"));
334 if (op ==
"expr" &&
get(
n,
"expr").is_string()) {
337 }
catch (
const std::exception&
e) {
338 errs.push_back(E(
"5.19",
where +
": " +
e.what()));
342 if (op ==
"split" &&
get(
n,
"sizes").
is_array() &&
outs.is_array() &&
outs.size() !=
n.at(
"sizes").size())
343 errs.push_back(E(
"5.5",
where +
": " + std::to_string(
n.at(
"sizes").size()) +
" sizes for " +
344 std::to_string(
outs.size()) +
" outputs"));
345 if (op ==
"rope" &&
L) {
346 const auto hd = layers(
get(
n,
"head_dim"),
L),
nr = layers(
get(
n,
"n_rot"),
L),
fs = layers(
get(
n,
"freq_scale"),
L);
347 for (
int i = 0;
i <
L; ++
i) {
350 if (!(0 <
a &&
a <=
h &&
a % 2 == 0)) {
351 errs.push_back(E(
"5.14",
where +
": n_rot " + std::to_string(
a) +
" is not even and in 1 .. head_dim " +
356 if (
fs && !(*
fs)[
i].is_null() && (*fs)[
i].is_number() && !((*fs)[
i].get<
double>() > 0)) {
357 errs.push_back(E(
"5.14",
where +
": freq_scale " +
show((*
fs)[
i]) +
" is not positive"));
362 if (
const auto af = layers(
get(
n,
"attn_factor"),
L))
363 for (
int i = 0;
i <
L; ++
i)
364 if ((*
af)[
i].is_number() && !((*af)[
i].get<
double>() > 0)) {
365 errs.push_back(E(
"5.14 (rule 10)",
where +
": attn_factor " +
show((*
af)[
i]) +
" is not positive"));
372 if (op ==
"rope" &&
n.contains(
"freq_factors") && !
is_pl(
n.at(
"freq_factors"))) {
373 const json&
ff =
n.at(
"freq_factors");
374 bool ok =
ff.is_string() && !
ff.get<std::string>().
empty();
378 const auto hd = layers(
get(
n,
"head_dim"), std::max(
L, 1)),
nr = layers(
get(
n,
"n_rot"), std::max(
L, 1));
379 for (
int i = 0; ok &&
hd &&
nr &&
i < std::max(
L, 1); ++
i) {
380 const json&
r = (*nr)[
i].is_null() ? (*hd)[
i] : (*nr)[
i];
384 if (
ff.is_object()) {
385 ok =
get(
ff,
"short").is_string() &&
get(
ff,
"long").is_string() &&
is_int(
get(
ff,
"original_ctx")) &&
386 ff.at(
"original_ctx").get<
int64_t>() >= 1;
388 if (
kv.key() !=
"short" &&
kv.key() !=
"long" &&
kv.key() !=
"original_ctx" &&
kv.key().
rfind(
'_', 0) != 0)
393 " is neither a tensor name, {short, long, original_ctx} with two "
394 "names and an integer original_ctx >= 1, nor a list of n_rot / 2 "
395 "positive numbers"));
403 errs.push_back(E(
"5.16 (rule 10)",
where +
": groups {n " + std::to_string(
G) +
", top " + std::to_string(g) +
404 "} do not divide n_experts " + std::to_string(
nE) +
405 " into groups keeping at least top_k " + std::to_string(
k) +
" experts"));
408 if (
n.contains(
"apply")) {
409 const json&
a =
n.at(
"apply");
411 if (
get(
blk,
"nodes").is_array())
414 errs.push_back(E(
"3.8 (rule 43)",
where +
": apply outside the block"));
416 errs.push_back(E(
"3.8 (rule 43)",
where +
": apply in a block with depth_route"));
417 if (
is_pl(
a) &&
a.at(
"per_layer").is_array()) {
418 for (
const auto&
v :
a.
at(
"per_layer"))
420 errs.push_back(E(
"3.8 (rule 43)",
where +
": apply entry " +
show(
v) +
" is not true or false"));
426 if (op ==
"attention" &&
L)
427 if (
const auto sc = layers(
get(
n,
"softcap"),
L))
428 for (
int i = 0;
i <
L; ++
i)
430 errs.push_back(E(
"5.15 (rule 10)",
where +
": softcap " +
show((*
sc)[
i]) +
" is not positive"));
433 if (op ==
"attention" &&
L) {
434 const auto h = layers(
get(
n,
"heads"),
L),
kvh = layers(
get(
n,
"kv_heads"),
L),
w = layers(
get(
n,
"window"),
L),
435 s = layers(
get(
n,
"sinks"),
L),
c = layers(
get(
n,
"causal"),
L);
436 for (
int i = 0;
h &&
kvh &&
i <
L; ++
i)
439 errs.push_back(E(
"5.15",
where +
": heads " +
show((*
h)[
i]) +
" is not a multiple of kv_heads " +
show((*
kvh)[
i])));
442 for (
int i = 0;
i <
L; ++
i)
444 errs.push_back(E(
"5.15",
where +
": window and sinks must be >= 0"));
453 if (op ==
"attention" &&
n.contains(
"relative_kv") &&
n.contains(
"position_bias"))
454 errs.push_back(E(
"5.15 (rule 40)",
where +
": relative_kv and position_bias on one node (revision 1.4 defines "
456 if (op ==
"linear_attention" && !(
get(
n,
"causal").
is_boolean() &&
n.at(
"causal").get<
bool>()))
457 errs.push_back(E(
"6.4",
where +
": causal must be true"));
458 if (op ==
"mla_attention" &&
L) {
459 const auto w = layers(
get(
n,
"window"),
L),
s = layers(
get(
n,
"sinks"),
L),
c = layers(
get(
n,
"causal"),
L);
460 for (
int i = 0;
i <
L; ++
i)
462 errs.push_back(E(
"5.20 (rule 10)",
where +
": window and sinks must be >= 0"));
467 if (
not_true)
errs.push_back(E(
"5.20 (rule 14)",
where +
": causal must be true"));
471 if (!(1 <=
k &&
k <=
e))
472 errs.push_back(E(
"5.16",
where +
": top_k " + std::to_string(
k) +
" is not in 1 .. n_experts " + std::to_string(
e)));
474 if (op ==
"conv1d_causal" && ((
is_int(
get(
n,
"channels")) &&
n.at(
"channels").get<
int64_t>() < 1) ||
476 errs.push_back(E(
"6.6",
where +
": channels must be >= 1 and kernel >= 2"));
478 for (
const auto& [key,
name] : tensor_names(
n)) {
479 if (
name.find(
"{i}") != std::string::npos &&
sect !=
"block")
480 errs.push_back(E(
"3.7",
where +
": " + key +
" " + q(
name) +
" uses {i} outside the block"));
484 for (
size_t k = 0;
k <
ins.size(); ++
k) {
486 const std::string
nm =
ins[
k].get<std::string>();
487 const bool ok =
nm ==
"tokens" ? (op ==
"embedding" &&
k == 0)
488 :
nm ==
"pos" ? ((op ==
"rope" &&
k == 2) || (op ==
"pos_embedding" &&
k == 1))
490 if (!ok)
errs.push_back(E(
"3.5",
where +
" reads the graph input " + q(
nm) +
" as its input " + std::to_string(
k)));
492 if ((op ==
"rope" &&
ins.size() == 3 &&
ins[2] !=
"pos") || (op ==
"pos_embedding" &&
ins.size() == 2 &&
ins[1] !=
"pos"))
493 errs.push_back(E(
"3.5",
where +
": the position input must be the graph input 'pos'"));
494 if (op ==
"embedding" && !
ins.empty() &&
ins[0] !=
"tokens")
495 errs.push_back(E(
"3.5",
where +
": an embedding reads the graph input 'tokens'"));
499 if (
n.contains(
"tensor_index") && (
sect !=
"block" || !
blk.contains(
"weight_index")))
500 errs.push_back(E(
"7.2",
where +
": tensor_index needs a block with weight_index"));
507 if (op ==
"attention") {
508 for (
const char* key : {
"window",
"sinks",
"kv_heads",
"head_dim"}) {
509 const auto a = layers(
get(
node, key),
L),
b = layers(
get(slot, key),
L);
510 if (!
a || !
b)
continue;
511 for (
int i = 0;
i <
L; ++
i)
512 if ((*
a)[
i] != (*
b)[
i]) {
513 const bool q7 = std::string(key) ==
"kv_heads" || std::string(key) ==
"head_dim";
514 errs.push_back(E(
"5.15",
"attention " + std::string(key) +
" " +
show((*
a)[
i]) +
" (application " +
515 std::to_string(
i) +
") differs from its kv state " +
show(
get(slot,
"name")) +
516 "'s " +
show((*
b)[
i]) + (
q7 ?
" (Q7)" :
"")));
522 if (op ==
"mla_attention") {
524 const auto kvh = layers(
get(slot,
"kv_heads"),
L),
hd = layers(
get(slot,
"head_dim"),
L);
525 for (
int i = 0;
kvh &&
i <
L; ++
i)
527 errs.push_back(E(
"5.20 (rule 12)",
"mla_attention reads kv state " +
show(
get(slot,
"name")) +
" of kv_heads " +
528 show((*
kvh)[
i]) +
" (application " + std::to_string(
i) +
"), not 1"));
533 errs.push_back(E(
"5.20 (rule 12)",
"mla_attention reads kv state " +
show(
get(slot,
"name")) +
" of head_dim " +
534 show((*
hd)[
i]) +
" (application " + std::to_string(
i) +
535 "), not kv_rank + qk_rope = " +
539 for (
const char* key : {
"window",
"sinks"}) {
540 const auto a = layers(
get(
node, key),
L),
b = layers(
get(slot, key),
L);
541 if (!
a || !
b)
continue;
542 for (
int i = 0;
i <
L; ++
i)
543 if ((*
a)[
i] != (*
b)[
i]) {
544 errs.push_back(E(
"5.20 (rule 12)",
"mla_attention " + std::string(key) +
" " +
show((*
a)[
i]) +
" (application " +
545 std::to_string(
i) +
") differs from its kv state " +
552 if (op ==
"recurrence") {
556 for (
const auto&
t : slot.
at(
"tensors"))
560 const std::string
why = rec::Recurrence::compile(
node).slot_errors(slot);
561 if (!
why.empty())
errs.push_back(E(
"6.10",
"(rule 33) " +
why));
562 }
catch (
const std::exception&
e) {
563 std::string
m =
e.what();
564 const std::string
tag =
"IR_SPEC 6.10 ";
565 errs.push_back(E(
"6.10",
m.rfind(
tag, 0) == 0 ?
m.substr(
tag.size()) :
m));
571 for (
const auto&
t : slot.
at(
"tensors"))
574 if (op ==
"linear_attention") {
575 want = json::object();
578 }
else if (op ==
"ssm_scan") {
579 want = json::object();
581 }
else if (op ==
"conv1d_causal") {
583 want = json::object();
586 want = json::object();
590 errs.push_back(E(
"6.3",
"recurrent state " +
show(
get(slot,
"name")) +
": tensors " +
shapes.dump() +
" do not fit " +
591 op +
" (want " +
want.dump() +
")"));
598 for (
const char*
k : {
"d",
"vocab",
"ctx",
"n_layers"})
600 errs.push_back(E(
"3.3", std::string(
"model.") +
k +
" missing or not a positive integer"));
601 if (
get(
blk,
"repeat") !=
get(model,
"n_layers"))
602 errs.push_back(E(
"3.3",
"block.repeat " +
show(
get(
blk,
"repeat")) +
" != model.n_layers " +
show(
get(model,
"n_layers"))));
604 if (
blk.contains(
"weight_index")) {
605 const json&
m =
get(
blk.at(
"weight_index"),
"per_layer");
606 bool ok =
m.is_array() &&
static_cast<int>(
m.size()) ==
L;
609 errs.push_back(E(
"7.1",
"block.weight_index must be {per_layer: [block.repeat integers >= 0]}"));
610 }
else if (!
m.empty()) {
611 std::set<int64_t>
used;
617 errs.push_back(E(
"7.1",
"block.weight_index uses stored layers [" +
s +
"], not every one of 0.." + std::to_string(
mx)));
621 std::vector<std::string>
order;
622 std::map<std::string, const json*>
slots;
624 for (
const auto&
s :
blk.
at(
"state")) {
625 if (!
s.is_object())
continue;
628 errs.push_back(E(
"6.1",
"state slot " +
name +
" declared twice"));
633 if (!
is_pl(
kv.value()))
continue;
634 const json&
pl =
kv.value().at(
"per_layer");
636 errs.push_back(E(
"3.8",
"state slot " +
name +
": " + q(
kv.key()) +
" is structural and cannot be per-layer"));
637 else if (!
pl.is_array() ||
static_cast<int>(
pl.size()) !=
L)
638 errs.push_back(E(
"3.8",
"state slot " +
name +
": per-layer " + q(
kv.key()) +
" needs block.repeat " +
639 std::to_string(
L) +
" entries"));
643 for (
const char*
k : {
"heads",
"kv_heads",
"head_dim",
"max_len",
"window",
"sinks"})
645 if (
s.contains(
"kv_from"))
errs.push_back(E(
"7.6",
"kv state " +
name +
": kv_from is reserved and not implemented"));
646 }
else if (
kind ==
"recurrent") {
650 errs.push_back(E(
"6.3",
"recurrent state " +
name +
": op (a recurrent op), dtype f32 and tensors required"));
652 for (
const auto&
t :
ts) {
654 (
get(
t,
"init") ==
json(0) || (
get(
t,
"init") ==
"body" &&
get(
s,
"op") ==
"recurrence"));
658 errs.push_back(E(
"6.3",
"recurrent state " +
name +
": tensor " +
t.dump() +
" needs name, shape and init 0"));
661 errs.push_back(E(
"6.1",
"state slot " +
name +
": unknown kind " +
show(
kind)));
664 std::set<std::string>
used;
666 std::map<std::string, int>
readers;
672 if (
node.contains(
"state"))
673 errs.push_back(E(
"6.1",
sect +
"[" + std::to_string(
j) +
"] " +
show(
get(
node,
"op")) +
" names state " +
674 q(
show(
node.at(
"state"))) +
" but has no state"));
677 if (
sect !=
"block") {
678 errs.push_back(E(
"6.1",
sect +
": " + op +
" outside the block has no state"));
684 (
want->second ==
"recurrent" &&
get(*
it->second,
"op") != op)) {
685 errs.push_back(E(
"6.1",
sect +
": " + op +
" reads state " + q(
st) +
", not a declared " +
want->second +
" slot" +
686 (
want->second ==
"recurrent" ?
" of op " + op : std::
string())));
690 if (op ==
"attention" || op ==
"mla_attention" || op ==
"recurrence") {
699 errs.push_back(E(
"6.1",
"kv state " +
name +
" is read by " + std::to_string(
readers[
name]) +
700 " attention nodes (exactly one is defined)"));
702 errs.push_back(E(
"6.10",
"(rule 33) the slot " +
name +
" of a recurrence is read by " +
703 std::to_string(
readers[
name]) +
" nodes (the node alone reads it)"));
715 std::vector<size_t>
at;
716 for (
size_t j = 0;
j <
nodes.size(); ++
j)
718 std::vector<const json*>
kvs;
720 for (
const auto&
s :
blk.
at(
"state"))
725 " (only \"active\" is defined)"));
726 if (
at.empty())
return;
727 if (
at != std::vector<size_t>{0}) {
728 errs.push_back(E(
"7.4",
"depth_route must be the block's first node, once"));
734 errs.push_back(E(
"7.4",
"depth_route: n_rec must be an integer >= 1"));
738 if (
get(
r,
"route") !=
"token_choice")
739 errs.push_back(E(
"7.7",
"depth_route: route " + q(
show(
get(
r,
"route"))) +
" (only token_choice, causal, is defined)"));
740 if (
get(
r,
"gate") !=
"score" &&
get(
r,
"gate") !=
"none")
741 errs.push_back(E(
"7.4",
"depth_route: gate " + q(
show(
get(
r,
"gate"))) +
" is not score or none"));
743 if (!
w.is_string() ||
w.get<std::string>().find(
'{') != std::string::npos)
744 errs.push_back(E(
"7.7",
"depth_route: weight must be the router's full tensor name (constraint 4: no substitution)"));
745 if (!
get(
r,
"out").
is_string())
errs.push_back(E(
"7.4",
"depth_route: out must name the depth trace"));
751 errs.push_back(E(
"7.4",
"depth_route: step must be {per_layer: [block.repeat integers]}"));
754 std::vector<int64_t>
s;
756 std::vector<std::pair<int64_t, int64_t>>
runs;
757 for (
size_t i = 0;
i <
s.size(); ++
i) {
758 if (
s[
i] == -1)
continue;
759 if (
i == 0 ||
s[
i - 1] !=
s[
i])
runs.emplace_back(
s[
i], 0);
760 ++
runs.back().second;
762 std::vector<size_t>
inside;
763 for (
size_t i = 0;
i <
s.size(); ++
i)
770 errs.push_back(E(
"7.4",
"depth_route: steps " +
st.dump() +
" are not -1 or the consecutive steps 0 .. " +
771 std::to_string(
R - 1) +
" in order"));
773 std::set<int64_t>
lens;
775 if (
lens.size() != 1)
errs.push_back(E(
"7.4",
"depth_route: steps " +
st.dump() +
" differ in length"));
779 if (!
all_active)
errs.push_back(E(
"7.5",
"depth_route needs kv_rows \"active\" on every kv slot"));
780 for (
const auto&
n :
nodes)
782 errs.push_back(E(
"9.2",
"depth_route with mask turn_state is not defined (llama_state)"));
786 const std::string op =
op_of(*
np);
787 if (op ==
"list" || op ==
"append" || op ==
"depth_attend") {
788 errs.push_back(E(
"7.4",
"depth_route with list values (list, append, depth_attend) is not defined"));
796 if (
ins.is_array() && std::find(
ins.begin(),
ins.end(),
out) !=
ins.end())
797 errs.push_back(E(
"7.4",
sect +
"[" + std::to_string(
j) +
"] " +
show(
get(*
np,
"op")) +
" reads depth_route's out " +
798 q(
out.get<std::string>()) +
" (the depth report)"));
809 if (
op_of(
n) !=
"embedding" || !
n.contains(
"state_weight"))
continue;
812 errs.push_back(E(
"6.8.1",
sect +
"[" + std::to_string(
j) +
"]: an embedding state_weight outside the embed section (Q8)"));
813 if (
n.at(
"state_weight").is_string() &&
n.at(
"state_weight").get<std::string>().find(
'{') != std::string::npos)
814 errs.push_back(E(
"6.8.1",
"state_weight names its tensor in full (Q8)"));
816 if (
sws > 1)
errs.push_back(E(
"6.8.1",
"more than one embedding with a state_weight (Q8)"));
817 std::map<std::string, const json*>
slots;
819 for (
const auto&
s :
blk.
at(
"state"))
821 std::map<std::string, std::set<std::string>>
masks;
822 auto any_of = [](
const std::optional<std::vector<json>>&
v) {
823 if (!
v)
return false;
824 for (
const auto&
x : *
v)
829 for (
const auto&
n :
blk.
at(
"nodes")) {
830 if (!
n.is_object() ||
op_of(
n) !=
"attention")
continue;
831 const json m =
n.contains(
"mask") ?
n.at(
"mask") :
json(
"causal");
832 if (
m !=
"causal" &&
m !=
"turn_state")
833 errs.push_back(E(
"6.8.2",
"attention mask " + q(
show(
m)) +
" (causal or turn_state)"));
834 if (
m ==
"turn_state") {
836 auto field = [&](
const json&
o,
const char*
k) {
return layers(
o.contains(
k) ?
o.at(
k) :
json(0),
L); };
839 errs.push_back(E(
"6.8.2",
"attention mask turn_state needs window 0, sinks 0 (every application, and its kv slot) "
840 "and an embedding state_weight"));
845 for (
const auto&
st :
blk.
at(
"state")) {
846 if (!
st.is_object() || !
st.contains(
"evict"))
continue;
848 if (
st.at(
"evict") !=
"turn_state")
849 errs.push_back(E(
"6.8.3",
"kv state " +
name +
": evict " + q(
show(
st.at(
"evict"))) +
" (only turn_state is defined)"));
850 else if (
masks[
name] != std::set<std::string>{
"turn_state"})
851 errs.push_back(E(
"6.8.3",
"kv state " +
name +
": evict turn_state needs every attention node reading it to mask "
862 std::vector<std::string>
out;
863 if (
v.is_string())
out.push_back(
v.get<std::string>());
865 for (
const auto&
x :
v)
871 return n.is_array() ?
n :
kNull();
874 auto check = [&](
const std::string&
sect,
size_t j,
const json&
n,
const std::set<std::string>&
scope,
881 for (
const char*
sect : {
"embed",
"block",
"head"})
884 for (
size_t j = 0;
embed.is_array() &&
j <
embed.size(); ++
j) {
888 if (!
written.count(
"x"))
errs.push_back(E(
"3.5 (rule 36)",
"the embed section writes no 'x'"));
893 std::set<std::string> step = {
"x",
"pos"};
900 for (
int i = 0;
i <
L; ++
i)
901 for (
size_t j = 0;
j <
block.size(); ++
j) {
904 ?
a.at(
"per_layer")[
i]
906 if (
v.is_boolean() && !
v.get<
bool>())
continue;
908 "which no node before it writes (application " + std::to_string(
i) +
")");
914 check(
"block",
j,
block[
j],
written,
true,
"3.5",
"which no node before it writes (application 0)");
919 "outside what a depth_route step sees (x, pos and the values written before it in the application)");
929 for (
size_t j = 0;
head.is_array() &&
j <
head.size(); ++
j) {
931 mor ?
"a block value other than x (under depth_route the head reads x and the embed section's values)"
932 :
"which no node before it writes");
938 if (!
logits)
errs.push_back(E(
"3.5 (rule 36)",
"the head writes no 'logits'"));
945 if (
it == g.
metadata.end())
return std::nullopt;
947 [](
const auto&
v) -> std::optional<double> {
948 using T = std::decay_t<
decltype(
v)>;
949 if constexpr (std::is_arithmetic_v<T> && !std::is_same_v<T, bool>)
950 return static_cast<double>(
v);
957std::optional<std::string>
meta_str(
const GGUFData& g,
const std::string& key) {
959 if (
it == g.
metadata.end())
return std::nullopt;
960 if (
const auto*
s = std::get_if<std::string>(&
it->second))
return *
s;
964std::string
num(
double v) {
965 if (
v ==
static_cast<double>(
static_cast<int64_t>(
v)))
return std::to_string(
static_cast<int64_t>(
v));
966 return json(
v).dump();
969std::string
ne_str(
const std::vector<uint64_t>&
s) {
972 return "[" +
out +
"]";
977 if (
const auto emb =
meta_str(g,
"paperkiln.ir")) {
982 e = json::parse(*
emb);
983 }
catch (
const std::exception&) {
984 errs.push_back(E(
"3.1",
"paperkiln.ir is not a JSON document"));
986 const auto it = g.
metadata.find(
"paperkiln.ir_version");
988 if (
it == g.
metadata.end() || !std::holds_alternative<uint32_t>(
it->second) || !
is_int(
v) ||
990 errs.push_back(E(
"3.1",
"paperkiln.ir_version " +
994 : std::
string(
"(not a uint32)")) +
995 " does not match the document's ir_version " +
show(
v)));
999 if (
const auto key = g.
metadata.find(
"paperkiln.ir_sha256"); key != g.
metadata.end()) {
1000 const auto*
want = std::get_if<std::string>(&key->second);
1003 errs.push_back(E(
"3.10",
"paperkiln.ir_sha256 is not a string"));
1005 errs.push_back(E(
"3.10",
"paperkiln.ir_sha256 is present but the file embeds no paperkiln.ir"));
1009 errs.push_back(E(
"3.10",
"paperkiln.ir_sha256 is present but the embedded document has no digest (" + d.refused +
")"));
1010 else if (d.sha256 != *
want)
1011 errs.push_back(E(
"3.10",
"paperkiln.ir_sha256 " + *
want +
" is not the embedded document's digest " + d.sha256));
1014 auto ne =[&](
const std::string&
name) ->
const std::vector<uint64_t>* {
1018 const std::string arch =
meta_str(g,
"general.architecture").value_or(
"");
1025 if (
const auto*
got =
ne(
r.at(
"weight").get<std::string>())) {
1027 static_cast<int64_t>((*got)[0]) == model.at(
"d").get<
int64_t>() &&
1030 errs.push_back(E(
"7.4",
"depth_route: " +
r.at(
"weight").get<std::string>() +
" has ne " +
ne_str(*
got) +
1031 ", not [d, n_rec] = [" +
show(
get(model,
"d")) +
", " +
show(
get(
r,
"n_rec")) +
"]"));
1041 if (arch !=
"paperkiln")
errs.push_back(E(
"7.1",
"block.weight_index needs architecture paperkiln, not " + (arch.empty() ? std::string(
"None") : arch)));
1042 const auto bc =
meta_num(g, arch +
".block_count");
1043 if (!
bc || *
bc !=
static_cast<double>(
mx + 1))
1044 errs.push_back(E(
"7.1", arch +
".block_count " + (
bc ?
num(*
bc) : std::
string(
"None")) +
" != the " +
1045 std::
to_string(
mx + 1) +
" stored layers weight_index uses"));
1048 const auto k =
meta_num(g,
"paperkiln.state_tokens.k");
1049 std::vector<std::string>
sws;
1054 if ((
k && *
k > 0) != !
sws.empty())
1055 errs.push_back(E(
"6.8.1",
"paperkiln.state_tokens.k " + (
k ?
num(*
k) : std::
string(
"None")) +
1056 " without an embedding state_weight, or the reverse"));
1057 for (
const auto&
sw :
sws) {
1060 errs.push_back(E(
"6.8.1",
"embedding state_weight " +
sw +
" is not a tensor of the file"));
1061 }
else if (!(
got->size() == 2 &&
is_int(
get(model,
"d")) &&
k &&
1063 static_cast<double>((*
got)[1]) == *
k)) {
1064 errs.push_back(E(
"6.8.1",
"embedding state_weight " +
sw +
" has ne " +
ne_str(*
got) +
1065 ", not [d, paperkiln.state_tokens.k] = [" +
show(
get(model,
"d")) +
", " +
1070 if (arch !=
"paperkiln")
errs.push_back(E(
"8.2",
"state tokens need architecture paperkiln, not " + (arch.empty() ? std::string(
"None") : arch)));
1071 static const std::map<std::string, std::string>
kMarkers = {
1072 {
"eot",
"<|endoftext|>"}, {
"user",
"<|user|>"}, {
"assistant",
"<|assistant|>"}};
1074 for (
const char* role : {
"eot",
"user",
"assistant"}) {
1077 errs.push_back(E(
"8.2", std::string(
"state tokens: the tokenizer names no ") + role +
" id (the template needs it)"));
1083 errs.push_back(E(
"8.2", std::string(
"state tokens: specials.") + role +
" = " + std::to_string(
i) +
" is the token " +
1087 if (
const auto tmpl =
meta_str(g,
"paperkiln.chat_template")) {
1088 std::string
name =
"None";
1090 const json rec = json::parse(*tmpl);
1091 const std::string
nm =
show(
get(rec,
"name"));
1092 name =
nm.find(
'/') == std::string::npos ?
nm +
"/" +
show(
get(rec,
"version")) :
nm;
1093 }
catch (
const std::exception&) {
1095 if (
name !=
"paperkiln/1")
errs.push_back(E(
"8.2",
"state tokens are defined over paperkiln/1 only, not " +
name));
1099 const int L =
is_int(
get(
blk,
"repeat")) ?
blk.at(
"repeat").get<
int>() : 0;
1107 for (
const auto&
b :
pb.
at(
"buckets"))
1109 const auto heads = layers(
get(
n,
"heads"), std::max(
L, 1));
1110 for (
int i = 0;
i < (
sect ==
"block" ?
L : 1); ++
i) {
1111 std::string
w =
pb.at(
"weight").get<std::string>();
1112 const size_t at =
w.find(
"{i}");
1113 if (
at != std::string::npos) {
1121 errs.push_back(E(
"5.15 (rule 39)",
sect +
"[" + std::to_string(
j) +
"] attention position_bias " + q(
w) +
1122 (
sect ==
"block" ?
" (application " + std::to_string(
i) +
")" :
"") +
1124 ", B} with B greater than every bucket (" + std::
to_string(
top) +
")"));
1134 if (
op_of(
n) !=
"attention" || !
rk.is_object() || !
is_int(
get(
rk,
"max_distance")))
continue;
1136 if (K < 0)
continue;
1137 const auto heads = layers(
get(
n,
"heads"), std::max(
L, 1)),
hdim = layers(
get(
n,
"head_dim"), std::max(
L, 1));
1138 for (
const char*
member : {
"key",
"value"}) {
1140 for (
int i = 0;
i < (
sect ==
"block" ?
L : 1); ++
i) {
1141 std::string
w =
rk.at(
member).get<std::string>();
1142 const size_t at =
w.find(
"{i}");
1143 if (
at != std::string::npos) {
1150 const bool ok =
got->size() == 2 &&
static_cast<int64_t>((*got)[1]) == K + 1 &&
1153 errs.push_back(E(
"5.15 (rule 40)",
sect +
"[" + std::to_string(
j) +
"] attention relative_kv " +
member +
" " +
1154 q(
w) + (
sect ==
"block" ?
" (application " + std::to_string(
i) +
")" :
"") +
1155 " has ne " +
ne_str(*
got) +
", not {head_dim or heads * head_dim, " +
1156 "max_distance + 1} = {" + std::to_string(
hd) +
" or " +
1157 std::to_string(
H *
hd) +
", " + std::to_string(K + 1) +
"}"));
1168 std::vector<std::string>
names;
1169 if (
ff.is_string())
names.push_back(
ff.get<std::string>());
1170 for (
const char*
member : {
"short",
"long"})
1172 const auto hdim = layers(
get(
n,
"head_dim"), std::max(
L, 1)),
nrot = layers(
get(
n,
"n_rot"), std::max(
L, 1));
1177 const size_t at =
w.find(
"{i}");
1178 if (
at != std::string::npos) {
1184 const json&
r =
nrot && !(*nrot)[
i].is_null() ? (*nrot)[
i] : (*hdim)[
i];
1188 errs.push_back(E(
"5.14 (rule 41)",
sect +
"[" + std::to_string(
j) +
"] rope freq_factors " + q(
w) +
1189 (
sect ==
"block" ?
" (application " + std::to_string(
i) +
")" :
"") +
1190 " has ne " +
ne_str(*
got) +
", not {n_rot / 2} = {" +
1200 const auto nE = layers(
get(
n,
"n_experts"), std::max(
L, 1));
1201 const auto ap = layers(
get(
n,
"apply"), std::max(
L, 1));
1202 for (
int i = 0;
i < (
sect ==
"block" ?
L : 1); ++
i) {
1203 if (
ap && (*
ap)[
i].is_boolean() && !(*ap)[
i].get<
bool>())
continue;
1204 std::string
w =
n.at(
"selection_bias").get<std::string>();
1205 const size_t at =
w.find(
"{i}");
1206 if (
at != std::string::npos) {
1213 errs.push_back(E(
"5.16 (rule 44)",
sect +
"[" + std::to_string(
j) +
"] moe_ffn selection_bias " + q(
w) +
1214 (
sect ==
"block" ?
" (application " + std::to_string(
i) +
")" :
"") +
1215 " has ne " +
ne_str(*
got) +
", not {n_experts} = {" +
show((*
nE)[
i]) +
"}"));
1226 const int L =
is_int(
get(
blk,
"repeat")) ?
blk.at(
"repeat").get<
int>() : 0;
1230 if (
op_of(
n) !=
"mla_attention" ||
sect !=
"block")
continue;
1231 const auto H = layers(
get(
n,
"heads"),
L),
r = layers(
get(
n,
"kv_rank"),
L),
dn = layers(
get(
n,
"qk_nope"),
L),
1232 dv = layers(
get(
n,
"v_head_dim"),
L);
1233 if (!
H || !
r || !
dn || !
dv)
continue;
1234 for (
const char* key : {
"k_b",
"v_b"}) {
1235 if (!
get(
n, key).is_string())
continue;
1236 for (
int i = 0;
i <
L; ++
i) {
1238 std::string
w =
n.at(key).get<std::string>();
1239 const size_t at =
w.find(
"{i}");
1240 if (
at != std::string::npos) {
1246 std::vector<uint64_t>
got =
it->second.shape;
1247 while (
got.size() < 3)
got.push_back(1);
1250 const std::vector<uint64_t>
want = std::string(key) ==
"k_b" ? std::vector<uint64_t>{
c,
b,
a}
1251 : std::vector<uint64_t>{
b,
e,
a};
1253 errs.push_back(E(
"5.20 (rule 42)",
"block[" + std::to_string(
j) +
"] mla_attention " + key +
" " + q(
w) +
1254 " (application " + std::to_string(
i) +
") has ne " +
1256 (std::string(key) ==
"k_b" ?
" {qk_nope, kv_rank, heads}"
1257 :
" {kv_rank, v_head_dim, heads}")));
1276 if (
v.is_object()) {
1277 json o = json::object();
1283 json a = json::array();
1287 if (
v.is_number())
return json(
static_cast<double>(
static_cast<float>(
v.get<
double>())));
1291 std::map<std::string, std::string>
cur, slot;
1293 auto read = [&](
const std::string&
nm) {
1294 if (
nm ==
"tokens" ||
nm ==
"pos")
return nm;
1296 return it ==
cur.end() ?
"?" +
nm :
it->second;
1299 auto num_is = [](
const json&
v,
double x) {
return v.is_number() &&
v.get<
double>() ==
static_cast<double>(
static_cast<float>(
x)); };
1301 const std::string op =
n.value(
"op",
"");
1302 std::vector<std::string>
ins;
1303 if (
n.contains(
"in") &&
n.at(
"in").is_array())
1304 for (
const auto&
x :
n.
at(
"in"))
1306 const std::string
nm =
x.get<std::string>();
1312 if ((op ==
"add" || op ==
"mul") &&
ins.size() == 2 &&
ins[1] <
ins[0]) std::swap(
ins[0],
ins[1]);
1314 if (
n.contains(
"state") &&
n.at(
"state").is_string()) {
1315 const std::string
st =
n.at(
"state").get<std::string>();
1316 n[
"state"] = slot.count(
st) ? slot[
st] :
"?" +
st;
1319 if (op ==
"attention") {
1321 if (
n.value(
"mask",
json(
"causal")) ==
"causal")
n.erase(
"mask");
1323 if (op ==
"moe_ffn")
n.erase(
"routing");
1324 if ((op ==
"linear" || op ==
"scale") &&
n.contains(
"bias") &&
n.at(
"bias").is_null())
n.erase(
"bias");
1325 if (op ==
"linear") {
1326 if (
n.contains(
"tied_to") &&
n.at(
"tied_to").is_string() &&
1327 !(
n.contains(
"weight") &&
n.at(
"weight").is_string() &&
has_tensor(
n.at(
"weight").get<std::string>())))
1328 n[
"weight"] =
n.at(
"tied_to");
1331 if (op ==
"rmsnorm" &&
n.contains(
"weight_offset") &&
num_is(
n.at(
"weight_offset"), 0.0))
n.erase(
"weight_offset");
1333 if (
n.contains(
"kv_heads") &&
n.at(
"kv_heads") ==
get(
n,
"heads"))
n.erase(
"kv_heads");
1334 if (
n.contains(
"n_rot") &&
n.at(
"n_rot") ==
get(
n,
"head_dim"))
n.erase(
"n_rot");
1335 if (
n.contains(
"freq_scale") &&
num_is(
n.at(
"freq_scale"), 1.0))
n.erase(
"freq_scale");
1336 if (
n.contains(
"attn_factor") &&
num_is(
n.at(
"attn_factor"), 1.0))
n.erase(
"attn_factor");
1338 if (
n.contains(
"apply") &&
n.at(
"apply") ==
json(
true))
n.erase(
"apply");
1339 if (op ==
"moe_ffn" &&
n.contains(
"scoring") &&
n.at(
"scoring") ==
"softmax")
n.erase(
"scoring");
1340 if (op ==
"depth_attend" &&
n.contains(
"eps") &&
num_is(
n.at(
"eps"), 1
e-5))
n.erase(
"eps");
1341 if (op ==
"split" &&
n.contains(
"groups") &&
num_is(
n.at(
"groups"), 1.0))
n.erase(
"groups");
1342 if (op ==
"embedding" &&
n.contains(
"state_weight") &&
n.at(
"state_weight").is_null())
n.erase(
"state_weight");
1343 if (op ==
"conv1d_causal" &&
n.contains(
"bias") &&
n.at(
"bias").is_null())
n.erase(
"bias");
1344 if (op ==
"selective_scan") {
1345 if (
n.contains(
"d") &&
n.at(
"d").is_null())
n.erase(
"d");
1346 if (
n.value(
"dt_softplus",
json(
true)) ==
json(
true))
n.erase(
"dt_softplus");
1350 std::vector<std::string>
on;
1351 if (
o.is_string())
on.push_back(
o.get<std::string>());
1353 for (
const auto&
x :
o)
1355 for (
const auto&
nm :
on) {
1356 const std::string
v =
"v" + std::to_string(next++);
1367 for (
size_t k = 0;
k <
bl.at(
"state").size(); ++
k) {
1369 if (
nm.is_string() && !slot.count(
nm.get<std::string>())) slot[
nm.get<std::string>()] =
"slot" + std::to_string(
k);
1374 if (!
sec.is_object())
return sec;
1375 if (
sec.contains(
"nodes") &&
sec.at(
"nodes").is_array()) {
1383 if (d.is_object() && d.contains(
"model"))
out[
"model"] = d.at(
"model");
1384 if (d.is_object() && d.contains(
"embed"))
out[
"embed"] =
section(
"embed",
false);
1385 if (d.is_object() && d.contains(
"block")) {
1387 if (
block.is_object() &&
block.contains(
"state") &&
block.at(
"state").is_array()) {
1389 for (
size_t k = 0;
k <
block.at(
"state").size(); ++
k) {
1391 if (
s.is_object()) {
1392 s[
"name"] =
"slot" + std::to_string(
k);
1393 if (
get(
s,
"kind") ==
"kv" &&
s.value(
"dtype",
json(
"f32")) ==
"f32")
s.erase(
"dtype");
1401 if (d.is_object() && d.contains(
"head"))
out[
"head"] =
section(
"head",
false);
1407 if (
a.type() !=
b.type() && !(
a.is_number() &&
b.is_number()))
1409 if (
a.is_object()) {
1410 std::set<std::string> keys;
1413 for (
const auto&
k : keys) {
1415 if (!
b.contains(
k))
return path +
"." +
k +
": " +
short_dump(
a.at(
k)) +
" against absent";
1417 if (!d.empty())
return d;
1422 for (
size_t i = 0;
i < std::min(
a.size(),
b.size()); ++
i) {
1424 if (!d.empty())
return d;
1426 if (
a.size() !=
b.size())
1427 return path +
": " + std::to_string(
a.size()) +
" entries against " + std::to_string(
b.size());
1430 if (
a.is_number() &&
b.is_number())
return a.get<
double>() ==
b.get<
double>() ?
"" :
path +
": " +
a.dump() +
" against " +
b.dump();
1437 const std::string arch =
meta_str(g,
"general.architecture").value_or(
"");
1438 if (arch ==
"paperkiln")
return;
1439 const std::string
tail =
"; a file whose model is not that architecture's graph has general.architecture "
1440 "\"paperkiln\", which no llama.cpp release builds a graph for";
1441 if (!
meta_str(g,
"general.architecture")) {
1442 errs.push_back(E(
"3.1 (rule 38)",
"a file with an embedded document has no general.architecture string (\"paperkiln\" "
1443 "or one of llama, mistral, qwen2, qwen3, gpt2, gptneox, gemma3 and mamba)"));
1447 errs.push_back(E(
"3.1 (rule 38)",
"general.architecture " + q(arch) +
1448 " is neither \"paperkiln\" nor an architecture of section 9.3" +
tail));
1454 }
catch (
const std::exception&) {
1460 }
catch (
const std::exception&
e) {
1461 errs.push_back(E(
"3.1 (rule 38)",
"general.architecture " + q(arch) +
": its metadata builds no graph (" +
e.what() +
1467 errs.push_back(E(
"3.1 (rule 38)",
"general.architecture " + q(arch) +
": the embedded document is not the " + arch +
1468 " graph its metadata describes (section 9.3; first difference, document "
1469 "against graph: " + d +
")" +
tail));
1477 std::vector<std::set<std::string>>
open;
1480 const json::parser_callback_t
cb = [&](
int depth, json::parse_event_t
ev,
json& parsed) {
1481 if (
ev == json::parse_event_t::object_start) {
1482 open.emplace_back();
1483 }
else if (
ev == json::parse_event_t::object_end) {
1484 if (!
open.empty())
open.pop_back();
1485 }
else if (
ev == json::parse_event_t::key &&
dup_depth < 0 && !
open.empty() && parsed.is_string() &&
1486 !
open.back().insert(parsed.get<std::string>()).second) {
1487 dup = parsed.get<std::string>();
1494 throw std::runtime_error(E(
"3.2 (rule 35)",
"an object holds the member name " +
1495 json(
dup).
dump(-1,
' ',
false, json::error_handler_t::replace) +
1496 " twice (nesting depth " + std::to_string(
dup_depth) +
1497 "): a document names each member of an object once"));
1510 static const std::regex
kNode(R
"(^doc\.(embed|block|head)\.nodes\[(\d+)\](?: \([A-Za-z0-9_]+\))?(.*)$)");
1511 static const std::regex
kSlot(R
"(^doc\.block\.state\[(\d+)\](.*)$)");
1512 static const std::set<std::string>
kClosed = {
"gelu.approx",
"rope.pairing",
"rope.rope_heads",
"attention.mask",
1513 "moe_ffn.gate_norm",
"moe_ffn.activation",
"moe_ffn.scoring",
1514 "linear_attention.feature_map",
1515 "depth_route.route",
"depth_route.gate"};
1516 static const std::set<std::string>
kRecDecl = {
"index",
"inputs",
"weights",
"init",
"let",
"update",
"readout"};
1518 const bool required =
msg.rfind(
"lacks the required key '", 0) == 0;
1520 if (
rest.size() < 2 ||
rest[0] !=
'.')
return std::string();
1521 const size_t e =
rest.find_first_of(
".[", 1);
1522 return rest.substr(1,
e == std::string::npos ? std::string::npos :
e - 1);
1527 const size_t j = std::stoul(
m[2].
str());
1530 if (key ==
"position_bias")
1533 if (key ==
"relative_kv")
1537 return unknown_key ?
"3.5 (rule 4)" :
"3.8 (rule 43)";
1538 if (key ==
"freq_factors")
1541 const bool io = key ==
"in" || key ==
"out" || (
required && (
msg.find(
"'in'") != std::string::npos ||
1542 msg.find(
"'out'") != std::string::npos));
1543 if (
io)
return "3.5 (rule 7)";
1544 if (
unknown_key || key ==
"op")
return "3.5 (rule 4)";
1546 if (key ==
"tensor_index" ||
kClosed.count(op +
"." + key))
return "3.5 (rule 5)";
1547 if (key ==
"causal" && (op ==
"attention" || op ==
"linear_attention" || op ==
"mla_attention"))
1548 return op ==
"attention" ?
"5.15 (rule 14)" : op ==
"mla_attention" ?
"5.20 (rule 14)" :
"6.4 (rule 14)";
1549 if (op ==
"split" && key ==
"groups")
1551 if (op ==
"recurrence" &&
kRecDecl.count(key))
return "6.10 (rule 31)";
1552 return "3.5 (rule 1)";
1556 const size_t j = std::stoul(
m[1].
str());
1559 if (
unknown_key &&
get(slot,
"kind") ==
"recurrent")
return "6.3 (rule 13)";
1560 if (
unknown_key)
return msg.find(
"'kv_from'") != std::string::npos ?
"7.6 (rule 22)" :
"6.1 (rule 1)";
1561 if (key ==
"kind" || (
required &&
msg.find(
"'kind'") != std::string::npos))
return "6.1 (rule 11)";
1562 if (
get(slot,
"kind") ==
"kv") {
1563 if (key ==
"dtype" || key ==
"evict" || key ==
"kv_rows")
return "6.2 (rule 5)";
1564 return "6.2 (rule 12)";
1566 if (
get(slot,
"kind") ==
"recurrent")
return "6.3 (rule 13)";
1567 return "6.1 (rule 1)";
1569 if (
path.rfind(
"doc.ir_version", 0) == 0)
return "2.3 (rule 2)";
1570 if (
path.rfind(
"doc.model", 0) == 0) {
1572 for (
const char*
k : {
"vocab",
"d",
"n_layers",
"ctx"})
1574 return field ?
"3.3 (rule 3)" :
"3.3 (rule 1)";
1576 if (
path.rfind(
"doc.block.weight_index", 0) == 0)
return "7.1 (rule 18)";
1577 if (
path.rfind(
"doc.block.repeat", 0) == 0)
return "3.3 (rule 3)";
1578 return "3.2 (rule 1)";
1586 std::vector<std::string>
out;
1587 std::set<std::string>
seen;
1593 if (!
doc.is_object())
return {E(
"10.1 rule 1",
"the document is not a JSON object")};
1595 const json&
v = get(
doc,
"ir_version");
1597 errs.push_back(E(
"2.3",
"ir_version " +
v.dump() +
" is not supported (ember reads 0, 1)"));
1603 std::vector<std::string>
out;
1604 std::set<std::string>
seen;
1614 std::vector<std::string>
out;
1615 std::set<std::string>
seen;
1622 if (
doc.is_object())
1624 if (
errs.empty())
return;
1626 for (
const auto&
e :
errs)
msg += (
msg.empty() ?
"" :
"; ") +
e;
1627 throw std::runtime_error(
msg);
static Expr parse(const std::string &text, size_t n_inputs=1)
Data structures for GGUF (GPT-Generated Unified Format) file format.
const float * p[kIrDepthSourcesMax]
std::vector< Token > toks
std::vector< Value > items
void eval(const Expr &e, const std::vector< const float * > &ins, int T, int w, float *out)
std::string build_ir_from_gguf(const GGUFData &g, bool log)
const char *const kIrSchemaPieces[]
json parse_ir_document(const std::string &text)
DigestResult ir_digest(const std::string &text, bool prose)
void validate_ir(const json &doc, const GGUFData &gguf)
std::vector< std::string > ir_document_errors(const json &doc)
std::vector< std::string > ir_container_errors(const json &doc, const GGUFData &gguf)
std::vector< std::string > ir_schema_errors(const json &doc)
bool has_ir_builder(const std::string &arch)
Template get(const std::string &id)
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