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

Namespaces

namespace  cpu
 
namespace  expr
 
namespace  rec
 

Classes

struct  DigestResult
 
class  Interpreter
 
struct  Node
 

Functions

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)
 
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)
 
bool has_recurrent_state (const std::string &ir_json)
 
bool has_windowed_attention (const std::string &ir_json)
 
bool has_state_tokens (const std::string &ir_json)
 
bool tuned_llama_match (const std::string &ir_json, const GGUFData &gguf, std::string *why)
 
bool device_supported (const std::string &ir_json)
 
bool has_ir_builder (const std::string &arch)
 
std::string find_ir (const GGUFData &gguf)
 
std::string build_ir_from_gguf (const GGUFData &g, bool log)
 
static void cpu_only (bool gpu, const char *what)
 
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_mor_gate_cuda (const float *x_in, float *x, const float *g, int n, 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_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_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)
 
void ir_kv_compact_cuda (float *K, float *V, const int *idx, int w0, int n, int row, 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_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)
 
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)
 
void ir_relu_cuda (const float *x, float *out, size_t n, cudaStream_t stream)
 
void ir_sigmoid_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_mul_col_cuda (const float *x, const float *c, float *out, int T, int w, cudaStream_t stream)
 
void ir_scale_cuda (const float *x, const float *g, const float *b, float *out, int T, int w, cudaStream_t stream)
 
void ir_rms_row_cuda (const float *x, float *out, int T, int w, float eps, cudaStream_t stream)
 
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)
 
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_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)
 
void ir_embedding_cuda (const float *table, const int *tokens, float *out, int T, int d, 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_layernorm_cuda (const float *x, const float *w, const float *b, float *out, int T, int d, float eps, cudaStream_t stream)
 
void ir_bias_add_cuda (float *y, const float *b, int T, int O, cudaStream_t stream)
 
void ir_binary_cuda (const float *a, const float *b, float *out, size_t n, int op, cudaStream_t stream)
 
void ir_silu_cuda (const float *x, float *out, size_t n, cudaStream_t stream)
 
void ir_gelu_cuda (const float *x, float *out, size_t n, bool tanh_form, cudaStream_t stream)
 
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_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)
 
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_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)
 
size_t ir_expr_scratch_floats (const std::vector< expr::Stage > &stages, int T, int w)
 
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_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_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)
 
json parse_ir_document (const std::string &text)
 
std::vector< std::string > ir_schema_errors (const json &doc)
 
std::vector< std::string > ir_document_errors (const json &doc)
 
std::vector< std::string > ir_container_errors (const json &doc, const GGUFData &gguf)
 
void validate_ir (const json &doc, const GGUFData &gguf)
 
std::string sha256_hex_bytes (const std::string &bytes)
 
std::string es_number_string (double v)
 
DigestResult ir_digest (const std::string &text, bool prose)
 

Variables

const char *const kIrSchemaPieces []
 
const char *const patternProperties
 

Function Documentation

◆ build_ir_from_gguf()

std::string ember_ir::build_ir_from_gguf ( const GGUFData &  g,
bool  log 
)

Definition at line 553 of file graph_ir.cpp.

