#include "models.h" #include "llama-ext.h" #include void llama_model_vllm_decision2::load_arch_hparams(llama_model_loader & ml) { llama_model_qwen3::load_arch_hparams(ml); // used by the head ml.get_key(LLM_KV_ATTENTION_LAYERNORM_EPS, hparams.f_norm_eps); // the output is one score per token, see llama_batch_ext_set_decision_order() hparams.n_embd_out_impl = 1; } void llama_model_vllm_decision2::load_arch_tensors(llama_model_loader & ml) { llama_model_qwen3::load_arch_tensors(ml); LLAMA_LOAD_LOCALS; const auto * w_key = ml.get_weight(tn(LLM_TENSOR_DECISION_KEY, "weight").str().c_str()); if (w_key == nullptr) { throw std::runtime_error("the decision head is missing"); } const int64_t n_embd_h = w_key->tensor->ne[1]; option_norm = create_tensor(tn(LLM_TENSOR_DECISION_OPTION_NORM, "weight"), {n_embd}, 0); option_norm_b = create_tensor(tn(LLM_TENSOR_DECISION_OPTION_NORM, "bias"), {n_embd}, 0); query_norm = create_tensor(tn(LLM_TENSOR_DECISION_QUERY_NORM, "weight"), {n_embd}, 0); query_norm_b = create_tensor(tn(LLM_TENSOR_DECISION_QUERY_NORM, "bias"), {n_embd}, 0); key = create_tensor(tn(LLM_TENSOR_DECISION_KEY, "weight"), {n_embd, n_embd_h}, 0); query = create_tensor(tn(LLM_TENSOR_DECISION_QUERY, "weight"), {n_embd, n_embd_h}, 0); option_mlp = create_tensor(tn(LLM_TENSOR_DECISION_OPTION_MLP, "weight"), {n_embd, n_embd_h}, 0); option_mlp_b = create_tensor(tn(LLM_TENSOR_DECISION_OPTION_MLP, "bias"), {n_embd_h}, 0); query_mlp = create_tensor(tn(LLM_TENSOR_DECISION_QUERY_MLP, "weight"), {n_embd, n_embd_h}, 0); scorer_out = create_tensor(tn(LLM_TENSOR_DECISION_SCORER_OUT, "weight"), {n_embd_h, 1}, 0); } std::unique_ptr llama_model_vllm_decision2::build_arch_graph(const llm_graph_params & params) const { return std::make_unique(*this, params); } // output rows read by the head: the last token of each option span and the last token of the question span struct decision2_rows { std::vector options; int32_t query = 0; bool valid = false; }; // see llama_batch_ext_set_decision_order() // if the batch has no usable order, returns one option at row 0 static decision2_rows decision2_get_rows(const llama_ubatch & ubatch) { decision2_rows res; const int32_t n_tokens = ubatch.n_tokens; bool ok = ubatch.decision_order != nullptr && ubatch.n_seqs_unq == 1; // output row of each token, -1 if the token has no output std::vector rows(n_tokens, -1); for (int32_t i = 0, n = 0; i < n_tokens; i++) { if (ubatch.output[i]) { rows[i] = n++; } } int32_t n_questions = 0; for (int32_t i = 0; ok && i < n_tokens;) { const int32_t order = ubatch.decision_order[i]; int32_t end = i + 1; while (end < n_tokens && ubatch.decision_order[end] == order) { end++; } switch (order) { case LLAMA_DECISION_ORDER_NONE: break; case LLAMA_DECISION_ORDER_QUESTION_NOUL: case LLAMA_DECISION_ORDER_QUESTION_CHOICE: case LLAMA_DECISION_ORDER_QUESTION_SCORE: res.query = rows[end - 1]; n_questions++; break; case LLAMA_DECISION_ORDER_OPTION: res.options.push_back(rows[end - 1]); break; default: ok = false; } ok = ok && (order == LLAMA_DECISION_ORDER_NONE || rows[end - 1] >= 0); i = end; } res.valid = ok && n_questions == 1 && !res.options.empty(); if (!res.valid) { res.options.assign(1, 0); res.query = 0; } return res; } class llama_model_vllm_decision2::input_decision : public llm_graph_input_i { public: input_decision(const llama_ubatch & ubatch) : n_options(decision2_get_rows(ubatch).options.size()) {} void set_input(const llama_ubatch * ubatch) override { const auto rows = decision2_get_rows(*ubatch); GGML_ASSERT(rows.options.size() == n_options); // the scores are NaN if the batch has a decision order that cannot be used const float status_data = rows.valid || ubatch->decision_order == nullptr ? 0.0f : NAN; ggml_backend_tensor_set(options, rows.options.data(), 0, ggml_nbytes(options)); ggml_backend_tensor_set(query, &rows.query, 0, ggml_nbytes(query)); ggml_backend_tensor_set(status, &status_data, 0, ggml_nbytes(status)); } bool can_reuse(const llm_graph_params & params) override { // the values are computed again in set_input(), only the shapes must match return decision2_get_rows(params.ubatch).options.size() == n_options; } ggml_tensor * options = nullptr; // I32 [n_options] ggml_tensor * query = nullptr; // I32 [1] ggml_tensor * status = nullptr; // F32 [1], added to the scores: 0, or NaN on invalid input const size_t n_options; }; llama_model_vllm_decision2::graph::graph(const llama_model & model_base, const llm_graph_params & params) : llama_model_qwen3::graph(params, no_build()), model(static_cast(model_base)) { auto inp_decision_ptr = std::make_unique(ubatch); auto * inp_decision = inp_decision_ptr.get(); res->add_input(std::move(inp_decision_ptr)); ggml_tensor * cur = build_backbone(model); // the head is always evaluated, so that the graph has the same nodes for every batch cur = build_head(cur, inp_decision); // row i of the output is the score of option i cur = ggml_pad(ctx0, cur, 0, n_outputs - cur->ne[1], 0, 0); cur = ggml_add(ctx0, cur, inp_decision->status); cb(cur, "result_decision", -1); res->t_embd = cur; ggml_build_forward_expand(gf, cur); } ggml_tensor * llama_model_vllm_decision2::graph::build_head(ggml_tensor * hidden, input_decision * inp) { inp->options = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, inp->n_options); inp->query = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, 1); inp->status = ggml_new_tensor_1d(ctx0, GGML_TYPE_F32, 1); for (ggml_tensor * t : { inp->options, inp->query, inp->status }) { ggml_set_input(t); } ggml_tensor * options = ggml_get_rows(ctx0, hidden, inp->options); // [n_embd, n_options] ggml_tensor * query = ggml_get_rows(ctx0, hidden, inp->query); // [n_embd, 1] options = build_norm(options, model.option_norm, model.option_norm_b, LLM_NORM, -1); query = build_norm(query, model.query_norm, model.query_norm_b, LLM_NORM, -1); // scaled dot product of the option key and the query ggml_tensor * k = ggml_mul_mat(ctx0, model.key, options); // [n_embd_h, n_options] ggml_tensor * q = ggml_mul_mat(ctx0, model.query, query); // [n_embd_h, 1] ggml_tensor * bilinear = ggml_sum_rows(ctx0, ggml_mul(ctx0, k, q)); bilinear = ggml_scale(ctx0, bilinear, 1.0f / sqrtf((float) k->ne[0])); // [1, n_options] ggml_tensor * mlp = ggml_add(ctx0, ggml_mul_mat(ctx0, model.option_mlp, options), model.option_mlp_b); mlp = ggml_add(ctx0, mlp, ggml_mul_mat(ctx0, model.query_mlp, query)); mlp = ggml_gelu_erf(ctx0, mlp); ggml_tensor * nonlinear = ggml_mul_mat(ctx0, model.scorer_out, mlp); // [1, n_options] ggml_tensor * scores = ggml_add(ctx0, bilinear, nonlinear); cb(scores, "decision_scores", -1); return scores; }