char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
aee56b3ab
char/aliceai-t5-moe
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 ) { 24num_inputs_check (context ,2 ,2 ); 25 26Output < Node > res ; 27auto data = process_view_input_new (context ,0 ); 28 29auto op_case = context .get_op_case (); 30 ov::Output < ov::Node > indices ; 31if ((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. 35auto s_copy = context .get_input (1 ); 36auto len = context .get_input ("s_copy_active_slot_len" ); 37auto step = ov::op::v0::Constant ::create (ov::element::i64 , {1 }, {1 }); 38auto axis = ov::op::v0::Constant ::create (ov::element::i64 , {1 }, {3 }); 39if (op_case == 1 ) { 40auto begin = ov::op::v0::Constant ::create (ov::element::i64 , {1 }, {0 }); 41indices = std::make_shared < ov::op::v8::Slice > (s_copy ,begin ,len ,step ,axis ); 42 }else { 43auto end = ov::op::v0::Constant ::create (ov::element::i64 , {1 }, {INT_MAX }); 44indices = std::make_shared < ov::op::v8::Slice > (s_copy ,len ,end ,step ,axis ); 45 } 46 }else { 47indices = 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 52indices = 53 std::make_shared < ov::op::v0::Squeeze > (indices , ov::op::v0::Constant ::create (ov::element::i64 , {2 }, {0 ,1 })); 54if (data .get_partial_shape ().rank ()== 4 ) { 55if (!(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 57data = std::make_shared < ov::op::v0::Squeeze > (data , 58 ov::op::v0::Constant ::create (ov::element::i64 , {2 }, {0 ,1 })); 59auto axis = ov::op::v0::Constant ::create (ov::element::i32 , ov::Shape {}, {0 }); 60res = std::make_shared < ov::op::v8::Gather > (data ,indices ,axis ); 61 }else { 62auto axis = ov::op::v0::Constant ::create (ov::element::i32 , ov::Shape {}, {1 }); 63data = 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. 71const auto data_ps = data .get_partial_shape (); 72const auto idx_ps = indices .get_partial_shape (); 73const bool data_batch_static = data_ps .rank ().is_static ()&& data_ps [0 ].is_static (); 74const bool idx_batch_dynamic = idx_ps .rank ().is_dynamic ()|| idx_ps [0 ].is_dynamic (); 75 76if (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. 90auto zero = ov::op::v0::Constant ::create (ov::element::i64 , {1 }, {0 }); 91auto one = ov::op::v0::Constant ::create (ov::element::i64 , {1 }, {1 }); 92auto axis0 = ov::op::v0::Constant ::create (ov::element::i64 , {1 }, {0 }); 93auto data_b1 = std::make_shared < ov::op::v8::Slice > (data ,zero ,one ,one ,axis0 );// [1, rows, ...] 94 95auto idx_shape = std::make_shared < ov::op::v3::ShapeOf > (indices , ov::element::i64 ); 96auto idx_batch = get_dimensions (idx_shape , {0 });// [batch] (dynamic) 97auto data_b1_shape = std::make_shared < ov::op::v3::ShapeOf > (data_b1 , ov::element::i64 ); 98const auto rank = data_ps .rank ().get_length (); 99 std::vector < int > rest_axes ; 100for (int a = 1 ;a < rank ;++ a ) { 101rest_axes .push_back (a ); 102 } 103auto data_rest = get_dimensions (data_b1_shape ,rest_axes );// [rows, ...] 104auto data_target = std::make_shared < ov::op::v0::Concat > (ov::OutputVector {idx_batch ,data_rest },0 ); 105data = 106 std::make_shared < ov::op::v3::Broadcast > (data_b1 ,data_target , ov::op::BroadcastType ::BIDIRECTIONAL ); 107res = 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]. 112auto data_shape = std::make_shared < ov::op::v3::ShapeOf > (data , ov::element::i64 ); 113auto data_batch = get_dimensions (data_shape , {0 });// [batch] 114auto idx_shape = std::make_shared < ov::op::v3::ShapeOf > (indices , ov::element::i64 ); 115auto idx_n = get_dimensions (idx_shape , {1 });// [n] 116auto idx_target = std::make_shared < ov::op::v0::Concat > (ov::OutputVector {data_batch ,idx_n },0 ); 117indices = std::make_shared < ov::op::v3::Broadcast > (indices ,idx_target , 118 ov::op::BroadcastType ::BIDIRECTIONAL ); 119res = 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 ) { 123auto axis = ov::op::v0::Constant ::create (ov::element::i32 , ov::Shape {}, {1 }); 124res = std::make_shared < ov::op::v8::Gather > (data ,indices ,axis ,1 ); 125 }else { 126auto axis = ov::op::v0::Constant ::create (ov::element::i32 , ov::Shape {}, {0 }); 127res = std::make_shared < ov::op::v8::Gather > (data ,indices ,axis ); 128 } 129 130if (res .get_element_type ()!= context .get_output_type ()) { 131res = std::make_shared < ov::op::v0::Convert > (res ,context .get_output_type ()); 132 } 133if (!(context .is_stateful ())) { 134res = std::make_shared < ov::op::v0::Unsqueeze > (res , ov::op::v0::Constant ::create (ov::element::i64 , {1 }, {0 })); 135 } 136return rename_outputs_with_suffix ({res },context .get_name ()); 137} 138 139}// namespace op 140}// namespace ggml 141}// namespace frontend 142}// namespace ov