553 {
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 {
558 auto v = meta_number(g, p + key);
559 if (!v) v = dflt;
560 if (!v) throw std::runtime_error("IR builder: " + arch + " file has no " + p + key);
561 return *v;
562 };
563 auto shape_has = [&](const std::string& t) { return g.tensor_infos_map.count(t) > 0; };
564 auto no_rope_scaling = [&]() {
565 const auto scaling = meta_string(g, p + "rope.scaling.type");
566 if ((scaling && *scaling != "none") || shape_has("rope_freqs.weight"))
567 throw std::runtime_error("IR builder: " + arch + " with RoPE scaling is not built yet");
568 };
569 const int d = static_cast<int>(get("embedding_length"));
570 const int n_layers = static_cast<int>(get("block_count"));
571 int vocab = static_cast<int>(g.tokenizer_tokens.size());
572 if (vocab == 0) {
573 auto it = g.tensor_infos_map.find("token_embd.weight");
574 if (it == g.tensor_infos_map.end() || it->second.shape.size() < 2)
575 throw std::runtime_error("IR builder: no tokens and no token_embd.weight");
576 vocab = static_cast<int>(it->second.shape[1]);
577 }
578 const std::string B = "blk.{i}.";
579
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;
584 if (!tied.empty()) n["tied_to"] = tied;
585 return n;
586 };
587 auto rmsnorm = [](const std::string& x, const std::string& w, double eps, const std::string& out, int group = 0) {
588 json n = {{"op", "rmsnorm"}, {"in", {x}}, {"weight", w}, {"eps", f32_value(eps)}, {"out", out}};
589 if (group) n["group"] = group;
590 return n;
591 };
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"},
594 {"bias", name + ".bias"}, {"eps", f32_value(eps)}, {"out", out}};
595 };
596 auto op = [](const std::string& name, std::vector<std::string> in, const json& out, json attrs = json::object()) {
597 attrs["op"] = name;
598 attrs["in"] = in;
599 attrs["out"] = out;
600 return attrs;
601 };
602 auto rope = [](int H, int KV, int hd, int n_rot, const json& theta, const std::string& pairing,
603 const json& freq_scale = nullptr) {
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"}}};
607 if (!freq_scale.is_null()) n["freq_scale"] = freq_scale;
608 return n;
609 };
610 auto attention = [](int H, int KV, int hd, double scale, const json& window = 0) {
611 return json{{"op", "attention"}, {"in", {"q", "k", "v"}}, {"state", "kv"}, {"heads", H},
612 {"kv_heads", KV}, {"head_dim", hd}, {"causal", true}, {"scale", f32_value(scale)},
613 {"window", window}, {"sinks", 0}, {"softmax", "max-shifted, masked entries exactly zero"},
614 {"out", "a"}};
615 };
616 auto kv_state = [](int H, int KV, int hd, int ctx, const json& window = 0) {
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"}};
619 };
620 const bool tied = !shape_has("output.weight");
621 auto tied_head = [&](json norm_nodes) {
622 norm_nodes.push_back(linear("h", "output.weight", "logits", "", tied ? "token_embd.weight" : ""));
623 return norm_nodes;
624 };
625 json embed = json::array({{{"op", "embedding"}, {"in", {"tokens"}}, {"weight", "token_embd.weight"}, {"out", "x"}}});
627 int ctx = 0;
628 std::string summary;
629
630 // A truth value (IR_SPEC D.1): a bool, or a number that is true when non-zero.
631 auto truth = [&](const std::string& key, bool dflt) {
632 if (const auto b = meta_bool(g, p + key)) return *b;
633 if (const auto v = meta_number(g, p + key)) return *v != 0.0;
634 return dflt;
635 };
636 // moe_ffn in llama.cpp's expert layout (IR_SPEC D.3, D.10), its gates normalised as gate_norm says.
637 auto moe_node = [&](const std::string& gate_norm) {
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"))},
644 {"gate_norm", gate_norm},
645 {"activation", "silu"}});
646 };
647 // The shared expert's SwiGLU over ffn_{gate,up,down}_shexp (IR_SPEC D.10, D.11) -> sf.
648 auto shared_expert = [&](json& nodes) {
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")})
652 nodes.push_back(n);
653 };
654 const bool qwen_moe = arch == "qwen2moe" || arch == "qwen3moe";
655
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);
665 // HF Mistral's window (key j visible from query p iff p - w < j <= p); qwen2's
666 // GGUF does not say which layers a window would cover, so none.
667 int window = 0;
668 if (arch == "llama" || arch == "mistral")
669 window = static_cast<int>(meta_number(g, p + "attention.sliding_window").value_or(0));
670 const bool bias = shape_has("blk.0.attn_q.bias"), qk_norm = shape_has("blk.0.attn_q_norm.weight");
671 const std::string pairing = arch == "qwen2" || arch == "qwen3" || qwen_moe ? "neox" : "interleaved";
672 auto proj = [&](const std::string& name, const std::string& out) {
673 return linear("h", B + name + ".weight", out, bias ? B + name + ".bias" : "");
674 };
675 // Mixtral's layout (llama.cpp's llama graph; IR_SPEC 9.3, revision 1.3): llama.expert_count and ffn_gate_inp
676 // put a moe_ffn in place of the SwiGLU.
677 const bool moe = arch == "llama" && meta_number(g, p + "expert_count").value_or(0) > 0 &&
678 shape_has("blk.0.ffn_gate_inp.weight");
679 nodes = json::array({rmsnorm("x", B + "attn_norm.weight", eps, "h"), proj("attn_q", "q"), proj("attn_k", "k"),
680 proj("attn_v", "v")});
681 if (qk_norm) {
682 nodes.push_back(rmsnorm("q", B + "attn_q_norm.weight", eps, "q", hd));
683 nodes.push_back(rmsnorm("k", B + "attn_k_norm.weight", eps, "k", hd));
684 }
685 for (json n : {rope(H, KV, hd, n_rot, f32_value(theta), pairing),
686 attention(H, KV, hd, 1.0 / std::sqrt(static_cast<double>(hd)), window),
687 linear("a", B + "attn_output.weight", "o"), op("add", {"x", "o"}, "x"),
688 rmsnorm("x", B + "ffn_norm.weight", eps, "h2")})
689 nodes.push_back(n);
690 // Qwen-MoE (IR_SPEC D.10, revision 1.5): qwen2's / qwen3's attention, moe_ffn with gate_norm raw for qwen2moe
691 // (llama.cpp b6000's graph ignores norm_topk_prob) and renorm for qwen3moe; qwen2moe's shared expert times
692 // sigmoid(ffn_gate_inp_shexp h2) per token, added to f.
693 const bool shexp = arch == "qwen2moe" && shape_has("blk.0.ffn_gate_inp_shexp.weight");
694 if (moe) {
695 nodes.push_back(moe_node("renorm"));
696 } else if (qwen_moe) {
697 nodes.push_back(moe_node(arch == "qwen2moe" ? "raw" : "renorm"));
698 if (shexp) {
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")})
702 nodes.push_back(n);
703 }
704 } else {
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")})
707 nodes.push_back(n);
708 }
709 nodes.push_back(op("add", {"x", "f"}, "x"));
710 head = tied_head(json::array({rmsnorm("x", "output_norm.weight", eps, "h")}));
711 state = json::array({kv_state(H, KV, hd, ctx, window)});
712 summary = "heads " + std::to_string(H) + "/" + std::to_string(KV) + " x " + std::to_string(hd) + ", n_rot " +
713 std::to_string(n_rot) + ", " + pairing + " RoPE" + (window ? ", window " + std::to_string(window) : "") +
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") {
718 // LayerNorm blocks over a fused, biased qkv (q | k | v as llama.cpp's convert
719 // writes it; for gptneox it has already de-interleaved HF's per-head [q k v] rows).
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");
726 if (!neox)
727 embed.push_back({{"op", "pos_embedding"}, {"in", {"x", "pos"}}, {"weight", "position_embd.weight"}, {"out", "x"}});
728 nodes = json::array({layernorm("x", B + "attn_norm", eps, "h"),
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}}})});
731 int n_rot = 0;
732 if (neox) {
734 n_rot = static_cast<int>(get("rope.dimension_count", hd));
735 nodes.push_back(rope(H, KV, hd, n_rot, f32_value(get("rope.freq_base", 10000.0)), "neox"));
736 }
737 nodes.push_back(attention(H, KV, hd, 1.0 / std::sqrt(static_cast<double>(hd))));
738 nodes.push_back(linear("a", B + "attn_output.weight", "o", B + "attn_output.bias"));
739 const json ffn = json::array({layernorm("x", B + "ffn_norm", eps, "h2"),
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")});
743 const bool parallel = neox && meta_bool(g, p + "use_parallel_residual").value_or(true);
744 if (parallel) {
745 // llama.cpp: cur = ffn(ln2 x) + x, then + attn_out; HF sums mlp + attn + x (ROUNDING apart).
746 for (const auto& n : ffn) nodes.push_back(n);
747 nodes.push_back(op("add", {"f", "x"}, "x"));
748 nodes.push_back(op("add", {"x", "o"}, "x"));
749 } else {
750 nodes.push_back(op("add", {"x", "o"}, "x"));
751 for (const auto& n : ffn) nodes.push_back(n);
752 nodes.push_back(op("add", {"x", "f"}, "x"));
753 }
754 head = json::array({layernorm("x", "output_norm", eps, "h")});
755 if (neox)
756 head.push_back(linear("h", "output.weight", "logits"));
757 else
759 state = json::array({kv_state(H, KV, hd, ctx)});
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") {
764 // llama.cpp b6000 hparams.set_swa_pattern(6): layer i is local (windowed,
765 // theta 10000) iff i % 6 < 5; the rest are global (the file's theta, linear scaling).
766 constexpr int kSwaPattern = 6;
767 constexpr double kLocalTheta = 10000.0;
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");
775 const auto stype = meta_string(g, p + "rope.scaling.type");
776 if (stype && *stype != "none" && *stype != "linear")
777 throw std::runtime_error("IR builder: gemma3 RoPE scaling '" + *stype + "' is not built yet");
778 double factor = 1.0;
779 if (stype && *stype == "linear") {
780 factor = meta_number(g, p + "rope.scaling.factor").value_or(1.0);
781 if (factor == 0.0) factor = 1.0;
782 }
783 const int win = static_cast<int>(get("attention.sliding_window"));
784 const double theta_global = get("rope.freq_base");
785 json windows = json::array(), thetas = json::array(), scales = json::array();
786 for (int i = 0; i < n_layers; ++i) {
787 const bool local = i % kSwaPattern < kSwaPattern - 1;
788 windows.push_back(local ? win : 0);
790 scales.push_back(local ? 1.0 : f32_value(1.0 / factor));
791 }
792 windows = {{"per_layer", windows}};
793 thetas = {{"per_layer", thetas}};
794 scales = {{"per_layer", scales}};
795 // llama.cpp: f_attention_scale = 1/sqrt(n_embd_head_k), except 1/sqrt(n_embd / n_head) for the 27B.
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}}));
799 nodes = json::array({rmsnorm("x", B + "attn_norm.weight", eps, "h"), linear("h", B + "attn_q.weight", "q"),
800 linear("h", B + "attn_k.weight", "k"), linear("h", B + "attn_v.weight", "v"),
801 rmsnorm("q", B + "attn_q_norm.weight", eps, "q", hd),
802 rmsnorm("k", B + "attn_k_norm.weight", eps, "k", hd),
803 rope(H, KV, hd, n_rot, thetas, "neox", factor != 1.0 ? scales : json(nullptr)),
804 attention(H, KV, hd, scale, windows), linear("a", B + "attn_output.weight", "o"),
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")});
810 head = tied_head(json::array({rmsnorm("x", "output_norm.weight", eps, "h")}));
811 state = json::array({kv_state(H, KV, hd, ctx, windows)});
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") {
816 // DeepSeek-V2 and V3 in llama.cpp's MLA layout (IR_SPEC Appendix D.11; revision 1.6's leading dense blocks, V3's
817 // router and YaRN): the query from attn_q (V2-Lite) or the low-rank attn_q_a / attn_q_b; attn_kv_a_mqa h split
818 // into the latent c and the shared rotary key kr; mla_attention over a kv slot holding [c | kr]; the first
819 // leading_dense_block_count applications a dense SwiGLU, the rest moe_ffn (times expert_weights_scale) plus
820 // ungated shared experts, the two branches selected by apply. Files converted before llama.cpp's MLA keys,
821 // other gating functions and other RoPE scaling build no graph.
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");
825 if (!shape_has("blk.0.attn_k_b.weight") || !meta_number(g, p + "attention.key_length_mla"))
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);
829 if (gating == 0.0) gating = 1.0;
830 if (gating != 1.0 && gating != 2.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");
833 const auto stype = meta_string(g, p + "rope.scaling.type");
834 if (shape_has("rope_freqs.weight") || (stype && *stype != "none" && *stype != "yarn"))
835 throw std::runtime_error("IR builder: deepseek2 with RoPE scaling other than YaRN builds no graph");
836 const bool yarn = stype && *stype == "yarn";
837 const int k = static_cast<int>(get("leading_dense_block_count", 0.0));
838 if (k < 0 || k > n_layers)
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) + "."; // the first mixture application's tensors
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;
849 // YaRN (IR_SPEC 5.14, revision 1.6): pair j's divisor f_j = 1 / ((1 - e_j) / c + e_j), e_j = 1 - ramp(j), the
850 // ramp linear from the correction dimension of beta_fast = 32 rotations over the original context (floor) to
851 // that of beta_slow = 1 (ceiling), within 0 .. dr - 1; in binary64, each value rounded to f32. The softmax
852 // temperature m = 1 + yarn_log_multiplier ln(c) enters the attention scale as m^2.
853 json yarn_f = nullptr;
854 double mscale = 1.0;
855 if (yarn) {
856 const double c = get("rope.scaling.factor"), o = get("rope.scaling.original_context_length");
857 const double pi = 3.14159265358979323846;
858 auto corr = [&](double beta) { return dr * std::log(o / (beta * 2.0 * pi)) / (2.0 * std::log(theta)); };
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;
861 yarn_f = json::array();
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;
865 yarn_f.push_back(f32_value(1.0 / ((1.0 - e) / c + e)));
866 }
867 mscale = 1.0 + get("rope.scaling.yarn_log_multiplier", 0.0) * std::log(c);
868 }
869 nodes = json::array({rmsnorm("x", B + "attn_norm.weight", eps, "h")});
870 const bool q_lora = shape_has("blk.0.attn_q_a.weight");
871 if (q_lora) {
872 for (json n : {linear("h", B + "attn_q_a.weight", "qa"), rmsnorm("qa", B + "attn_q_a_norm.weight", eps, "qa"),
873 linear("qa", B + "attn_q_b.weight", "q")})
874 nodes.push_back(n);
875 } else {
876 nodes.push_back(linear("h", B + "attn_q.weight", "q"));
877 }
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"}}};
881 if (yarn) rope_node["freq_factors"] = yarn_f;
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}}}),
884 rmsnorm("c", B + "attn_kv_a_norm.weight", eps, "c"), rope_node,
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"},
888 {"scale", f32_value(mscale * mscale / std::sqrt(static_cast<double>(dn + dr)))},
889 {"causal", true}, {"window", 0}, {"sinks", 0}, {"out", "a"}},
890 linear("a", B + "attn_output.weight", "o"), op("add", {"x", "o"}, "x"),
891 rmsnorm("x", B + "ffn_norm.weight", eps, "h2")})
892 nodes.push_back(n);
893 // apply (IR_SPEC 3.8): with 0 < k < L the dense branch runs in applications i < k, the mixture in i >= k.
894 const bool split_layers = k > 0 && k < n_layers;
895 auto applied = [&](json n, bool dense) {
896 if (split_layers) {
897 json pl = json::array();
898 for (int i = 0; i < n_layers; ++i) pl.push_back(dense ? i < k : i >= k);
899 n["apply"] = {{"per_layer", pl}};
900 }
901 return n;
902 };
903 if (k > 0)
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")})
906 nodes.push_back(applied(n, true));
907 const bool shexp = k < n_layers && shape_has(M + "ffn_gate_shexp.weight");
908 std::string router_note;
909 if (k < n_layers) {
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());
918 nodes.push_back(applied(moe, false));
919 if (s != 1.0) nodes.push_back(applied(op("affine", {"f"}, "f", {{"scale", f32_value(s)}, {"shift", 0.0}}), false));
920 if (shexp) {
921 json sh = json::array();
923 for (const auto& n : sh) nodes.push_back(applied(n, false));
924 nodes.push_back(applied(op("add", {"f", "sf"}, "f"), false));
925 }
926 }
927 nodes.push_back(op("add", {"x", "f"}, "x"));
928 head = tied_head(json::array({rmsnorm("x", "output_norm.weight", eps, "h")}));
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 (" +
936 gate_norm + router_note + (s != 1.0 ? ", x " + json(f32_value(s)).dump() : std::string()) +
937 ")" + (shexp ? " + shared experts" : "")
938 : std::string());
939 } else if (arch == "phi3") {
940 // IR_SPEC Appendix D.8 (revision 1.5): pre-RMSNorm, fused (or separate) q/k/v, NEOX RoPE over
941 // rope.dimension_count dims with LongRoPE's factor sets when the file has them, HF's sliding window, SwiGLU
942 // from the fused ffn_up (gate | up), output or the tied embedding.
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");
955 const bool fs = shape_has("rope_factors_short.weight"), fl = shape_has("rope_factors_long.weight");
956 if (fs != fl)
957 throw std::runtime_error("IR builder: phi3 with one LongRoPE factor set (rope_factors_short / _long) builds no graph");
958 json r = rope(H, KV, hd, n_rot, f32_value(theta), "neox");
959 double m = 1.0;
960 if (fs) {
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;
966 if (m != 1.0) r["attn_factor"] = f32_value(m);
967 }
968 auto bias = [&](const std::string& name) { return shape_has("blk.0." + name + ".bias") ? B + name + ".bias" : ""; };
969 nodes = json::array({rmsnorm("x", B + "attn_norm.weight", eps, "h")});
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}}}));
973 } else {
974 for (const char* t : {"q", "k", "v"})
975 nodes.push_back(linear("h", B + "attn_" + t + ".weight", t, bias(std::string("attn_") + t)));
976 }
977 for (json n : {r, attention(H, KV, hd, 1.0 / std::sqrt(static_cast<double>(hd)), window),
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")})
982 nodes.push_back(n);
983 head = tied_head(json::array({rmsnorm("x", "output_norm.weight", eps, "h")}));
984 state = json::array({kv_state(H, KV, hd, ctx, window)});
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") {
989 // IR_SPEC Appendix D.9 (revision 1.5): gemma3's block without q/k norms; one RoPE base; local applications
990 // i % 2 == 0 windowed; the attention scores soft-capped at attn_logit_softcapping, the logits through
991 // tanh(x / C) * C with C = final_logit_softcapping.
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;
1003 };
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);
1006 json windows = json::array();
1007 for (int i = 0; i < n_layers; ++i) windows.push_back(i % 2 == 0 ? win : 0);
1008 windows = {{"per_layer", windows}};
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));
1012 att["softcap"] = f32_value(cap);
1013 // C: the shortest decimal that reads back as the f32 nearest fcap, written as Python's repr of that float
1014 // (an integral value with ".0").
1015 std::string C;
1016 {
1017 char buf[64];
1018 const auto res = std::to_chars(buf, buf + sizeof(buf), f32_value(fcap));
1019 C.assign(buf, res.ptr);
1020 if (C.find_first_of(".eEn") == std::string::npos) C += ".0";
1021 }
1022 embed.push_back(op("affine", {"x"}, "x", {{"scale", f32_value(std::sqrt(static_cast<double>(d)))}, {"shift", 0.0}}));
1023 nodes = json::array({rmsnorm("x", B + "attn_norm.weight", eps, "h"), linear("h", B + "attn_q.weight", "q"),
1024 linear("h", B + "attn_k.weight", "k"), linear("h", B + "attn_v.weight", "v"),
1025 rope(H, KV, hd, n_rot, f32_value(theta), "neox"), att,
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}})});
1035 state = json::array({kv_state(H, KV, hd, ctx, windows)});
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;
1039 } else { // mamba
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; // llama.cpp writes 1M: a recurrence has no length limit
1045 if (!shape_has("blk.0.ssm_dt.weight") || !shape_has("blk.0.ssm_d"))
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}}})}}});
1067 head = tied_head(json::array({rmsnorm("x", "output_norm.weight", eps, "h")}));
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);
1070 }
1071
1072 const auto eos = meta_number(g, "tokenizer.ggml.eos_token_id");
1073 const auto bos = meta_number(g, "tokenizer.ggml.bos_token_id");
1074 json doc;
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; // IR_SPEC 2.3: as tools/gguf_ir.py's IR_VERSION from paperkiln 5349ee19 on
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"},
1081 {"source", "gguf"},
1082 {"specials", eos ? json{{"eot", static_cast<int64_t>(*eos)}} : json::object()},
1083 {"bos", bos ? json(static_cast<int64_t>(*bos)) : json(nullptr)}};
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"}}});
1086 doc["embed"] = {{"nodes", embed}};
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"}};
1093 if (log)
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" : ""));
1096 return doc.dump();
1097}
static void info(const std::string &message)
Definition logger.cpp:135
const float * in[expr::Expr::kMaxInputs]
const float * p[kIrDepthSourcesMax]
int n
std::vector< float > v
const char * name
float c
node layernorm
node rmsnorm
node out
node scale
node linear
size_t j
Constants for attention mechanism calculations.
bool has_ir_builder(const std::string &arch)
Definition graph_ir.cpp:519
Template get(const std::string &id)
Constants for Rotary Position Embedding (RoPE)
nlohmann::json json
Definition server.cpp:68
std::vector< std::string > tokenizer_tokens
std::map< std::string, GGUFTensorInfo > tensor_infos_map

