char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Zijun YuOpenVINO: Qwen3.5, memory optimization, and test-recurrent-state-rollback (#26952)aee56b3ab

char/aliceai-t5-moe
4.7 KiB108 linesraw
1#include "../node_context.h"
2#include "../op_table.h"
3#include "../utils.h"
4
5#include <openvino/op/broadcast.hpp>
6#include <openvino/op/constant.hpp>
7#include <openvino/op/divide.hpp>
8#include <openvino/op/gather.hpp>
9#include <openvino/op/loop.hpp>
10#include <openvino/op/matmul.hpp>
11#include <openvino/op/scatter_update.hpp>
12#include <openvino/op/shape_of.hpp>
13#include <openvino/op/subtract.hpp>
14
15namespace ov {
16namespace frontend {
17namespace ggml {
18namespace op {
19
20// GGML SOLVE_TRI: solve Ax = B for lower-triangular A via forward substitution.
21// Currently only lower, right, non-unitriangular variant is implemented.
22//
23// ggml layout: A [n, n, B1, B2], B [k, n, B1, B2] → X [k, n, B1, B2]
24// OV layout:   A [B2, B1, n, n], B [B2, B1, n, k] → X [B2, B1, n, k]
25//
26// Forward substitution row i:
27//   x[i] = (b[i] - sum_{t<i} A[i,t]*x[t]) / A[i,i]
28//
29// Implemented as an OV Loop op iterating n times with a carried X accumulator.
30// Key insight: A is lower-triangular and X starts as zeros, so the full matmul
31//   A_row_i @ X_partial = sum_{t<i} A[i,t]*x[t] exactly (upper triangle of A
32//   is zero; unfilled rows of X are zero).
33OutputVector translate_solve_tri(const NodeContext & context) {
34    num_inputs_check(context, 2, 2);
35
36    auto A = context.get_input(0);  // [B2, B1, n, n]
37    auto B = context.get_input(1);  // [B2, B1, n, k]
38
39    auto A_shape = context.get_input_shape(0).to_shape();
40    int64_t n = static_cast<int64_t>(A_shape[2]);
41
42    // Initial X: zeros with shape of B
43    auto B_shape_node = std::make_shared<ov::op::v3::ShapeOf>(B, ov::element::i64);
44    auto zero_f32  = ov::op::v0::Constant::create(ov::element::f32, {}, {0.0f});
45    auto X_init    = std::make_shared<ov::op::v3::Broadcast>(zero_f32, B_shape_node);
46
47    // --- Loop body parameters ---
48    // body_iter: iteration counter injected by the Loop op (i64, shape {1})
49    auto body_iter = std::make_shared<ov::op::v0::Parameter>(ov::element::i64, ov::Shape{1});
50    auto body_X    = std::make_shared<ov::op::v0::Parameter>(ov::element::f32, ov::PartialShape::dynamic(4));
51    auto body_A    = std::make_shared<ov::op::v0::Parameter>(ov::element::f32, ov::PartialShape::dynamic(4));
52    auto body_B_p  = std::make_shared<ov::op::v0::Parameter>(ov::element::f32, ov::PartialShape::dynamic(4));
53
54    auto c_axis2        = ov::op::v0::Constant::create(ov::element::i64, {1}, {int64_t(2)});
55    auto c_axis3        = ov::op::v0::Constant::create(ov::element::i64, {1}, {int64_t(3)});
56    auto c_axis2_scalar = ov::op::v0::Constant::create(ov::element::i64, {}, {int64_t(2)});
57
58    // b_i      = B[..., i, :]      [B2, B1, 1, k]
59    auto b_i = std::make_shared<ov::op::v8::Gather>(body_B_p, body_iter, c_axis2);
60
61    // A_row_i  = A[..., i, :]      [B2, B1, 1, n]
62    auto A_row_i = std::make_shared<ov::op::v8::Gather>(body_A, body_iter, c_axis2);
63
64    // sum_i    = A_row_i @ X       [B2, B1, 1, k]
65    // (lower-tri zeros + unfilled-X zeros make this equal to the partial sum)
66    auto sum_i = std::make_shared<ov::op::v0::MatMul>(A_row_i, body_X, false, false);
67
68    // diag_i   = A[..., i, i]      [B2, B1, 1, 1]
69    auto diag_i = std::make_shared<ov::op::v8::Gather>(A_row_i, body_iter, c_axis3);
70
71    // x_i      = (b_i - sum_i) / diag_i    [B2, B1, 1, k]
72    auto x_i = std::make_shared<ov::op::v1::Divide>(
73        std::make_shared<ov::op::v1::Subtract>(b_i, sum_i), diag_i);
74
75    // X_updated: scatter x_i into body_X at row i along axis 2
76    auto X_updated = std::make_shared<ov::op::v3::ScatterUpdate>(body_X, body_iter, x_i, c_axis2_scalar);
77
78    auto body_cond = ov::op::v0::Constant::create(ov::element::boolean, ov::Shape{1}, {true});
79
80    auto body = std::make_shared<ov::Model>(
81        ov::OutputVector{body_cond, X_updated},
82        ov::ParameterVector{body_iter, body_X, body_A, body_B_p});
83
84    // --- Assemble Loop ---
85    auto trip_count = ov::op::v0::Constant::create(ov::element::i64, ov::Shape{1}, std::vector<int64_t>{n});
86    auto exec_cond  = ov::op::v0::Constant::create(ov::element::boolean, ov::Shape{1}, {true});
87
88    auto loop = std::make_shared<ov::op::v5::Loop>(trip_count, exec_cond);
89    loop->set_function(body);
90    // iter_counter_body_param_idx=0 (body_iter), exec_condition_body_result_idx=0 (body_cond)
91    loop->set_special_body_ports(ov::op::v5::Loop::SpecialBodyPorts{0, 0});
92
93    // Carried state: X feeds back from X_updated each iteration
94    loop->set_merged_input(body_X, X_init, X_updated);
95    // Invariant inputs passed through unchanged
96    loop->set_invariant_input(body_A,   A);
97    loop->set_invariant_input(body_B_p, B);
98
99    // Final output: value of X_updated after the last iteration
100    auto X_final = loop->get_iter_value(X_updated, -1);
101
102    return rename_outputs_with_suffix({X_final}, context.get_name());
103}
104
105}  // namespace op
106}  // namespace ggml
107}  // namespace frontend
108}  // namespace ov