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
8.1 KiB142 linesraw
1#include "../node_context.h"
2#include "../op_table.h"
3#include "../utils.h"
4
5#include <climits>
6#include <openvino/core/node.hpp>
7#include <openvino/core/node_output.hpp>
8#include <openvino/op/broadcast.hpp>
9#include <openvino/op/concat.hpp>
10#include <openvino/op/constant.hpp>
11#include <openvino/op/convert.hpp>
12#include <openvino/op/gather.hpp>
13#include <openvino/op/shape_of.hpp>
14#include <openvino/op/slice.hpp>
15#include <openvino/op/squeeze.hpp>
16#include <openvino/op/unsqueeze.hpp>
17
18namespace ov {
19namespace frontend {
20namespace ggml {
21namespace op {
22
23OutputVector translate_get_rows(const NodeContext & context) {
24    num_inputs_check(context, 2, 2);
25
26    Output<Node> res;
27    auto data = process_view_input_new(context, 0);
28
29    auto op_case = context.get_op_case();
30    ov::Output<ov::Node> indices;
31    if ((op_case == 1 || op_case == 2) && context.has_input("s_copy_active_slot_len")) {
32        // Recurrent state reorder (inp->s_copy): slice the active (op_case 1) or extra (op_case 2)
33        // segment from the s_copy index list at runtime, instead of baking the static view offset,
34        // so the cached IR works for any number of active sequences.
35        auto s_copy = context.get_input(1);
36        auto len = context.get_input("s_copy_active_slot_len");
37        auto step = ov::op::v0::Constant::create(ov::element::i64, {1}, {1});
38        auto axis = ov::op::v0::Constant::create(ov::element::i64, {1}, {3});
39        if (op_case == 1) {
40            auto begin = ov::op::v0::Constant::create(ov::element::i64, {1}, {0});
41            indices = std::make_shared<ov::op::v8::Slice>(s_copy, begin, len, step, axis);
42        } else {
43            auto end = ov::op::v0::Constant::create(ov::element::i64, {1}, {INT_MAX});
44            indices = std::make_shared<ov::op::v8::Slice>(s_copy, len, end, step, axis);
45        }
46    } else {
47        indices = process_view_input_new(context, 1);
48    }
49
50    // data[1,b,x,y] ind[1,1,b,x'] test-backend-ops case
51    // data[x,y] ind[1,1,1,x'] normal case
52    indices =
53        std::make_shared<ov::op::v0::Squeeze>(indices, ov::op::v0::Constant::create(ov::element::i64, {2}, {0, 1}));
54    if (data.get_partial_shape().rank() == 4) {
55        if (!(data.get_partial_shape()[1].is_dynamic()) && data.get_partial_shape()[1].get_length() == 1) {
56            // Work-around for a bug in ov cpu plugin for test-backend-ops
57            data = std::make_shared<ov::op::v0::Squeeze>(data,
58                                                         ov::op::v0::Constant::create(ov::element::i64, {2}, {0, 1}));
59            auto axis = ov::op::v0::Constant::create(ov::element::i32, ov::Shape{}, {0});
60            res = std::make_shared<ov::op::v8::Gather>(data, indices, axis);
61        } else {
62            auto axis = ov::op::v0::Constant::create(ov::element::i32, ov::Shape{}, {1});
63            data =
64                std::make_shared<ov::op::v0::Squeeze>(data, ov::op::v0::Constant::create(ov::element::i64, {1}, {0}));
65            // data: [batch, rows, ...], indices: [batch, n] - this is a batched gather
66            // (batch_dims=1) along the rows axis. The data and indices batch dims are
67            // logically equal (both == n_tokens) but reach this node through independent
68            // reshapes, so the GPU plugin's gather shape inference cannot prove
69            // data.shape[0] == indices.shape[0] and rejects the node. We must tie both
70            // batch dims to the SAME value, and crucially that value must stay DYNAMIC.
71            const auto data_ps = data.get_partial_shape();
72            const auto idx_ps = indices.get_partial_shape();
73            const bool data_batch_static = data_ps.rank().is_static() && data_ps[0].is_static();
74            const bool idx_batch_dynamic = idx_ps.rank().is_dynamic() || idx_ps[0].is_dynamic();
75
76            if (data_batch_static && idx_batch_dynamic) {
77                // MoE per-expert-scale path: `data` is a statically-tiled REPEAT
78                // (ggml_repeat_4d(scale, 1, n_expert, n_tokens, 1)) whose batch dim is a
79                // compile-time-constant n_tokens, and every batch slice is IDENTICAL (it was
80                // tiled from a single [1, n_expert, 1] scale). `indices` (selected_experts)
81                // carries the genuinely dynamic token dim. Broadcasting indices up to the
82                // static data batch (the naive fix) would freeze the token dim to the
83                // captured prefill length, and that static value then flows through the
84                // gather into the residual stream, making every following decoder layer
85                // static -> triggers the GPU in-place-concat KV-cache corruption (only
86                // layer 0 stays dynamic). A static->dynamic Broadcast cannot expand, so
87                // instead collapse the redundant data batch to 1 and broadcast 1->dynamic to
88                // match the indices batch. Mathematically identical (the slices are equal),
89                // and the whole graph stays dynamic.
90                auto zero = ov::op::v0::Constant::create(ov::element::i64, {1}, {0});
91                auto one = ov::op::v0::Constant::create(ov::element::i64, {1}, {1});
92                auto axis0 = ov::op::v0::Constant::create(ov::element::i64, {1}, {0});
93                auto data_b1 = std::make_shared<ov::op::v8::Slice>(data, zero, one, one, axis0);  // [1, rows, ...]
94
95                auto idx_shape = std::make_shared<ov::op::v3::ShapeOf>(indices, ov::element::i64);
96                auto idx_batch = get_dimensions(idx_shape, {0});  // [batch] (dynamic)
97                auto data_b1_shape = std::make_shared<ov::op::v3::ShapeOf>(data_b1, ov::element::i64);
98                const auto rank = data_ps.rank().get_length();
99                std::vector<int> rest_axes;
100                for (int a = 1; a < rank; ++a) {
101                    rest_axes.push_back(a);
102                }
103                auto data_rest = get_dimensions(data_b1_shape, rest_axes);  // [rows, ...]
104                auto data_target = std::make_shared<ov::op::v0::Concat>(ov::OutputVector{idx_batch, data_rest}, 0);
105                data =
106                    std::make_shared<ov::op::v3::Broadcast>(data_b1, data_target, ov::op::BroadcastType::BIDIRECTIONAL);
107                res = std::make_shared<ov::op::v8::Gather>(data, indices, axis, 1);
108            } else {
109                // General case: tie the indices batch to the data batch (the data batch is
110                // already dynamic, e.g. the routing-weights gather whose data comes from the
111                // activations). Broadcast indices to [data_batch, indices_n].
112                auto data_shape = std::make_shared<ov::op::v3::ShapeOf>(data, ov::element::i64);
113                auto data_batch = get_dimensions(data_shape, {0});  // [batch]
114                auto idx_shape = std::make_shared<ov::op::v3::ShapeOf>(indices, ov::element::i64);
115                auto idx_n = get_dimensions(idx_shape, {1});  // [n]
116                auto idx_target = std::make_shared<ov::op::v0::Concat>(ov::OutputVector{data_batch, idx_n}, 0);
117                indices = std::make_shared<ov::op::v3::Broadcast>(indices, idx_target,
118                                                                  ov::op::BroadcastType::BIDIRECTIONAL);
119                res = std::make_shared<ov::op::v8::Gather>(data, indices, axis, 1);
120            }
121        }
122    } else if (context.is_stateful() && data.get_partial_shape().rank() == 3) {
123        auto axis = ov::op::v0::Constant::create(ov::element::i32, ov::Shape{}, {1});
124        res = std::make_shared<ov::op::v8::Gather>(data, indices, axis, 1);
125    } else {
126        auto axis = ov::op::v0::Constant::create(ov::element::i32, ov::Shape{}, {0});
127        res = std::make_shared<ov::op::v8::Gather>(data, indices, axis);
128    }
129
130    if (res.get_element_type() != context.get_output_type()) {
131        res = std::make_shared<ov::op::v0::Convert>(res, context.get_output_type());
132    }
133    if (!(context.is_stateful())) {
134        res = std::make_shared<ov::op::v0::Unsqueeze>(res, ov::op::v0::Constant::create(ov::element::i64, {1}, {0}));
135    }
136    return rename_outputs_with_suffix({res}, context.get_name());
137}
138
139}  // namespace op
140}  // namespace ggml
141}  // namespace frontend
142}  // namespace ov