References c, has_ir_builder(), in, Logger::info(), j, k, layernorm, linear, n, name, out, p, rmsnorm, scale, state, GGUFData::tensor_infos_map, GGUFData::tokenizer_tokens, and v.

Referenced by find_ir().

◆ cpu_only()

static void ember_ir::cpu_only ( bool  gpu,
const char *  what 
)
static

Definition at line 3053 of file graph_ir.cpp.

3053 {
3054 if (gpu) throw std::runtime_error(std::string("IR ") + what + " is implemented for the CPU path only");
3055}

References cpu_only(), and j.

Referenced by cpu_only(), ember_ir::Interpreter::kv_positions(), ember_ir::Interpreter::load_state(), ember_ir::Interpreter::recurrent_values(), ember_ir::Interpreter::save_state(), and ember_ir::Interpreter::truncate().

◆ device_supported()

bool ember_ir::device_supported ( const std::string &  ir_json)

Definition at line 463 of file graph_ir.cpp.

463 {
464 // Per-turn state tokens (state_weight, mask turn_state, evict turn_state) run on either path.
465 const json j = json::parse(ir_json);
466 // depth_attend reads at most every tensor ever put in a list (list inputs
467 // and appends, the block's once per repeat) plus its own extra inputs.
468 // Per-layer attributes (the ones the interpreter accepts: attention and
469 // kv-state window and sinks, RoPE theta and freq_scale) run on either path.
470 // Mixture-of-Recursions (depth_route) runs a step on its active rows at their own positions: rope and
471 // attention take them (mor_step_device); a pos_embedding in its block stays on the CPU.
472 size_t list_tensors = 0, extra = 0;
473 bool mor = false, block_pos_embedding = false, softcap = false, turn_state = false;
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";
482 block_pos_embedding = block_pos_embedding || op == "pos_embedding";
483 }
484 if (op == "attention" && n.contains("position_bias")) return false; // CPU only (IR_SPEC 5.15, revision 1.3)
485 if (op == "attention" && n.contains("relative_kv")) return false; // CPU only (IR_SPEC 5.15, revision 1.4)
486 // revision 1.6's apply and DeepSeek-V3's router have no device path: a CUDA build serves the document on the CPU
487 if (n.contains("apply")) return false;
488 if (op == "moe_ffn" && (n.contains("scoring") || n.contains("selection_bias") || n.contains("groups"))) return false;
489 // softcap (revision 1.5) runs in the window kernel; with turn_state or depth_route it stays on the CPU
490 if (op == "attention" && n.contains("softcap")) softcap = true;
491 if (op == "attention" && n.contains("mask") && n.at("mask") != "causal") turn_state = true;
492 if (op == "expr") { // the CUDA form's limits (graph_ir_expr.h lower)
493 try {
494 expr::lower(expr::cached(n.at("expr").get<std::string>(), n.at("in").size()));
495 } catch (const std::exception&) {
496 return false;
497 }
498 }
499 const size_t ins = n.contains("in") && n.at("in").is_array() ? n.at("in").size() : 1;
500 if (op == "list") list_tensors += ins * times;
501 if (op == "append") list_tensors += times;
502 if (op == "depth_attend") extra = std::max(extra, ins - 1);
503#ifdef HAS_CUDA
504 if (op == "ssm_scan" && n.value("n", 0) > kIrSsmMax) return false;
505#endif
506 }
507 }
508 if (mor && block_pos_embedding) return false;
509 // A window under depth_route (IR_SPEC 5.15, v1.4.2) counts positions over an application's compact cache: the
510 // CPU interpreter's (attention under attn_row_); a CUDA build serves such a document on the CPU.
511 if (mor && has_windowed_attention(ir_json)) return false;
512 if (softcap && (mor || turn_state)) return false;
513#ifdef HAS_CUDA
514 if (list_tensors + extra > static_cast<size_t>(kIrDepthSourcesMax)) return false;
515#endif
516 return true;
517}
float value
node sub
bool has_windowed_attention(const std::string &ir_json)
Definition graph_ir.cpp:245

