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 <cstdint> 6#include <memory> 7#include <openvino/core/node.hpp> 8#include <openvino/core/node_output.hpp> 9#include <openvino/op/add.hpp> 10#include <openvino/op/broadcast.hpp> 11#include <openvino/op/concat.hpp> 12#include <openvino/op/constant.hpp> 13#include <openvino/op/convert.hpp> 14#include <openvino/op/cos.hpp> 15#include <openvino/op/gather.hpp> 16#include <openvino/op/multiply.hpp> 17#include <openvino/op/reshape.hpp> 18#include <openvino/op/shape_of.hpp> 19#include <openvino/op/sin.hpp> 20#include <openvino/op/slice.hpp> 21#include <openvino/op/split.hpp> 22#include <openvino/op/subtract.hpp> 23#include <openvino/op/transpose.hpp> 24#include <openvino/op/unsqueeze.hpp> 25#include <openvino/op/variadic_split.hpp> 26#include <vector> 27 28namespace ov { 29namespace frontend { 30namespace ggml { 31namespace op { 32 33OutputVector translate_rope (const NodeContext & context ) { 34num_inputs_check (context ,2 ,3 ); 35 36int op_case = context .get_op_case (); 37 38 ov::Output < Node > res ; 39 40auto data_node = context .get_input (0 ).get_node_shared_ptr (); 41auto output_shape = context .get_output_shape ().to_shape (); 42int32_t * op_params = context .get_output_op_params (); 43const int mode = op_case ; 44const int64_t head_dim = static_cast < int64_t > (output_shape [3 ]); 45const int64_t configured_n_dims = static_cast < int64_t > (op_params [1 ]); 46const int64_t n_dims = configured_n_dims == 0 ?head_dim :configured_n_dims ; 47 48constexpr int TYPE_NORMAL = 0 ; 49constexpr int TYPE_NEOX = 1 ; 50constexpr int TYPE_IMROPE = 2 ; 51 52Output < Node > cos_theta_node ; 53Output < Node > sin_theta_node ; 54if (context .has_input ("rope_cos" )) { 55cos_theta_node = context .get_input ("rope_cos" ); 56sin_theta_node = context .get_input ("rope_sin" ); 57 }else { 58auto inp_pos = context .get_input (1 ).get_node_shared_ptr (); 59 std::shared_ptr < ov::Node > rope_freqs_weight ; 60if (context .get_input_size ()== 3 ) { 61rope_freqs_weight = context .get_input (2 ).get_node_shared_ptr (); 62 } 63auto sin_cos = make_sin_cos (op_params ,inp_pos ,rope_freqs_weight ,mode == TYPE_IMROPE , false); 64sin_theta_node = sin_cos .first ; 65cos_theta_node = sin_cos .second ; 66 } 67 68if (context .get_view_input_size (0 )> 0 ) { 69data_node = process_view_input_new (context ,0 ).get_node_shared_ptr (); 70if (context .is_stateful ()) { 71auto data_shape = ov::op::v0::Constant ::create ( 72 ov::element::i64 , {3 }, std::vector < int64_t > {-1 , (int64_t )output_shape [2 ], (int64_t )output_shape [3 ]}); 73data_node = std::make_shared < ov::op::v1::Reshape > (data_node ,data_shape , false); 74 }else { 75auto data_shape = ov::op::v0::Constant ::create ( 76 ov::element::i64 , {4 }, 77 std::vector < int64_t > {1 ,-1 , (int64_t )output_shape [2 ], (int64_t )output_shape [3 ]}); 78data_node = std::make_shared < ov::op::v1::Reshape > (data_node ,data_shape , false); 79 } 80 } 81 82auto output_type = context .get_output_type (); 83if (data_node -> get_element_type ()!= ov::element::f32 ) { 84data_node = std::make_shared < ov::op::v0::Convert > (data_node , ov::element::f32 ); 85 } 86 87FRONT_END_OP_CONVERSION_CHECK (n_dims > 0 && n_dims <=head_dim && (n_dims %2 == 0 ), 88"ROPE expects even n_dims in [1, head_dim]" ); 89 90// TODO(openvino-gpu-rope-fusion): TEMPORARY WORKAROUND - do NOT revert until the 91// OpenVINO GPU plugin is updated. 92// 93// For TYPE_NORMAL rope (both stateful and stateless) we emit the Flux-style 94// interleaved pattern below so the GPU plugin's RoPEFusionFlux matcher folds it 95// into ov::op::internal::RoPE. The matcher requires rank-4 inputs, which is why 96// the original even/odd Slice translation (kept in the `else if (mode == 97// TYPE_NORMAL)` branch below for reference) does not get fused. 98// 99// Once the GPU plugin's RoPE fusion is extended to also recognize the original 100// even/odd Slice form, this Flux rewrite should be removed and both modes should 101// be restored to the captured even/odd translation. Until then, keep both paths: 102// the active Flux rewrite here and the previous translation preserved below. 103if (mode == TYPE_NORMAL ) { 104auto axis_last = ov::op::v0::Constant ::create (ov::element::i64 , {1 }, {-1 }); 105auto zero = ov::op::v0::Constant ::create (ov::element::i64 , {1 }, {0 }); 106auto step_one = ov::op::v0::Constant ::create (ov::element::i64 , {1 }, {1 }); 107 108// Emit the Flux-style interleaved-RoPE pattern so the GPU plugin's 109// RoPEFusionFlux matcher folds this subgraph into ov::op::internal::RoPE: 110// x_paired = Reshape(x_rot, [1, S, n_heads, n_dims/2, 2]) 111// x0, x1 = Split(x_paired, axis=-1, num_splits=2) 112// x1_neg = x1 * -1 113// x_rotated = Reshape(Concat([x1_neg, x0], axis=-1), [1, S, n_heads, n_dims]) 114// y_rot = x_rot * t_cos + x_rotated * t_sin 115// y = Concat([y_rot, x_tail], axis=-1) if n_dims < head_dim 116// Mathematically equivalent to the even/odd Slice form below. 117// 118// RoPEFusionFlux requires rank_equals(4) on x, t_cos and t_sin. The cos/sin 119// tables are already built rank-4 ([1, S, 1, head_size/2]) for both modes. In 120// stateful mode the data arrives rank-3 ([S, n_heads, head_size]), so lift it 121// to rank-4 ([1, S, n_heads, head_size]) here. Stateful RoPE already produced 122// rank-4 output, so downstream attention is unaffected. 123if (context .is_stateful ()) { 124auto r4_shape = ov::op::v0::Constant ::create ( 125 ov::element::i64 , {4 }, 126 std::vector < int64_t > {1 ,-1 , (int64_t )output_shape [2 ], (int64_t )output_shape [3 ]}); 127data_node = std::make_shared < ov::op::v1::Reshape > (data_node ,r4_shape , false); 128 } 129const int64_t n_heads = static_cast < int64_t > (output_shape [2 ]); 130const int64_t half = n_dims /2 ; 131auto rot_end = ov::op::v0::Constant ::create (ov::element::i64 , {1 }, {n_dims }); 132auto rot_data = std::make_shared < ov::op::v8::Slice > (data_node ,zero ,rot_end ,step_one ,axis_last ); 133 134auto neg_one_f = ov::op::v0::Constant ::create (data_node -> get_element_type (), ov::Shape {}, {-1.0f }); 135 136auto paired_shape = ov::op::v0::Constant ::create ( 137 ov::element::i64 , {5 }, std::vector < int64_t > {1 ,-1 ,n_heads ,half ,2 }); 138auto x_paired = std::make_shared < ov::op::v1::Reshape > (rot_data ,paired_shape , false); 139 140auto split_axis = ov::op::v0::Constant ::create (ov::element::i64 , ov::Shape {}, {-1 }); 141auto data_split = std::make_shared < ov::op::v1::Split > (x_paired ,split_axis ,2 ); 142Output < Node > x0 = data_split -> outputs ()[0 ]; 143Output < Node > x1 = data_split -> outputs ()[1 ]; 144 145auto x1_neg = std::make_shared < ov::op::v1::Multiply > (x1 ,neg_one_f ); 146auto x_rotated_paired = std::make_shared < ov::op::v0::Concat > (ov::OutputVector {x1_neg ,x0 },-1 ); 147 148auto flat_shape = 149 ov::op::v0::Constant ::create (ov::element::i64 , {4 }, std::vector < int64_t > {1 ,-1 ,n_heads ,n_dims }); 150auto x_rotated = 151 std::make_shared < ov::op::v1::Reshape > (x_rotated_paired ,flat_shape , false); 152 153// Expand cos/sin from [..., n_dims/2] to [..., n_dims] by repeating each 154// entry twice. Use special_zero on the final Reshape so the seq dim passes 155// through dynamically. Final rank is 4 to satisfy the matcher's predicate. 156auto expand_cos_sin = [& ](Output < Node > cs ) { 157auto cs_unsq = std::make_shared < ov::op::v0::Unsqueeze > ( 158cs , ov::op::v0::Constant ::create (ov::element::i64 , {1 }, {-1 })); 159auto bcast_target = ov::op::v0::Constant ::create ( 160 ov::element::i64 , {5 }, std::vector < int64_t > {1 ,1 ,1 ,half ,2 }); 161auto bcast = std::make_shared < ov::op::v3::Broadcast > ( 162cs_unsq ,bcast_target , ov::op::BroadcastType ::BIDIRECTIONAL ); 163auto flat = ov::op::v0::Constant ::create (ov::element::i64 , {4 }, std::vector < int64_t > {0 ,0 ,0 ,n_dims }); 164return std::make_shared < ov::op::v1::Reshape > (bcast ,flat , true); 165 }; 166Output < Node > cos_full = expand_cos_sin (cos_theta_node ); 167Output < Node > sin_full = expand_cos_sin (sin_theta_node ); 168 169auto y1 = std::make_shared < ov::op::v1::Multiply > (rot_data ,cos_full ); 170auto y2 = std::make_shared < ov::op::v1::Multiply > (x_rotated ,sin_full ); 171auto rotated = std::make_shared < ov::op::v1::Add > (y1 ,y2 ); 172 173if (n_dims < head_dim ) { 174auto tail_start = ov::op::v0::Constant ::create (ov::element::i64 , {1 }, {n_dims }); 175auto tail_end = ov::op::v0::Constant ::create (ov::element::i64 , {1 }, {head_dim }); 176auto tail = std::make_shared < ov::op::v8::Slice > (data_node ,tail_start ,tail_end ,step_one ,axis_last ); 177res = std::make_shared < ov::op::v0::Concat > (ov::OutputVector {rotated ,tail },-1 ); 178 }else { 179res = rotated ; 180 } 181 } 182// PRESERVED PREVIOUS TRANSLATION - Re-enable this branch (and remove the Flux branch above) once 183// the GPU plugin's RoPE fusion is updated to recognize the even/odd Slice form; 184// see the TODO(openvino-gpu-rope-fusion) note above. Do not delete. 185// 186// Original even/odd Slice form. In stateless mode it ran on rank-4 data 187// ([1, S, n_heads, head_size]); in stateful mode on rank-3 data 188// ([S, n_heads, head_size]). Either way it does not match RoPEFusionFlux 189// (which needs rank-4 x in the interleaved layout), so the RoPE stays as 190// discrete elementwise ops. 191// 192// } else if (mode == TYPE_NORMAL) { 193// auto neg_one = ov::op::v0::Constant::create(ov::element::i64, {1}, {-1}); 194// auto zero = ov::op::v0::Constant::create(ov::element::i64, {1}, {0}); 195// auto one = ov::op::v0::Constant::create(ov::element::i64, {1}, {1}); 196// auto two = ov::op::v0::Constant::create(ov::element::i64, {1}, {2}); 197// auto end = ov::op::v0::Constant::create(ov::element::i64, {1}, {output_shape[3]}); 198// Output<Node> even_slice; 199// Output<Node> odd_slice; 200// // stateful data is rank 3 (unsqueeze at axis 3), stateless is rank 4 (axis 4) 201// int32_t unsqueeze_dim = context.is_stateful() ? 3 : 4; 202// even_slice = std::make_shared<ov::op::v8::Slice>(data_node, zero, end, two, neg_one); 203// odd_slice = std::make_shared<ov::op::v8::Slice>(data_node, one, end, two, neg_one); 204// 205// Output<Node> first_half = 206// std::make_shared<ov::op::v1::Subtract>(std::make_shared<ov::op::v1::Multiply>(even_slice, cos_theta_node), 207// std::make_shared<ov::op::v1::Multiply>(odd_slice, sin_theta_node)); 208// Output<Node> second_half = 209// std::make_shared<ov::op::v1::Add>(std::make_shared<ov::op::v1::Multiply>(even_slice, sin_theta_node), 210// std::make_shared<ov::op::v1::Multiply>(odd_slice, cos_theta_node)); 211// 212// first_half = std::make_shared<ov::op::v0::Unsqueeze>(first_half, 213// ov::op::v0::Constant::create(ov::element::i64, {1}, {unsqueeze_dim})); 214// second_half = std::make_shared<ov::op::v0::Unsqueeze>(second_half, 215// ov::op::v0::Constant::create(ov::element::i64, {1}, {unsqueeze_dim})); 216// auto stack = std::make_shared<ov::op::v0::Concat>(OutputVector{first_half, second_half}, unsqueeze_dim); 217// 218// auto data_shape = ov::op::v0::Constant::create( 219// ov::element::i64, {4}, std::vector<int64_t>{1, -1, (int64_t) output_shape[2], (int64_t) output_shape[3]}); 220// res = std::make_shared<ov::op::v1::Reshape>(stack, data_shape, false); 221else if (mode == TYPE_NEOX ) { 222// In stateful mode the data arrives rank-3 ([S, n_heads, head_size]) while the 223// cos/sin tables are rank-4 ([1, S, 1, n_dims/2]). The resulting mixed-rank 224// broadcast in the Multiply below is miscomputed by the OpenVINO GPU plugin, 225// corrupting the rotated Q/K. Lift the data to rank-4 ([1, S, n_heads, head_size]) 226// first so the RoPE Multiplies are equal-rank, matching the TYPE_NORMAL branch. 227// Stateful RoPE already produced rank-4 output, so downstream attention is unaffected. 228if (context .is_stateful ()) { 229auto r4_shape = ov::op::v0::Constant ::create ( 230 ov::element::i64 , {4 }, 231 std::vector < int64_t > {1 ,-1 , (int64_t )output_shape [2 ], (int64_t )output_shape [3 ]}); 232data_node = std::make_shared < ov::op::v1::Reshape > (data_node ,r4_shape , false); 233 } 234auto axis_last = ov::op::v0::Constant ::create (ov::element::i64 , ov::Shape {}, {-1 }); 235 std::vector < int64_t > split_lengths = {n_dims /2 ,n_dims /2 }; 236if (n_dims < head_dim ) { 237split_lengths .push_back (head_dim - n_dims ); 238 } 239 240auto data_split = std::make_shared < ov::op::v1::VariadicSplit > ( 241data_node ,axis_last , 242 ov::op::v0::Constant ::create (ov::element::i64 , {split_lengths .size ()},split_lengths )); 243Output < Node > slice_data_node_0 = data_split -> outputs ()[0 ]; 244Output < Node > slice_data_node_1 = data_split -> outputs ()[1 ]; 245 246auto first_half_node = std::make_shared < ov::op::v1::Subtract > ( 247 std::make_shared < ov::op::v1::Multiply > (slice_data_node_0 ,cos_theta_node ), 248 std::make_shared < ov::op::v1::Multiply > (slice_data_node_1 ,sin_theta_node )); 249 250auto second_half_node = std::make_shared < ov::op::v1::Add > ( 251 std::make_shared < ov::op::v1::Multiply > (slice_data_node_0 ,sin_theta_node ), 252 std::make_shared < ov::op::v1::Multiply > (slice_data_node_1 ,cos_theta_node )); 253 254if (n_dims < head_dim ) { 255Output < Node > tail = data_split -> outputs ()[2 ]; 256res = std::make_shared < ov::op::v0::Concat > (ov::OutputVector {first_half_node ,second_half_node ,tail },-1 ); 257 }else { 258res = std::make_shared < ov::op::v0::Concat > (ov::OutputVector {first_half_node ,second_half_node },-1 ); 259 } 260 }else if (mode == TYPE_IMROPE ) { 261auto cos_sin_shape = std::make_shared < ov::op::v0::Constant > (ov::element::i64 , ov::Shape {4 }, 262 std::vector < int64_t > {1 ,-1 ,1 , (n_dims >>1 )}); 263auto cos_reshaped = std::make_shared < ov::op::v1::Reshape > (cos_theta_node ,cos_sin_shape , true); 264auto sin_reshaped = std::make_shared < ov::op::v1::Reshape > (sin_theta_node ,cos_sin_shape , true); 265 266auto split_axis = ov::op::v0::Constant ::create (ov::element::i64 , ov::Shape {}, {3 }); 267 std::vector < int64_t > split_lengths = {n_dims /2 ,n_dims /2 }; 268if (n_dims < head_dim ) { 269split_lengths .push_back (head_dim - n_dims ); 270 } 271 272auto split_a = std::make_shared < ov::op::v1::VariadicSplit > ( 273data_node ,split_axis , 274 ov::op::v0::Constant ::create (ov::element::i64 , {split_lengths .size ()},split_lengths )); 275auto x0 = split_a -> output (0 ); 276auto x1 = split_a -> output (1 ); 277auto mul_a = std::make_shared < ov::op::v1::Multiply > (x0 ,cos_reshaped ); 278auto mul_b = std::make_shared < ov::op::v1::Multiply > (x1 ,sin_reshaped ); 279auto sub = std::make_shared < ov::op::v1::Subtract > (mul_a ,mul_b ); 280 281auto mul_c = std::make_shared < ov::op::v1::Multiply > (x0 ,sin_reshaped ); 282auto mul_d = std::make_shared < ov::op::v1::Multiply > (x1 ,cos_reshaped ); 283auto add = std::make_shared < ov::op::v1::Add > (mul_c ,mul_d ); 284 285if (n_dims < head_dim ) { 286auto tail = split_a -> output (2 ); 287res = std::make_shared < ov::op::v0::Concat > (ov::OutputVector {sub ,add ,tail },3 ); 288 }else { 289res = std::make_shared < ov::op::v0::Concat > (ov::OutputVector {sub ,add },3 ); 290 } 291 } 292 293if (res .get_element_type ()!= output_type ) { 294res = std::make_shared < ov::op::v0::Convert > (res ,output_type ); 295 } 296 297return rename_outputs_with_suffix ({res },context .get_name ()); 298} 299 300}// namespace op 301}// namespace ggml 302}// namespace frontend 303}// namespace ov