References ember_ir::expr::cached(), has_windowed_attention(), j, ember_ir::expr::lower(), n, and sub.

Referenced by ember_ir::Interpreter::init(), and tinyllama::TinyLlamaSession::TinyLlamaSession().

◆ es_number_string()

std::string ember_ir::es_number_string ( double  v)

Definition at line 472 of file ir_digest.cpp.

472 {
473 if (v == 0.0) return "0";
474 if (v < 0) return "-" + es_number_string(-v);
475 char buf[64];
476 const auto r = std::to_chars(buf, buf + sizeof(buf), v, std::chars_format::scientific);
477 if (r.ec != std::errc()) throw std::runtime_error("es_number_string: to_chars failed");
478 const std::string sci(buf, r.ptr);
479 const size_t e = sci.find('e');
480 std::string digits;
481 for (size_t i = 0; i < e; ++i)
482 if (sci[i] != '.') digits += sci[i];
483 const int exp10 = std::atoi(sci.c_str() + e + 1);
484 const int k = static_cast<int>(digits.size()), n = exp10 + 1;
485 if (k <= n && n <= 21) return digits + std::string(static_cast<size_t>(n - k), '0');
486 if (0 < n && n <= 21) return digits.substr(0, n) + "." + digits.substr(n);
487 if (-6 < n && n <= 0) return "0." + std::string(static_cast<size_t>(-n), '0') + digits;
488 const int ex = n - 1;
489 std::string out = digits.substr(0, 1);
490 if (k > 1) out += "." + digits.substr(1);
491 out += ex >= 0 ? "e+" : "e-";
492 out += std::to_string(ex >= 0 ? ex : -ex);
493 return out;
494}
std::string es_number_string(double v)

References es_number_string(), j, k, n, out, and v.

Referenced by es_number_string().

◆ find_ir()

std::string ember_ir::find_ir ( const GGUFData &  gguf)

Definition at line 525 of file graph_ir.cpp.

525 {
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;
530 ss << f.rdbuf();
531 return ss.str();
532 }
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;
536 }
537 // No IR in the file: every built architecture gets one from its metadata,
538 // except that llama and mistral without a sliding window keep the llama path
539 // unless EMBER_IR_BUILD=1 (a window needs the IR: the llama path has none).
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");
543 const bool windowed = meta_number(gguf, arch + ".attention.sliding_window").value_or(0) > 0;
544 return windowed || (b && std::string(b) == "1") ? build_ir_from_gguf(gguf) : std::string();
545 }
546 if (has_ir_builder(arch)) return build_ir_from_gguf(gguf);
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)");
551}
std::string build_ir_from_gguf(const GGUFData &g, bool log)
Definition graph_ir.cpp:553

References build_ir_from_gguf(), has_ir_builder(), and j.

Referenced by TinyLlamaModel::TinyLlamaModel(), and tinyllama::TinyLlamaSession::TinyLlamaSession().

◆ has_ir_builder()

bool ember_ir::has_ir_builder ( const std::string &  arch)

Definition at line 519 of file graph_ir.cpp.

519 {
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;
523}

References j.

Referenced by build_ir_from_gguf(), and find_ir().

◆ has_recurrent_state()

bool ember_ir::has_recurrent_state ( const std::string &  ir_json)

Definition at line 237 of file graph_ir.cpp.

237 {
238 const json j = json::parse(ir_json);
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;
242 return false;
243}

References j.

◆ has_state_tokens()

bool ember_ir::has_state_tokens ( const std::string &  ir_json)

Definition at line 257 of file graph_ir.cpp.

257 {
258 const json j = json::parse(ir_json);
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;
265 }
266 }
267 if (j.contains("block"))
268 for (const auto& s : j.at("block").value("state", json::array()))
269 if (has(s, "evict")) return true;
270 return false;
271}

References j, n, and sub.

◆ has_windowed_attention()

bool ember_ir::has_windowed_attention ( const std::string &  ir_json)

Definition at line 245 of file graph_ir.cpp.

245 {
246 const json j = json::parse(ir_json);
247 if (!j.contains("block")) return false;
248 const json& blk = j.at("block");
249 for (const auto& s : blk.value("state", json::array()))
250 if (any_nonzero(s, "window")) return true;
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"))
253 return true;
254 return false;
255}

References j, and n.

Referenced by device_supported().

◆ ir_affine_cuda()

void ember_ir::ir_affine_cuda ( const float *  x,
float *  out,
size_t  n,
float  scale,
float  shift,
cudaStream_t  stream 
)

Definition at line 737 of file graph_ir_cuda.cu.

737 {
739 check_launch("affine");
740}

References j, n, out, and scale.

◆ ir_bias_add_cuda()

void ember_ir::ir_bias_add_cuda ( float *  y,
const float *  b,
int  T,
int  O,
cudaStream_t  stream 
)

Definition at line 803 of file graph_ir_cuda.cu.

803 {
804 const size_t n = (size_t)T * O;
806 check_launch("bias");
807}

References j, and n.

◆ ir_binary_cuda()

void ember_ir::ir_binary_cuda ( const float *  a,
const float *  b,
float *  out,
size_t  n,
int  op,
cudaStream_t  stream 
)

Definition at line 809 of file graph_ir_cuda.cu.

809 {
810 binary_kernel<<<blocks_for(n), kThreads, 0, stream>>>(a, b, out, n, op);
811 check_launch(op == 0 ? "add" : op == 1 ? "mul" : "sub");
812}
std::vector< E > a

References a, j, n, and out.

◆ ir_container_errors()

std::vector< std::string > ember_ir::ir_container_errors ( const json &  doc,
const GGUFData &  gguf 
)

Definition at line 1609 of file graph_ir_validate.cpp.

1609 {
1610 Errs errs;
1614 std::vector<std::string> out;
1615 std::set<std::string> seen;
1616 for (const auto& m : errs) add_unique(out, seen, m);
1617 return out;
1618}

References ir_container_errors(), j, and out.

Referenced by ir_container_errors(), and validate_ir().

◆ ir_conv1d_causal_cuda()

void ember_ir::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 
)

Definition at line 838 of file graph_ir_cuda.cu.

839 {
840 if (K < 1 || C <= 0) throw std::runtime_error("IR CUDA conv1d_causal: kernel " + std::to_string(K));
841 conv1d_causal_kernel<<<(C + 127) / 128, 128, 0, stream>>>(x, w, bias, state, out, T, C, K);
842 check_launch("conv1d_causal");
843}

References j, out, and state.

◆ ir_depth_attend_cuda()

void ember_ir::ir_depth_attend_cuda ( const float *const *  sources,
int  S,
const float *  w,
float *  out,
int  T,
int  d,
float  eps,
cudaStream_t  stream 
)

Definition at line 759 of file graph_ir_cuda.cu.

760 {
762 throw std::runtime_error("IR CUDA depth_attend: " + std::to_string(S) + " sources (2.." +
763 std::to_string(kIrDepthSourcesMax) + " supported)");
764 DepthSources src{};
765 for (int i = 0; i < S; ++i) src.p[i] = sources[i];
767 check_launch("depth_attend");
768}

References j, and out.

◆ ir_digest()

DigestResult ember_ir::ir_digest ( const std::string &  text,
bool  prose 
)

Definition at line 496 of file ir_digest.cpp.

496 {
498 try {
499 const Value v = Parser(text).document();
500 if (holds_negative_zero(v)) throw NoDigest{"a negative zero (RFC 8785 writes it as 0, so it would share +0's digest)"};
501 write(r.canonical, v, prose);
502 } catch (const NoDigest& e) {
503 r.refused = e.why;
504 r.canonical.clear();
505 return r;
506 }
507 r.sha256 = sha256_hex_bytes(r.canonical);
508 r.ok = true;
509 return r;
510}
std::string text
std::string sha256_hex_bytes(const std::string &bytes)

References j, sha256_hex_bytes(), text, and v.

◆ ir_document_errors()

std::vector< std::string > ember_ir::ir_document_errors ( const json &  doc)

Definition at line 1592 of file graph_ir_validate.cpp.

1592 {
1593 if (!doc.is_object()) return {E("10.1 rule 1", "the document is not a JSON object")};
1594 Errs errs = ir_schema_errors(doc);
1595 const json& v = get(doc, "ir_version");
1596 if (is_int(v) && v.get<int64_t>() != 0 && v.get<int64_t>() != 1)
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;
1605 for (const auto& m : errs) add_unique(out, seen, m);
1606 return out;
1607}

References ir_document_errors(), ir_schema_errors(), j, out, and v.

Referenced by ir_document_errors(), and validate_ir().

◆ ir_embedding_cuda()

void ember_ir::ir_embedding_cuda ( const float *  table,
const int *  tokens,
float *  out,
int  T,
int  d,
cudaStream_t  stream 
)

Definition at line 786 of file graph_ir_cuda.cu.

786 {
788 check_launch("embedding");
789}

References j, and out.

◆ ir_embedding_state_cuda()

void ember_ir::ir_embedding_state_cuda ( const float *  table,
const float *  state,
int  vocab,
const int *  tokens,
float *  out,
int  T,
int  d,
cudaStream_t  stream 
)

Definition at line 702 of file graph_ir_cuda.cu.

703 {
705 check_launch("embedding (state rows)");
706}

References j, out, and state.

◆ ir_expr_cuda()

void ember_ir::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 
)

Definition at line 857 of file graph_ir_cuda.cu.

858 {
859 if (n_ins <= 0 || n_ins > (int)expr::Expr::kMaxInputs || stages.empty() || stages.size() - 1 > expr::kMaxColumns)
860 throw std::runtime_error("IR CUDA expr: " + std::to_string(n_ins) + " inputs, " + std::to_string(stages.size()) +
861 " stages");
862 ExprPtrs p{};
863 for (int k = 0; k < n_ins; ++k) p.in[k] = ins[k];
864 float* full = scratch; // a reduction's argument, [T, w], reused stage by stage
865 for (size_t k = 0; k + 1 < stages.size(); ++k) p.col[k] = scratch + (size_t)T * w + k * (size_t)T;
866 const size_t n = (size_t)T * w;
867 for (size_t k = 0; k < stages.size(); ++k) {
868 const expr::Stage& s = stages[k];
869 if (s.code.empty() || s.code.size() > expr::kMaxCode) throw std::runtime_error("IR CUDA expr: a stage's code");
870 ExprCode code{};
871 code.n = (int)s.code.size();
872 for (size_t i = 0; i < s.code.size(); ++i) code.code[i] = s.code[i];
873 const bool last = k + 1 == stages.size();
874 expr_kernel<<<blocks_for(n), kThreads, 0, stream>>>(code, p, last ? out : full, n, w);
875 check_launch("expr");
876 if (last) break;
877 row_reduce_kernel<<<(T + 63) / 64, 64, 0, stream>>>(full, const_cast<float*>(p.col[k]), T, w, s.reduce == 2);
878 check_launch("expr row reduction");
879 }
880}
expr::Instr code[expr::kMaxCode]

References code, j, k, ember_ir::expr::kMaxCode, ember_ir::expr::kMaxColumns, ember_ir::expr::Expr::kMaxInputs, n, out, and p.

◆ ir_expr_scratch_floats()

size_t ember_ir::ir_expr_scratch_floats ( const std::vector< expr::Stage > &  stages,
int  T,
int  w 
)

Definition at line 852 of file graph_ir_cuda.cu.

852 {
853 if (stages.size() <= 1) return 0;
854 return (size_t)T * w + (stages.size() - 1) * (size_t)T;
855}

References j.

◆ ir_gelu_cuda()

void ember_ir::ir_gelu_cuda ( const float *  x,
float *  out,
size_t  n,
bool  tanh_form,
cudaStream_t  stream 
)

Definition at line 819 of file graph_ir_cuda.cu.

819 {
820 gelu_kernel<<<blocks_for(n), kThreads, 0, stream>>>(x, out, n, tanh_form ? 1 : 0);
821 check_launch("gelu");
822}

References j, n, and out.

◆ ir_kv_compact_cuda()

void ember_ir::ir_kv_compact_cuda ( float *  K,
float *  V,
const int *  idx,
int  w0,
int  n,
int  row,
cudaStream_t  stream 
)

Definition at line 696 of file graph_ir_cuda.cu.

696 {
697 if (w0 >= n) return;
699 check_launch("kv compact");
700}

References j, and n.

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

◆ ir_kv_write_cuda()

void ember_ir::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 
)

Definition at line 720 of file graph_ir_cuda.cu.

721 {
722 const size_t n = (size_t)T * row;
724 check_launch("kv write");
725}

References j, k, n, and v.

◆ ir_layernorm_cuda()

void ember_ir::ir_layernorm_cuda ( const float *  x,
const float *  w,
const float *  b,
float *  out,
int  T,
int  d,
float  eps,
cudaStream_t  stream 
)

Definition at line 797 of file graph_ir_cuda.cu.

798 {
800 check_launch("layernorm");
801}

References j, and out.

◆ ir_linear_attention_cuda()

void ember_ir::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 
)

Definition at line 770 of file graph_ir_cuda.cu.

771 {
772 linear_attention_kernel<<<(width + 127) / 128, 128, 0, stream>>>(q, k, v, num, den, out, T, width, den_floor);
773 check_launch("linear_attention");
774}

References j, k, out, and v.

◆ ir_mla_attention_cuda()

void ember_ir::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 
)

Definition at line 995 of file graph_ir_cuda.cu.

997 {
998 const size_t smem = (static_cast<size_t>(r) * 2 + dr + kMlaChunk) * sizeof(float);
999 if (smem > 48 * 1024)
1000 throw std::runtime_error("IR mla_attention on CUDA: kv_rank " + std::to_string(r) + " needs " + std::to_string(smem) +
1001 " bytes of shared memory (at most 48 KiB)");
1004 check_launch("mla attention");
1005}

References j, out, and scale.

◆ ir_mla_kv_write_cuda()

void ember_ir::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 
)

Definition at line 988 of file graph_ir_cuda.cu.

989 {
990 const size_t n = (size_t)T * (r + dr);
992 check_launch("mla kv write");
993}

References c, j, and n.

◆ ir_mor_gate_cuda()

void ember_ir::ir_mor_gate_cuda ( const float *  x_in,
float *  x,
const float *  g,
int  n,
int  w,
cudaStream_t  stream 
)

Definition at line 665 of file graph_ir_cuda.cu.

665 {
666 if (n <= 0) return;
668 check_launch("mor gate");
669}

References j, and n.

◆ ir_mul_col_cuda()

void ember_ir::ir_mul_col_cuda ( const float *  x,
const float *  c,
float *  out,
int  T,
int  w,
cudaStream_t  stream 
)

Definition at line 742 of file graph_ir_cuda.cu.

742 {
743 const size_t n = (size_t)T * w;
744 mul_col_kernel<<<blocks_for(n), kThreads, 0, stream>>>(x, c, out, n, w);
745 check_launch("mul_col");
746}

References c, j, n, and out.

◆ ir_pos_embedding_cuda()

void ember_ir::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 
)

Definition at line 791 of file graph_ir_cuda.cu.

792 {
794 check_launch("pos_embedding");
795}

References j, and out.

◆ ir_relu_cuda()

void ember_ir::ir_relu_cuda ( const float *  x,
float *  out,
size_t  n,
cudaStream_t  stream 
)

Definition at line 727 of file graph_ir_cuda.cu.

727 {
728 unary_kernel<<<blocks_for(n), kThreads, 0, stream>>>(x, out, n, 0, 0.0f, 0.0f);
729 check_launch("relu");
730}

References j, n, and out.

◆ ir_rms_row_cuda()

void ember_ir::ir_rms_row_cuda ( const float *  x,
float *  out,
int  T,
int  w,
float  eps,
cudaStream_t  stream 
)

Definition at line 754 of file graph_ir_cuda.cu.

754 {
755 rms_row_kernel<<<(T + 63) / 64, 64, 0, stream>>>(x, out, T, w, eps);
756 check_launch("rms_row");
757}

References j, and out.

◆ ir_rope_at_cuda()

void ember_ir::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 
)

Definition at line 671 of file graph_ir_cuda.cu.

672 {
674 check_launch("rope (positions)");
675}

References j, k, and pos.

◆ ir_rope_cuda()

void ember_ir::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 
)

Definition at line 824 of file graph_ir_cuda.cu.

825 {
827 check_launch("rope");
828}

References j, and k.

◆ ir_rope_rows_at_cuda()

void ember_ir::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 
)

Definition at line 677 of file graph_ir_cuda.cu.

678 {
680 throw std::runtime_error("IR CUDA rope: n_rot " + std::to_string(n_rot) + " / head_dim " + std::to_string(hd));
682 check_launch("rope (rows, positions)");
683}

References j, and pos.

◆ ir_rope_rows_cuda()

void ember_ir::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 
)

Definition at line 830 of file graph_ir_cuda.cu.

831 {
833 throw std::runtime_error("IR CUDA rope: n_rot " + std::to_string(n_rot) + " / head_dim " + std::to_string(hd));
835 check_launch("rope (rows)");
836}

References j.

◆ ir_rows_gather_cuda()

void ember_ir::ir_rows_gather_cuda ( const float *  src,
const int *  idx,
float *  dst,
int  n,
int  w,
cudaStream_t  stream 
)

Definition at line 653 of file graph_ir_cuda.cu.

653 {
654 if (n <= 0) return;
656 check_launch("rows gather");
657}

References j, and n.

◆ ir_rows_scatter_cuda()

void ember_ir::ir_rows_scatter_cuda ( const float *  src,
const int *  idx,
float *  dst,
int  n,
int  w,
cudaStream_t  stream 
)

Definition at line 659 of file graph_ir_cuda.cu.

659 {
660 if (n <= 0) return;
662 check_launch("rows scatter");
663}

References j, and n.

◆ ir_scale_cuda()

void ember_ir::ir_scale_cuda ( const float *  x,
const float *  g,
const float *  b,
float *  out,
int  T,
int  w,
cudaStream_t  stream 
)

Definition at line 748 of file graph_ir_cuda.cu.

748 {
749 const size_t n = (size_t)T * w;
750 scale_kernel<<<blocks_for(n), kThreads, 0, stream>>>(x, g, b, out, n, w);
751 check_launch("scale");
752}

References j, n, and out.

◆ ir_schema_errors()

std::vector< std::string > ember_ir::ir_schema_errors ( const json &  doc)

Definition at line 1583 of file graph_ir_validate.cpp.

1583 {
1584 Failures f;
1585 eval(schema(), doc, "doc", f);
1586 std::vector<std::string> out;
1587 std::set<std::string> seen;
1588 for (const auto& [p, m] : f) add_unique(out, seen, E(schema_rule(doc, p, m), p + " " + m));
1589 return out;
1590}

References ir_schema_errors(), j, out, and p.

Referenced by ir_document_errors(), and ir_schema_errors().

◆ ir_selective_scan_cuda()

void ember_ir::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 
)

Definition at line 845 of file graph_ir_cuda.cu.

847 {
848 selective_scan_kernel<<<(D + 127) / 128, 128, 0, stream>>>(x, dt, B, C, A, d, s, y, T, D, N, softplus ? 1 : 0);
849 check_launch("selective_scan");
850}

References j.

◆ ir_sigmoid_cuda()

void ember_ir::ir_sigmoid_cuda ( const float *  x,
float *  out,
size_t  n,
cudaStream_t  stream 
)

Definition at line 732 of file graph_ir_cuda.cu.

732 {
733 unary_kernel<<<blocks_for(n), kThreads, 0, stream>>>(x, out, n, 1, 0.0f, 0.0f);
734 check_launch("sigmoid");
735}

References j, n, and out.

◆ ir_silu_cuda()

void ember_ir::ir_silu_cuda ( const float *  x,
float *  out,
size_t  n,
cudaStream_t  stream 
)

Definition at line 814 of file graph_ir_cuda.cu.

814 {
816 check_launch("silu");
817}
__global__ void silu_kernel(const float *x, float *out, int n)

References j, n, out, and silu_kernel().

◆ ir_ssm_scan_cuda()

void ember_ir::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 
)

Definition at line 776 of file graph_ir_cuda.cu.

777 {
779 throw std::runtime_error("IR CUDA ssm_scan: n " + std::to_string(n) + " (at most " + std::to_string(kIrSsmMax) +
780 ")");
781 const int threads = n < kThreads ? (n + 31) / 32 * 32 : kThreads;
782 ssm_scan_kernel<<<1, threads, (size_t)2 * n * sizeof(float), stream>>>(u, A, B, C, D, s, y, T, n);
783 check_launch("ssm_scan");
784}

References j, and n.

◆ ir_turn_state_attention_cuda()

void ember_ir::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 
)

Definition at line 685 of file graph_ir_cuda.cu.

687 {
689 throw std::runtime_error("IR CUDA turn_state attention: unsupported shape (head_dim " + std::to_string(hd) + ")");
690 const size_t shm = (size_t)(hd + kWinChunk) * sizeof(float);
692 drow);
693 check_launch("turn_state attention");
694}

References j, out, and scale.

◆ ir_window_attention_cuda()

void ember_ir::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 
)

Definition at line 708 of file graph_ir_cuda.cu.

710 {
712 throw std::runtime_error("IR CUDA window attention: unsupported shape (window " + std::to_string(window) +
713 ", head_dim " + std::to_string(hd) + ")");
714 const size_t shm = (size_t)(hd + kWinChunk) * sizeof(float);
716 sinks, dpos, softcap);
717 check_launch("window attention");
718}

References j, out, and scale.

◆ linear_attention_row_cpu()

void ember_ir::linear_attention_row_cpu ( const float *  q,
const float *  k,
const float *  v,
float *  num,
float *  den,
float *  out,
int  width,
float  den_floor 
)

Definition at line 213 of file graph_ir.cpp.

214 {
215 // num and den take token t before out[t] is formed (token t sees keys 0..t).
216 for (int c = 0; c < width; ++c) {
217 const float pk = elu_plus_one(k[c]);
218 num[c] = num[c] + pk * v[c];
219 den[c] = den[c] + pk;
220 out[c] = elu_plus_one(q[c]) * num[c] / std::max(den[c], den_floor);
221 }
222}

References c, j, k, out, and v.

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

◆ parse_ir_document()

nlohmann::json ember_ir::parse_ir_document ( const std::string &  text)

Definition at line 1474 of file graph_ir_validate.cpp.

1474 {
1475 // nlohmann's parse keeps the last of two members with one name; its callback sees every key as it is read,
1476 // so each open object keeps the names it has seen.
1477 std::vector<std::set<std::string>> open;
1478 std::string dup;
1479 int dup_depth = -1;
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>();
1488 dup_depth = depth;
1489 }
1490 return true;
1491 };
1492 json j = json::parse(text, cb);
1493 if (dup_depth >= 0)
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"));
1498 return j;
1499}

References j, parse_ir_document(), and text.

Referenced by ember_ir::Interpreter::Interpreter(), parse_ir_document(), and tuned_llama_match().

◆ sha256_hex_bytes()

std::string ember_ir::sha256_hex_bytes ( const std::string &  bytes)

Definition at line 454 of file ir_digest.cpp.

454 {
455 uint32_t h[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
456 const uint64_t bits = static_cast<uint64_t>(bytes.size()) * 8;
457 std::string msg = bytes;
458 msg += static_cast<char>(0x80);
459 while (msg.size() % 64 != 56) msg += '\0';
460 for (int k = 7; k >= 0; --k) msg += static_cast<char>((bits >> (8 * k)) & 0xFF);
461 for (size_t off = 0; off < msg.size(); off += 64) sha256_block(h, reinterpret_cast<const unsigned char*>(msg.data()) + off);
462 static const char* hex = "0123456789abcdef";
463 std::string out;
464 for (uint32_t x : h)
465 for (int k = 7; k >= 0; --k) out += hex[(x >> (4 * k)) & 15];
466 return out;
467}

References j, k, off, and out.

Referenced by ir_digest().

◆ ssm_scan_row_cpu()

void ember_ir::ssm_scan_row_cpu ( const float *  u,
const float *  A,
const float *  B,
const float *  C,
float  D,
float *  s,
float *  scratch,
float *  y,
int  n 
)

Definition at line 224 of file graph_ir.cpp.

225 {
226 // ops::ssm_scan: all of s_t from s_{t-1}, then y from s_t.
227 for (int i = 0; i < n; ++i) {
228 float acc = 0;
229 for (int j = 0; j < n; ++j) acc += A[static_cast<size_t>(i) * n + j] * s[j];
230 acc += B[i] * u[i];
231 scratch[i] = acc;
232 }
233 std::memcpy(s, scratch, static_cast<size_t>(n) * sizeof(float));
234 for (int i = 0; i < n; ++i) y[i] = C[i] * s[i] + D * u[i];
235}

References j, and n.

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

◆ tuned_llama_match()

bool ember_ir::tuned_llama_match ( const std::string &  ir_json,
const GGUFData &  gguf,
std::string *  why 
)

Definition at line 440 of file graph_ir.cpp.

440 {
441 auto no = [&](const std::string& m) {
442 if (why) *why = m;
443 return false;
444 };
445 json j;
446 try {
447 j = parse_ir_document(ir_json); // rule 35 too: the interpreter then refuses the document
448 } catch (const std::exception& e) {
449 return no(std::string("the document does not parse: ") + e.what());
450 }
451 if (!j.is_object() || j.value("family_hint", json()) != "llama") return no("family_hint is not llama");
452 try {
453 validate_ir(j, gguf); // an invalid document goes to the interpreter, which refuses it
454 LlamaMatch m(j, parse_model_config_from_gguf(gguf));
455 if (!m.run()) return no(m.why);
456 } catch (const std::exception& e) {
457 return no(e.what());
458 }
459 if (why) why->clear();
460 return true;
461}
std::string why
Definition graph_ir.cpp:280
ModelConfig parse_model_config_from_gguf(const GGUFData &gguf)
void validate_ir(const json &doc, const GGUFData &gguf)

References j, parse_ir_document(), parse_model_config_from_gguf(), validate_ir(), and why.

Referenced by TinyLlamaModel::TinyLlamaModel(), and tinyllama::TinyLlamaSession::TinyLlamaSession().

◆ validate_ir()

void ember_ir::validate_ir ( const json &  doc,
const GGUFData &  gguf 
)

Definition at line 1620 of file graph_ir_validate.cpp.

1620 {
1621 std::vector<std::string> errs = ir_document_errors(doc);
1622 if (doc.is_object())
1623 for (const auto& m : ir_container_errors(doc, gguf)) errs.push_back(m);
1624 if (errs.empty()) return;
1625 std::string msg;
1626 for (const auto& e : errs) msg += (msg.empty() ? "" : "; ") + e;
1627 throw std::runtime_error(msg);
1628}
std::vector< std::string > ir_document_errors(const json &doc)
std::vector< std::string > ir_container_errors(const json &doc, const GGUFData &gguf)

References ir_container_errors(), ir_document_errors(), j, and validate_ir().

Referenced by ember_ir::Interpreter::Interpreter(), tuned_llama_match(), and validate_ir().

Variable Documentation

◆ kIrSchemaPieces

const char* const ember_ir::kIrSchemaPieces[]
inline
Initial value:
= {
R"IRS({"$schema":"https:
R"IRS(e":true,"kind":true,"op":{"enum":["linear_attention","ssm_scan","conv1d_causal","selective_scan","recurrence"]},"dtype":{"enum":["f32"]},"tensors":{"type":"array","minItems":1,"items":{"type":"object","required":["name","shape","init"],"properties":{"name":{"$ref":"#/$defs/name"},"shape":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/posint"}},"init":{"enum":[0,"body"]}},"patternProperties":{"^_":true},"additionalProperties":false}}}

Definition at line 7 of file graph_ir_schema.h.

7 {
8 R"IRS({"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"urn:paperkiln:ir:1","title":"paperkiln IR document, version 1","description":"The structure of an IR document (spec/IR_SPEC.md sections 3, 5, 6 and 7). Keys starting with '_' are prose and allowed anywhere. The semantic rules (section 10.1) are checked by tools/ir_validate.py.","type":"object","required":["ir_version","family_hint","model","embed","block","head"],"properties":{"ir_version":{"type":"integer","minimum":0},"family_hint":{"type":"string"},"model":{"$ref":"#/$defs/model"},"tokenizer":{"$ref":"#/$defs/tokenizer"},"inputs":{"type":"array","items":{"anyOf":[{"type":"string"},{"$ref":"#/$defs/io"}]}},"outputs":{"type":"array","items":{"anyOf":[{"type":"string"},{"$ref":"#/$defs/io"}]}},"modes":{"type":"object","additionalProperties":{"type":"string"}},"embed":{"$ref":"#/$defs/section"},"block":{"$ref":"#/$defs/block"},"head":{"$ref":"#/$defs/section"}},"patternProperties":{"^_":true},"additionalProperties":false,"$defs":{"name":{"type":"string","minLength":1},"names":{"type":"array","items":{"$ref":"#/$defs/name"}},"posint":{"type":"integer","minimum":1},"nonneg":{"type":"integer","minimum":0},"per_layer":{"type":"object","required":["per_layer"],"properties":{"per_layer":{"type":"array"}},"patternProperties":{"^_":true},"additionalProperties":false},"apply":{"anyOf":[{"type":"boolean"},{"$ref":"#/$defs/per_layer"}]},"int_or_layers":{"anyOf":[{"$ref":"#/$defs/nonneg"},{"$ref":"#/$defs/per_layer"}]},"num_or_layers":{"anyOf":[{"type":"number"},{"$ref":"#/$defs/per_layer"}]},"io":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"kind":{"type":"string"},"note":{"type":"string"}},"patternProperties":{"^_":true},"additionalProperties":false},"model":{"type":"object","required":["vocab","d","n_layers","ctx"],"properties":{"vocab":{"$ref":"#/$defs/posint"},"d":{"$ref":"#/$defs/posint"},"n_layers":{"$ref":"#/$defs/posint"},"ctx":{"$ref":"#/$defs/posint"},"dtype":{"enum":["f32"]}},"patternProperties":{"^_":true},"additionalProperties":false},"tokenizer":{"type":"object","properties":{"type":{"type":"string"},"source":{"type":"string"},"bos":{"type":["integer","null"]},"specials":{"type":"object","additionalProperties":{"type":"integer"}}},"patternProperties":{"^_":true},"additionalProperties":false},"section":{"type":"object","required":["nodes"],"properties":{"nodes":{"type":"array","items":{"$ref":"#/$defs/node"}}},"patternProperties":{"^_":true},"additionalProperties":false},"block":{"type":"object","required":["repeat","nodes"],"properties":{"repeat":{"$ref":"#/$defs/posint"},"state":{"type":"array","items":{"$ref":"#/$defs/slot"}},"nodes":{"type":"array","items":{"$ref":"#/$defs/node"}},"weight_index":{"$ref":"#/$defs/per_layer"}},"patternProperties":{"^_":true},"additionalProperties":false},"slot":{"type":"object","required":["name","kind"],"properties":{"name":{"$ref":"#/$defs/name"},"kind":{"enum":["kv","recurrent"]}},"allOf":[{"if":{"properties":{"kind":{"const":"kv"}},"required":["kind"]},"then":{"$ref":"#/$defs/slot_kv"}},{"if":{"properties":{"kind":{"const":"recurrent"}},"required":["kind"]},"then":{"$ref":"#/$defs/slot_recurrent"}}]},"slot_kv":{"type":"object","required":["name","kind","heads","kv_heads","head_dim","max_len","window","sinks"],"properties":{"name":true,"kind":true,"heads":{"anyOf":[{"$ref":"#/$defs/posint"},{"$ref":"#/$defs/per_layer"}]},"kv_heads":{"anyOf":[{"$ref":"#/$defs/posint"},{"$ref":"#/$defs/per_layer"}]},"head_dim":{"anyOf":[{"$ref":"#/$defs/posint"},{"$ref":"#/$defs/per_layer"}]},"max_len":{"anyOf":[{"$ref":"#/$defs/posint"},{"$ref":"#/$defs/per_layer"}]},"window":{"$ref":"#/$defs/int_or_layers"},"sinks":{"$ref":"#/$defs/int_or_layers"},"dtype":{"enum":["f32"]},"evict":{"enum":["turn_state"]},"kv_rows":{"enum":["active"]},"kv_from":true},"patternProperties":{"^_":true},"additionalProperties":false},"slot_recurrent":{"type":"object","required":["name","kind","op","dtype","tensors"],"properties":{"nam)IRS",
9 R"IRS(e":true,"kind":true,"op":{"enum":["linear_attention","ssm_scan","conv1d_causal","selective_scan","recurrence"]},"dtype":{"enum":["f32"]},"tensors":{"type":"array","minItems":1,"items":{"type":"object","required":["name","shape","init"],"properties":{"name":{"$ref":"#/$defs/name"},"shape":{"type":"array","minItems":1,"items":{"$ref":"#/$defs/posint"}},"init":{"enum":[0,"body"]}},"patternProperties":{"^_":true},"additionalProperties":false}}},"patternProperties":{"^_":true},"additionalProperties":false},"node":{"type":"object","required":["op","in","out"],"properties":{"op":{"enum":["embedding","pos_embedding","linear","scale","split","rmsnorm","layernorm","gelu","silu","sigmoid","relu","add","mul","sub","affine","mul_col","rms_row","rope","attention","moe_ffn","list","append","depth_attend","expr","linear_attention","ssm_scan","conv1d_causal","selective_scan","depth_route","recurrence","mla_attention"]},"in":{"$ref":"#/$defs/names"}},"allOf":[{"if":{"properties":{"op":{"const":"embedding"}}},"then":{"$ref":"#/$defs/op_embedding"}},{"if":{"properties":{"op":{"const":"pos_embedding"}}},"then":{"$ref":"#/$defs/op_weight_only"}},{"if":{"properties":{"op":{"const":"linear"}}},"then":{"$ref":"#/$defs/op_linear"}},{"if":{"properties":{"op":{"const":"scale"}}},"then":{"$ref":"#/$defs/op_scale"}},{"if":{"properties":{"op":{"const":"split"}}},"then":{"$ref":"#/$defs/op_split"}},{"if":{"properties":{"op":{"const":"rmsnorm"}}},"then":{"$ref":"#/$defs/op_rmsnorm"}},{"if":{"properties":{"op":{"const":"layernorm"}}},"then":{"$ref":"#/$defs/op_layernorm"}},{"if":{"properties":{"op":{"const":"gelu"}}},"then":{"$ref":"#/$defs/op_gelu"}},{"if":{"properties":{"op":{"enum":["silu","sigmoid","relu","add","mul","sub","mul_col","list","append"]}}},"then":{"$ref":"#/$defs/op_plain"}},{"if":{"properties":{"op":{"const":"affine"}}},"then":{"$ref":"#/$defs/op_affine"}},{"if":{"properties":{"op":{"const":"rms_row"}}},"then":{"$ref":"#/$defs/op_rms_row"}},{"if":{"properties":{"op":{"const":"rope"}}},"then":{"$ref":"#/$defs/op_rope"}},{"if":{"properties":{"op":{"const":"attention"}}},"then":{"$ref":"#/$defs/op_attention"}},{"if":{"properties":{"op":{"const":"moe_ffn"}}},"then":{"$ref":"#/$defs/op_moe_ffn"}},{"if":{"properties":{"op":{"const":"depth_attend"}}},"then":{"$ref":"#/$defs/op_depth_attend"}},{"if":{"properties":{"op":{"const":"expr"}}},"then":{"$ref":"#/$defs/op_expr"}},{"if":{"properties":{"op":{"const":"linear_attention"}}},"then":{"$ref":"#/$defs/op_linear_attention"}},{"if":{"properties":{"op":{"const":"ssm_scan"}}},"then":{"$ref":"#/$defs/op_ssm_scan"}},{"if":{"properties":{"op":{"const":"conv1d_causal"}}},"then":{"$ref":"#/$defs/op_conv1d_causal"}},{"if":{"properties":{"op":{"const":"selective_scan"}}},"then":{"$ref":"#/$defs/op_selective_scan"}},{"if":{"properties":{"op":{"const":"depth_route"}}},"then":{"$ref":"#/$defs/op_depth_route"}},{"if":{"properties":{"op":{"const":"recurrence"}}},"then":{"$ref":"#/$defs/op_recurrence"}},{"if":{"properties":{"op":{"const":"mla_attention"}}},"then":{"$ref":"#/$defs/op_mla_attention"}}]},"op_embedding":{"required":["weight"],"properties":{"op":true,"in":true,"apply":{"$ref":"#/$defs/apply"},"out":{"$ref":"#/$defs/name"},"tensor_index":{"enum":["application"]},"weight":{"$ref":"#/$defs/name"},"state_weight":{"$ref":"#/$defs/name"}},"patternProperties":{"^_":true},"additionalProperties":false},"op_weight_only":{"required":["weight"],"properties":{"op":true,"in":true,"apply":{"$ref":"#/$defs/apply"},"out":{"$ref":"#/$defs/name"},"tensor_index":{"enum":["application"]},"weight":{"$ref":"#/$defs/name"}},"patternProperties":{"^_":true},"additionalProperties":false},"op_linear":{"required":["weight"],"properties":{"op":true,"in":true,"apply":{"$ref":"#/$defs/apply"},"out":{"$ref":"#/$defs/name"},"tensor_index":{"enum":["application"]},"weight":{"$ref":"#/$defs/name"},"bias":{"type":["string","null"]},"tied_to":{"$ref":"#/$defs/name"}},"patternProperties":{"^_":true},"additionalProperties":false},"op_scale":{"r)IRS",

◆ patternProperties

const char* const ember_ir::patternProperties

Definition at line 9 of file graph_ir_schema.h.