char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
aee56b3ab
char/aliceai-t5-moe
1#include "ggml-quants.h" 2 3#include "ggml-common.h" 4#include "ggml-impl.h" 5#include "ggml-openvino-extra.h" 6#include "ggml.h" 7 8#include <algorithm> 9#include <cassert> 10#include <cmath> 11#include <cstddef> 12#include <cstdint> 13#include <limits> 14#include <memory> 15#include <openvino/core/except.hpp> 16#include <openvino/core/node.hpp> 17#include <openvino/core/node_output.hpp> 18#include <openvino/core/parallel.hpp> 19#include <openvino/core/shape.hpp> 20#include <openvino/core/type/element_type.hpp> 21#include <openvino/core/type/element_type_traits.hpp> 22#include <openvino/core/type/float16.hpp> 23#include <openvino/core/type/float4_e2m1.hpp> 24#include <openvino/core/type/float8_e8m0.hpp> 25#include <openvino/op/add.hpp> 26#include <openvino/op/constant.hpp> 27#include <openvino/op/convert.hpp> 28#include <openvino/op/multiply.hpp> 29#include <openvino/op/reshape.hpp> 30#include <openvino/op/subtract.hpp> 31#include <openvino/op/util/attr_types.hpp> 32#include <openvino/pass/constant_folding.hpp> 33#include <openvino/runtime/tensor.hpp> 34#include <string> 35#include <vector> 36 37void unpack_32_4 (const uint8_t * data ,uint8_t * dst ) { 38 std::fill_n (dst ,16 ,0 ); 39for (int j = 0 ;j < 16 ;++ j ) { 40uint8_t x = (data [j ]& 0x0F ); 41uint8_t y = (data [j ] >>4 ); 42if (j %2 != 0 ) { 43x <<=4 ; 44y <<=4 ; 45 } 46dst [j /2 ] |=x ; 47dst [8 + j /2 ] |=y ;// Last 16 weights are in the higher bits 48 } 49} 50 51static constexpr size_t MXFP4_BLOCK_SIZE = 32 ; 52static constexpr size_t MXFP4_BLOCK_QS_SIZE = MXFP4_BLOCK_SIZE /2 ; 53static constexpr size_t MXFP4_BLOCK_BYTES = sizeof (uint8_t )+ MXFP4_BLOCK_QS_SIZE ; 54 55static void pack_32_mxfp4_for_openvino (const uint8_t * data ,uint8_t * dst ) { 56for (int j = 0 ;j < static_cast < int > (MXFP4_BLOCK_QS_SIZE );j += 2 ) { 57const uint8_t v0 = data [j ]& 0x0F ; 58const uint8_t v1 = (data [j + 1 ]& 0x0F ) <<4 ; 59const uint8_t v16 = data [j ] >>4 ; 60const uint8_t v17 = data [j + 1 ]& 0xF0 ; 61dst [j /2 ]= v0 |v1 ; 62dst [MXFP4_BLOCK_SIZE /4 + j /2 ]= v16 |v17 ; 63 } 64} 65 66void extract_mxfp4_data (const ggml_tensor * tensor , ov::Tensor & weights_arr , ov::Tensor & scales_arr ) { 67GGML_ASSERT (tensor -> type == GGML_TYPE_MXFP4 ); 68GGML_ASSERT (weights_arr .get_element_type ()== ov::element::f4e2m1 ); 69GGML_ASSERT (scales_arr .get_element_type ()== ov::element::f8e8m0 ); 70 71const auto * data = static_cast < const uint8_t *> (tensor -> data ); 72auto * weights = static_cast < uint8_t *> (weights_arr .data ()); 73auto * scales = scales_arr .data < ov::element_type_traits < ov::element::f8e8m0 > ::value_type > (); 74const size_t n_blocks = scales_arr .get_size (); 75 76 ov::parallel_for (n_blocks , [& ](size_t i ) { 77const uint8_t * block = data + i * MXFP4_BLOCK_BYTES ; 78pack_32_mxfp4_for_openvino (block + sizeof (uint8_t ),weights + i * MXFP4_BLOCK_QS_SIZE ); 79scales [i ]= ov::float8_e8m0::from_bits (block [0 ]); 80 }); 81} 82 83// Extracts (weight, scales, zp) from Q4_0 tensors. 84// Data layout is: |16 bit scale|32 x 4bit weights|. 85// When zp_arr is empty (symmetric), weights are stored as signed i4 (value - 8). 86void extract_q4_0_data (const ggml_tensor * tensor , 87 ov::Tensor & weights_arr , 88 ov::Tensor & scales_arr , 89 ov::Tensor & zp_arr ) { 90const uint64_t bytes_per_block = 18 ;// 2 bytes scale, 32x0.5 byte weights 91 92auto * data = static_cast < uint8_t *> (tensor -> data ); 93auto * weights = static_cast < uint8_t *> (weights_arr .data ()); 94auto * scales = scales_arr .data < ov::element_type_traits < ov::element::f16 > ::value_type > (); 95 96bool is_symmetric = (weights_arr .get_element_type ()== ov::element::i4 );// Signed i4 path 97 98if (!is_symmetric ) { 99auto * zp = static_cast < uint8_t *> (zp_arr .data ()); 100 ov::parallel_for (scales_arr .get_size (), [& ](size_t i ) { 101scales [i ]= ov::float16::from_bits (* ((uint16_t * ) (data + i * bytes_per_block ))); 102// Pack two 4-bit zero points per byte 103if (i %2 == 0 ) { 104zp [i /2 ]= 8 ;// Lower nibble 105 }else { 106zp [i /2 ] |= (8 <<4 );// Upper nibble 107 } 108unpack_32_4 (data + i * bytes_per_block + 2 ,weights + i * 16 ); 109 }); 110 }else { 111// Symmetric: unpack as u4 then convert to i4 by subtracting 8 (XOR each nibble) 112 ov::parallel_for (scales_arr .get_size (), [& ](size_t i ) { 113scales [i ]= ov::float16::from_bits (* ((uint16_t * ) (data + i * bytes_per_block ))); 114unpack_32_4 (data + i * bytes_per_block + 2 ,weights + i * 16 ); 115// Convert u4 to i4: subtract 8 from each nibble. XOR 0x88 flips each nibble by 8. 116for (int j = 0 ;j < 16 ;++ j ) { 117weights [i * 16 + j ] ^=0x88 ; 118 } 119 }); 120 } 121} 122 123// Extracts (weight, scales, zp) from Q4_1 tensors. 124// Data layout is: |16 bit scale|16 bit min|32 x 4bit weights|. 125void extract_q4_1_data (const ggml_tensor * tensor , 126 ov::Tensor & weights_arr , 127 ov::Tensor & scales_arr , 128 ov::Tensor & zp_arr , 129bool use_bias ) { 130const uint64_t bytes_per_block = 20 ;// 2 bytes scale, 2 bytes min, 32x0.5 byte weights 131 132auto * data = static_cast < uint8_t *> (tensor -> data ); 133auto * weights = static_cast < uint8_t *> (weights_arr .data ()); 134auto * scales = scales_arr .data < ov::element_type_traits < ov::element::f16 > ::value_type > (); 135 136if (use_bias ) { 137// Store bias (min) directly as f16 instead of computing u4 zero points 138auto * bias = zp_arr .data < ov::element_type_traits < ov::element::f16 > ::value_type > (); 139 ov::parallel_for (scales_arr .get_size (), [& ](size_t i ) { 140float scale = static_cast < float > (ov::float16::from_bits (* ((uint16_t * ) (data + i * bytes_per_block )))); 141float min = static_cast < float > (ov::float16::from_bits (* ((uint16_t * ) (data + i * bytes_per_block + 2 )))); 142scales [i ]= ov::float16 (scale ); 143bias [i ]= ov::float16 (min );// bias = min, dequant: w*s + bias 144unpack_32_4 (data + i * bytes_per_block + 4 ,weights + i * 16 ); 145 }); 146 }else { 147auto * zp = static_cast < uint8_t *> (zp_arr .data ()); 148 ov::parallel_for (scales_arr .get_size (), [& ](size_t i ) { 149float scale = static_cast < float > (ov::float16::from_bits (* ((uint16_t * ) (data + i * bytes_per_block )))); 150float min = static_cast < float > (ov::float16::from_bits (* ((uint16_t * ) (data + i * bytes_per_block + 2 )))); 151scales [i ]= ov::float16 (scale ); 152// zp = -min / scale (bias = min, so zp = -bias/scale) 153uint8_t zp_val = (scale != 0.0f ) ? (uint8_t ) std::round (- min /scale ) :0 ; 154// Pack two 4-bit zero points per byte 155if (i %2 == 0 ) { 156zp [i /2 ]= zp_val & 0x0F ;// Lower nibble 157 }else { 158zp [i /2 ] |= (zp_val <<4 );// Upper nibble 159 } 160unpack_32_4 (data + i * bytes_per_block + 4 ,weights + i * 16 ); 161 }); 162 } 163} 164 165// Extracts (weight, scales, zp) from Q5_1 tensors. 166// Data layout is: |16 bit scale|16 bit min|32 bit qh (5th bits)|32 x 4bit low nibbles|. 167// Reconstructed quant q in [0,31]: q = (low nibble) | (qh_bit << 4). Dequant: w*d + m. 168// Weights are stored as u8 (5-bit values do not fit u4), matching make_int8_weights. 169void extract_q5_1_data (const ggml_tensor * tensor , 170 ov::Tensor & weights_arr , 171 ov::Tensor & scales_arr , 172 ov::Tensor & zp_arr , 173bool use_bias ) { 174const uint64_t bytes_per_block = 24 ;// 2 scale + 2 min + 4 qh + 16 (32x0.5) weights 175const int qk = 32 ; 176 177auto * data = static_cast < uint8_t *> (tensor -> data ); 178auto * weights = static_cast < uint8_t *> (weights_arr .data ());// u8 weights, one byte per weight 179auto * scales = scales_arr .data < ov::element_type_traits < ov::element::f16 > ::value_type > (); 180 181// Read a 16-bit little-endian value without aliasing/const-qual violations. 182auto read_u16 = [](const uint8_t * p ) { 183uint16_t v ; 184memcpy (& v ,p ,sizeof (v )); 185return v ; 186 }; 187 188auto unpack_block = [& ](const uint8_t * block ,uint8_t * dst ) { 189uint32_t qh ; 190memcpy (& qh ,block + 4 ,sizeof (uint32_t )); 191const uint8_t * qs = block + 8 ; 192for (int j = 0 ;j < qk /2 ;++ j ) { 193const uint8_t lo = qs [j ]& 0x0F ; 194const uint8_t hi = qs [j ] >>4 ; 195const uint8_t bit_lo = (qh >>j )& 1 ; 196const uint8_t bit_hi = (qh >> (j + qk /2 ))& 1 ; 197dst [j ]= lo | (bit_lo <<4 );// first 16 weights 198dst [j + qk /2 ]= hi | (bit_hi <<4 );// last 16 weights 199 } 200 }; 201 202if (use_bias ) { 203// Store bias (min) directly as f16: dequant w*d + m 204auto * bias = zp_arr .data < ov::element_type_traits < ov::element::f16 > ::value_type > (); 205 ov::parallel_for (scales_arr .get_size (), [& ](size_t i ) { 206const uint8_t * block = data + i * bytes_per_block ; 207float scale = static_cast < float > (ov::float16::from_bits (read_u16 (block ))); 208float min = static_cast < float > (ov::float16::from_bits (read_u16 (block + 2 ))); 209scales [i ]= ov::float16 (scale ); 210bias [i ]= ov::float16 (min ); 211unpack_block (block ,weights + i * qk ); 212 }); 213 }else { 214auto * zp = static_cast < uint8_t *> (zp_arr .data ());// u8 zero points 215 ov::parallel_for (scales_arr .get_size (), [& ](size_t i ) { 216const uint8_t * block = data + i * bytes_per_block ; 217float scale = static_cast < float > (ov::float16::from_bits (read_u16 (block ))); 218float min = static_cast < float > (ov::float16::from_bits (read_u16 (block + 2 ))); 219scales [i ]= ov::float16 (scale ); 220// zp = -min / scale (dequant: (w - zp) * s == w*s + min) 221zp [i ]= (scale != 0.0f ) ? (uint8_t ) std::lround (- min /scale ) :0 ; 222unpack_block (block ,weights + i * qk ); 223 }); 224 } 225} 226 227// Extracts (weight, scales, zp) from Q8_0 tensors. 228// Data layout is: |16 bit scale|32 x 8bit weights|. 229// When zp_arr is empty (symmetric), weights are stored as signed i8 directly. 230void extract_q8_0_data (const ggml_tensor * tensor , 231 ov::Tensor & weights_arr , 232 ov::Tensor & scales_arr , 233 ov::Tensor & zp_arr ) { 234const uint64_t weights_per_block = 32 ; 235const uint64_t bytes_per_block = 34 ;// 2 bytes scale, 32x1 byte weights 236 237auto * data = static_cast < uint8_t *> (tensor -> data ); 238auto * weights = static_cast < uint8_t *> (weights_arr .data ()); 239auto * scales = scales_arr .data < ov::element_type_traits < ov::element::f16 > ::value_type > (); 240 241bool is_symmetric = (weights_arr .get_element_type ()== ov::element::i8 );// Signed i8 path 242 243if (!is_symmetric ) { 244auto * zp = static_cast < uint8_t *> (zp_arr .data ()); 245 ov::parallel_for (scales_arr .get_size (), [& ](size_t i ) { 246uint8_t * block_data = data + i * bytes_per_block ; 247scales [i ]= ov::float16::from_bits (* (uint16_t * )block_data ); 248zp [i ]= 128 ; 249for (size_t j = 0 ;j < weights_per_block ;++ j ) { 250uint8_t x = block_data [j + 2 ]; 251x ^=1 <<7 ;// Convert int8 to uint8 by flipping sign bit 252weights [i * weights_per_block + j ]= x ; 253 } 254 }); 255 }else { 256// Symmetric: store original int8 values directly (no unsigned bias) 257 ov::parallel_for (scales_arr .get_size (), [& ](size_t i ) { 258uint8_t * block_data = data + i * bytes_per_block ; 259scales [i ]= ov::float16::from_bits (* (uint16_t * )block_data ); 260// Copy int8 weights as-is (the tensor element type is i8) 261memcpy (weights + i * weights_per_block ,block_data + 2 ,weights_per_block ); 262 }); 263 } 264} 265 266void unpack_256_4 (const uint8_t * data ,uint8_t * dst ) { 267// Initialize the output array with zeros 268 std::fill_n (dst ,128 ,0 ); 269 270for (size_t i = 0 ;i < 4 ;++ i ) { 271for (int j = 0 ;j < 32 ;++ j ) { 272uint8_t x = (data [i * 32 + j ]& 0x0F ); 273uint8_t y = (data [i * 32 + j ] >>4 ); 274if (j %2 != 0 ) { 275x <<=4 ; 276y <<=4 ; 277 } 278dst [i * 32 + j /2 ] |=x ; 279dst [i * 32 + 16 + j /2 ] |=y ;// Last 16 weights are in the higher bits 280 } 281 } 282} 283 284void extract_q4_k_data (const ggml_tensor * tensor , 285 ov::Tensor & weights_arr , 286 ov::Tensor & scales_arr , 287 ov::Tensor & zp_arr , 288bool use_bias ) { 289const uint64_t bytes_per_block = 2 + 2 + 12 + 128 ; 290const uint64_t n_super_block = tensor -> nb [3 ] /bytes_per_block ; 291 292auto * data = static_cast < uint8_t *> (tensor -> data ); 293auto * weights = static_cast < uint8_t *> (weights_arr .data ()); 294auto * scales = scales_arr .data < ov::element_type_traits < ov::element::f16 > ::value_type > (); 295 296// For bias path, zp_arr holds f16 bias values; for zp path, it holds packed u4 zero points 297auto * zp_u4 = use_bias ?nullptr :static_cast < uint8_t *> (zp_arr .data ()); 298auto * bias_f16 = use_bias ?zp_arr .data < ov::element_type_traits < ov::element::f16 > ::value_type > () :nullptr ; 299 300 ov::parallel_for (n_super_block , [& ](size_t i ) { 301uint8_t * block_data = data + i * bytes_per_block ; 302 303// Extract scale factors and offsets 304float scale_scales = static_cast < float > (ov::float16::from_bits (* ((uint16_t * )block_data ))); 305float scale_mins = static_cast < float > (ov::float16::from_bits (* ((uint16_t * )block_data + 1 ))); 306 307// Extract qs1 and qs2 308uint8_t * qs1 = block_data + 4 ; 309 310// Calculate scales 311float scale_vals [8 ]; 312scale_vals [0 ]= scale_scales * static_cast < float > ((* (qs1 )& 0b111111 )); 313scale_vals [1 ]= scale_scales * static_cast < float > ((* (qs1 + 1 )& 0b111111 )); 314scale_vals [2 ]= scale_scales * static_cast < float > ((* (qs1 + 2 )& 0b111111 )); 315scale_vals [3 ]= scale_scales * static_cast < float > ((* (qs1 + 3 )& 0b111111 )); 316scale_vals [4 ]= scale_scales * static_cast < float > ((* (qs1 + 8 )& 0b00001111 ) | ((* (qs1 ) >>6 ) <<4 )); 317scale_vals [5 ]= scale_scales * static_cast < float > ((* (qs1 + 9 )& 0b00001111 ) | ((* (qs1 + 1 ) >>6 ) <<4 )); 318scale_vals [6 ]= scale_scales * static_cast < float > ((* (qs1 + 10 )& 0b00001111 ) | ((* (qs1 + 2 ) >>6 ) <<4 )); 319scale_vals [7 ]= scale_scales * static_cast < float > ((* (qs1 + 11 )& 0b00001111 ) | ((* (qs1 + 3 ) >>6 ) <<4 )); 320 321// Calculate min values (bias = -min) 322float min_vals [8 ]; 323min_vals [0 ]= scale_mins * static_cast < float > ((* (qs1 + 4 )& 0b111111 )); 324min_vals [1 ]= scale_mins * static_cast < float > ((* (qs1 + 5 )& 0b111111 )); 325min_vals [2 ]= scale_mins * static_cast < float > ((* (qs1 + 6 )& 0b111111 )); 326min_vals [3 ]= scale_mins * static_cast < float > ((* (qs1 + 7 )& 0b111111 )); 327min_vals [4 ]= scale_mins * static_cast < float > ((* (qs1 + 8 ) >>4 ) | ((* (qs1 + 4 ) >>6 ) <<4 )); 328min_vals [5 ]= scale_mins * static_cast < float > ((* (qs1 + 9 ) >>4 ) | ((* (qs1 + 5 ) >>6 ) <<4 )); 329min_vals [6 ]= scale_mins * static_cast < float > ((* (qs1 + 10 ) >>4 ) | ((* (qs1 + 6 ) >>6 ) <<4 )); 330min_vals [7 ]= scale_mins * static_cast < float > ((* (qs1 + 11 ) >>4 ) | ((* (qs1 + 7 ) >>6 ) <<4 )); 331 332// Store scales and compute zero points or bias 333for (int j = 0 ;j < 8 ;j ++ ) { 334scales [i * 8 + j ]= ov::float16 (scale_vals [j ]); 335if (use_bias ) { 336// Store bias = -min directly as f16, dequant: w*s + bias 337bias_f16 [i * 8 + j ]= ov::float16 (- min_vals [j ]); 338 }else { 339// zp = min / scale (since bias = -min and zp = -bias/scale) 340uint8_t zp_val = (scale_vals [j ]!= 0.0f ) ? (uint8_t ) std::round (min_vals [j ] /scale_vals [j ]) :0 ; 341// Pack two 4-bit zero points per byte 342size_t idx = i * 8 + j ; 343if (idx %2 == 0 ) { 344zp_u4 [idx /2 ]= zp_val & 0x0F ; 345 }else { 346zp_u4 [idx /2 ] |= (zp_val <<4 ); 347 } 348 } 349 } 350unpack_256_4 (block_data + 16 ,weights + i * 128 ); 351 }); 352} 353 354void extract_q6_k_data (const ggml_tensor * tensor , 355 ov::Tensor & weights_arr , 356 ov::Tensor & scales_arr , 357 ov::Tensor & zp_arr ) { 358const uint64_t bytes_per_block = 128 + 64 + 16 + 2 ; 359const uint64_t n_super_block = tensor -> nb [3 ] /bytes_per_block ; 360 361auto * data = static_cast < uint8_t *> (tensor -> data ); 362auto * weights = static_cast < uint8_t *> (weights_arr .data ()); 363auto * scales = scales_arr .data < ov::element_type_traits < ov::element::f16 > ::value_type > (); 364 365bool is_symmetric = (weights_arr .get_element_type ()== ov::element::i8 );// Signed i8 path 366 367if (!is_symmetric ) { 368auto * zp = static_cast < uint8_t *> (zp_arr .data ()); 369 ov::parallel_for (n_super_block , [& ](size_t i ) { 370uint8_t * block_data = data + i * bytes_per_block ; 371float scale_factor = static_cast < float > (ov::float16::from_bits (* ((uint16_t * )block_data + 104 ))); 372for (size_t j = 0 ;j < 16 ;j ++ ) { 373scales [j + i * 16 ]= 374 ov::float16 (scale_factor * static_cast < float > (* ((int8_t * ) (block_data + 128 + 64 + j )))); 375zp [j + i * 16 ]= 32 ; 376 } 377uint8_t * ql = block_data ; 378uint8_t * qh = block_data + 128 ; 379for (int64_t j = 0 ;j < 32 ;++ j ) { 380weights [i * 256 + j ]= (ql [j ]& 0xF ) | (((qh [j ] >>0 )& 3 ) <<4 ); 381weights [i * 256 + j + 32 ]= (ql [32 + j ]& 0xF ) | (((qh [j ] >>2 )& 3 ) <<4 ); 382weights [i * 256 + j + 64 ]= (ql [j ] >>4 ) | (((qh [j ] >>4 )& 3 ) <<4 ); 383weights [i * 256 + j + 96 ]= (ql [32 + j ] >>4 ) | (((qh [j ] >>6 )& 3 ) <<4 ); 384weights [i * 256 + j + 128 ]= (ql [64 + j ]& 0xF ) | (((qh [32 + j ] >>0 )& 3 ) <<4 ); 385weights [i * 256 + j + 160 ]= (ql [96 + j ]& 0xF ) | (((qh [32 + j ] >>2 )& 3 ) <<4 ); 386weights [i * 256 + j + 192 ]= (ql [64 + j ] >>4 ) | (((qh [32 + j ] >>4 )& 3 ) <<4 ); 387weights [i * 256 + j + 224 ]= (ql [96 + j ] >>4 ) | (((qh [32 + j ] >>6 )& 3 ) <<4 ); 388 } 389 }); 390 }else { 391// Symmetric: subtract 32 from each weight to store as signed i8 392 ov::parallel_for (n_super_block , [& ](size_t i ) { 393uint8_t * block_data = data + i * bytes_per_block ; 394float scale_factor = static_cast < float > (ov::float16::from_bits (* ((uint16_t * )block_data + 104 ))); 395for (size_t j = 0 ;j < 16 ;j ++ ) { 396scales [j + i * 16 ]= 397 ov::float16 (scale_factor * static_cast < float > (* ((int8_t * ) (block_data + 128 + 64 + j )))); 398 } 399uint8_t * ql = block_data ; 400uint8_t * qh = block_data + 128 ; 401auto * signed_weights = reinterpret_cast < int8_t *> (weights ); 402for (int64_t j = 0 ;j < 32 ;++ j ) { 403signed_weights [i * 256 + j ]= static_cast < int8_t > ((ql [j ]& 0xF ) | (((qh [j ] >>0 )& 3 ) <<4 ))- 32 ; 404signed_weights [i * 256 + j + 32 ]= 405static_cast < int8_t > ((ql [32 + j ]& 0xF ) | (((qh [j ] >>2 )& 3 ) <<4 ))- 32 ; 406signed_weights [i * 256 + j + 64 ]= static_cast < int8_t > ((ql [j ] >>4 ) | (((qh [j ] >>4 )& 3 ) <<4 ))- 32 ; 407signed_weights [i * 256 + j + 96 ]= 408static_cast < int8_t > ((ql [32 + j ] >>4 ) | (((qh [j ] >>6 )& 3 ) <<4 ))- 32 ; 409signed_weights [i * 256 + j + 128 ]= 410static_cast < int8_t > ((ql [64 + j ]& 0xF ) | (((qh [32 + j ] >>0 )& 3 ) <<4 ))- 32 ; 411signed_weights [i * 256 + j + 160 ]= 412static_cast < int8_t > ((ql [96 + j ]& 0xF ) | (((qh [32 + j ] >>2 )& 3 ) <<4 ))- 32 ; 413signed_weights [i * 256 + j + 192 ]= 414static_cast < int8_t > ((ql [64 + j ] >>4 ) | (((qh [32 + j ] >>4 )& 3 ) <<4 ))- 32 ; 415signed_weights [i * 256 + j + 224 ]= 416static_cast < int8_t > ((ql [96 + j ] >>4 ) | (((qh [32 + j ] >>6 )& 3 ) <<4 ))- 32 ; 417 } 418 }); 419 } 420} 421 422static inline void get_scale_min_k4 (int j ,const uint8_t * q ,uint8_t * d ,uint8_t * m ) { 423if (j < 4 ) { 424* d = q [j ]& 63 ; 425* m = q [j + 4 ]& 63 ; 426 }else { 427* d = (q [j + 4 ]& 0xF ) | ((q [j - 4 ] >>6 ) <<4 ); 428* m = (q [j + 4 ] >>4 ) | ((q [j - 0 ] >>6 ) <<4 ); 429 } 430} 431 432void extract_q5_k_data (const ggml_tensor * tensor , 433 ov::Tensor & weights_arr , 434 ov::Tensor & scales_arr , 435 ov::Tensor & zp_arr , 436bool use_bias ) { 437const uint64_t bytes_per_block = 4 + 12 + 32 + 128 ; 438const uint64_t n_super_block = tensor -> nb [3 ] /bytes_per_block ; 439 440auto * data = static_cast < uint8_t *> (tensor -> data ); 441auto * weights = static_cast < uint8_t *> (weights_arr .data ()); 442auto * scales = scales_arr .data < ov::element_type_traits < ov::element::f16 > ::value_type > (); 443 444// For bias path, zp_arr holds f16 bias values; for zp path, it holds u8 zero points 445auto * zp_u8 = use_bias ?nullptr :static_cast < uint8_t *> (zp_arr .data ()); 446auto * bias_f16 = use_bias ?zp_arr .data < ov::element_type_traits < ov::element::f16 > ::value_type > () :nullptr ; 447 448 ov::parallel_for (n_super_block , [& ](size_t i ) { 449uint8_t * block_data = data + i * bytes_per_block ; 450 451const float d = static_cast < float > (ov::float16::from_bits (* ((uint16_t * )block_data ))); 452const float min_factor = static_cast < float > (ov::float16::from_bits (* ((uint16_t * )block_data + 1 ))); 453 454const uint8_t * scales_data = block_data + 4 ;// 12 bytes of scales 455const uint8_t * qh = block_data + 4 + 12 ;// 32 bytes of high bits 456const uint8_t * ql = block_data + 4 + 12 + 32 ;// 128 bytes of low bits 457 458int is = 0 ; 459uint8_t u1 = 1 ; 460uint8_t u2 = 2 ; 461 462// Process 2 blocks in one iteration 463for (int j = 0 ;j < 256 ;j += 64 ) {// 256 = QK_K, so 4 iterations of 64 464uint8_t sc ; 465uint8_t m ; 466 467// Get scale and min for first 32 elements 468get_scale_min_k4 (is + 0 ,scales_data ,& sc ,& m ); 469const float d1 = d * sc ; 470const float m1 = min_factor * m ; 471 472// Get scale and min for second 32 elements 473get_scale_min_k4 (is + 1 ,scales_data ,& sc ,& m ); 474const float d2 = d * sc ; 475const float m2 = min_factor * m ; 476 477scales [i * 8 + is ]= ov::float16 (d1 ); 478scales [i * 8 + is + 1 ]= ov::float16 (d2 ); 479if (use_bias ) { 480// Store bias = -min directly as f16, dequant: w*s + bias 481bias_f16 [i * 8 + is ]= ov::float16 (- m1 ); 482bias_f16 [i * 8 + is + 1 ]= ov::float16 (- m2 ); 483 }else { 484// zp = min / scale (since bias = -min and zp = -bias/scale) 485zp_u8 [i * 8 + is ]= (d1 != 0.0f ) ? (uint8_t ) std::round (m1 /d1 ) :0 ; 486zp_u8 [i * 8 + is + 1 ]= (d2 != 0.0f ) ? (uint8_t ) std::round (m2 /d2 ) :0 ; 487 } 488 489// Extract weights for first 32 elements (matching deq formula exactly) 490for (int l = 0 ;l < 32 ;++ l ) { 491weights [i * 256 + j + l ]= (ql [l ]& 0xF )+ ((qh [l ]& u1 ) ?16 :0 ); 492 } 493 494// Extract weights for second 32 elements 495for (int l = 0 ;l < 32 ;++ l ) { 496weights [i * 256 + j + l + 32 ]= (ql [l ] >>4 )+ ((qh [l ]& u2 ) ?16 :0 ); 497 } 498 499ql += 32 ; 500is += 2 ; 501u1 <<=2 ; 502u2 <<=2 ; 503 } 504 }); 505} 506 507// TODO Reorder for make_intX_weights 508 509// If for_gather_matmul is true, weight may be N-D (e.g. 3D MoE expert weights [n_expert, rows, cols]). 510// The dequantization chain below is built as usual but left in f16 (no final Convert to f32) -- 511// ov::pass::MarkDequantization (registered in translate_session.cpp) marks the chain so it survives 512// model-build-time ConstantFolding. mul_mat_id.cpp constructs ov::op::internal::GatherMatmul directly 513// on top of the resulting f16 chain. 514ov::Output < ov::Node > make_int8_weights (ov::Tensor & weight , 515 ov::Tensor & scales , 516 ov::Tensor & zp , 517size_t group_size , 518bool use_bias , 519bool for_gather_matmul ) { 520 ov::Shape orig_shape = weight .get_shape (); 521bool is_signed = (weight .get_element_type ()== ov::element::i8 );// Symmetric: signed weights, no ZP 522 523// Expand dimensions for scales and zp/bias 524auto scale_shape = scales .get_shape (); 525 526// Group the innermost (last) dimension. For 2D weights [rows, cols] this yields 527// [rows, cols/group_size, group_size]; for 3D MoE experts [n_expert, rows, cols] this yields 528// [n_expert, rows, cols/group_size, group_size]. 529 ov::Shape packed_shape = orig_shape ; 530packed_shape .back () /=group_size ; 531packed_shape .push_back (group_size ); 532const size_t group_dim = packed_shape .size ()- 2 ; 533 534if (packed_shape [group_dim ]== 1 ) { 535// Requantized channel-wise case 536packed_shape .erase (packed_shape .begin ()+ group_dim ); 537 }else { 538scale_shape .push_back (1 ); 539scales .set_shape (scale_shape ); 540if (!is_signed && zp .get_size ()> 0 ) { 541auto zp_shape = zp .get_shape (); 542zp_shape .push_back (1 ); 543zp .set_shape (zp_shape ); 544 } 545 } 546 547auto scales_f16 = std::make_shared < ov::op::v0::Constant > (scales ); 548 549 ov::Output < ov::Node > result ; 550if (is_signed ) { 551// Signed path: q * s (no zero point subtraction needed) 552auto weights_node = std::make_shared < ov::op::v0::Constant > (ov::element::i8 ,packed_shape , 553static_cast < uint8_t *> (weight .data ()),nullptr ); 554weights_node -> get_rt_info ()["__gguf_tensor_holder" ]= weight ; 555auto weights_f16 = std::make_shared < ov::op::v0::Convert > (weights_node , ov::element::f16 ); 556auto mul = std::make_shared < ov::op::v1::Multiply > (weights_f16 ,scales_f16 , ov::op::AutoBroadcastType ::NUMPY ); 557result = mul ; 558 }else { 559// Unsigned path 560auto weights_node = std::make_shared < ov::op::v0::Constant > (ov::element::u8 ,packed_shape , 561static_cast < uint8_t *> (weight .data ()),nullptr ); 562weights_node -> get_rt_info ()["__gguf_tensor_holder" ]= weight ; 563auto weights_f16 = std::make_shared < ov::op::v0::Convert > (weights_node , ov::element::f16 ); 564 565if (use_bias && zp .get_size ()> 0 ) { 566// Accurate dequant in the FUSABLE zero-point form: (w - zp) * s, where the zero 567// point is an exact f16 value zp = -bias/scale (the zp tensor holds bias values 568// coming in). Algebraically equal to w*s + bias, but unlike an Add(bias) graph this 569// matches CompressedWeightsBlock's pattern (Constant->Convert->Subtract->Multiply), 570// so for_gather_matmul weights still fuse into GatherMatmulCompressed. Also avoids 571// the round(min/scale) error of an integer zero point. Convert bias -> zero-point IN 572// PLACE in the (possibly buffer-backed) zp tensor to avoid a duplicate allocation. 573auto * bias_zp_data = zp .data < ov::float16 > (); 574const auto * scale_data = scales .data < ov::float16 > (); 575const size_t n = zp .get_size (); 576for (size_t i = 0 ;i < n ;i ++ ) { 577float s = static_cast < float > (scale_data [i ]); 578float b = static_cast < float > (bias_zp_data [i ]); 579bias_zp_data [i ]= ov::float16 (s != 0.0f ?- b /s :0.0f ); 580 } 581auto zero_point_f16 = std::make_shared < ov::op::v0::Constant > (zp ); 582auto w_zp = 583 std::make_shared < ov::op::v1::Subtract > (weights_f16 ,zero_point_f16 , ov::op::AutoBroadcastType ::NUMPY ); 584result = std::make_shared < ov::op::v1::Multiply > (w_zp ,scales_f16 , ov::op::AutoBroadcastType ::NUMPY ); 585 }else { 586// Zero point path: (w - zp) * s 587auto zero_point = std::make_shared < ov::op::v0::Constant > (zp ); 588float zp_value ; 589if (ov::op::util::get_single_value (zero_point ,zp_value )) { 590zero_point = ov::op::v0::Constant ::create (zero_point -> get_element_type (), {}, {zp_value }); 591 } 592auto zero_point_f16 = std::make_shared < ov::op::v0::Convert > (zero_point , ov::element::f16 ); 593auto w_zp = 594 std::make_shared < ov::op::v1::Subtract > (weights_f16 ,zero_point_f16 , ov::op::AutoBroadcastType ::NUMPY ); 595auto mul = std::make_shared < ov::op::v1::Multiply > (w_zp ,scales_f16 , ov::op::AutoBroadcastType ::NUMPY ); 596result = mul ; 597 } 598 } 599 600if (packed_shape .size ()!= orig_shape .size ()) { 601// If not requantized channel-wise case, reshape back to original shape 602auto final_shape = 603 std::make_shared < ov::op::v0::Constant > (ov::element::i64 , ov::Shape {orig_shape .size ()},orig_shape ); 604auto reshaped = std::make_shared < ov::op::v1::Reshape > (result ,final_shape , false); 605result = reshaped ; 606 } 607 608if (for_gather_matmul ) { 609return result ; 610 } 611return std::make_shared < ov::op::v0::Convert > (result , ov::element::f32 ); 612} 613 614// See make_int8_weights for the meaning of for_gather_matmul. 615ov::Output < ov::Node > make_int4_weights (ov::Tensor & weight , 616 ov::Tensor & scales , 617 ov::Tensor & zp , 618size_t group_size , 619bool use_bias , 620bool for_gather_matmul ) { 621 ov::Shape orig_weight_shape = weight .get_shape (); 622bool is_signed = (weight .get_element_type ()== ov::element::i4 );// Symmetric: signed weights, no ZP 623 624// Expand dimensions for scales and zp/bias 625 ov::Shape scale_shape = scales .get_shape (); 626 627// Create INT4 weight tensor. Group the innermost (last) dimension: for 2D weights 628// [rows, cols] this yields [rows, cols/group_size, group_size]; for 3D MoE experts 629// [n_expert, rows, cols] this yields [n_expert, rows, cols/group_size, group_size]. 630 ov::Shape packed_shape = orig_weight_shape ; 631packed_shape .back () /=group_size ; 632packed_shape .push_back (group_size ); 633const size_t group_dim = packed_shape .size ()- 2 ; 634 635if (packed_shape [group_dim ]== 1 ) { 636// Requantized channel-wise case 637packed_shape .erase (packed_shape .begin ()+ group_dim ); 638 }else { 639scale_shape .push_back (1 ); 640scales .set_shape (scale_shape ); 641if (!is_signed && zp .get_size ()> 0 ) { 642auto zp_shape = zp .get_shape (); 643zp_shape .push_back (1 ); 644zp .set_shape (zp_shape ); 645 } 646 } 647 648auto scales_f16 = std::make_shared < ov::op::v0::Constant > (scales ); 649 650 ov::Output < ov::Node > result ; 651if (is_signed ) { 652// Signed path: q * s (no zero point subtraction needed) 653auto weights_node = std::make_shared < ov::op::v0::Constant > (ov::element::i4 ,packed_shape , 654static_cast < uint8_t *> (weight .data ()),nullptr ); 655weights_node -> get_rt_info ()["__gguf_tensor_holder" ]= weight ; 656auto weights_f16 = std::make_shared < ov::op::v0::Convert > (weights_node , ov::element::f16 ); 657auto mul = std::make_shared < ov::op::v1::Multiply > (weights_f16 ,scales_f16 , ov::op::AutoBroadcastType ::NUMPY ); 658result = mul ; 659 }else { 660// Unsigned path 661auto weights_node = std::make_shared < ov::op::v0::Constant > (ov::element::u4 ,packed_shape , 662static_cast < uint8_t *> (weight .data ()),nullptr ); 663weights_node -> get_rt_info ()["__gguf_tensor_holder" ]= weight ; 664auto weights_f16 = std::make_shared < ov::op::v0::Convert > (weights_node , ov::element::f16 ); 665 666if (use_bias && zp .get_size ()> 0 ) { 667// Accurate dequant in the FUSABLE zero-point form: (w - zp) * s with an exact f16 668// zp = -bias/scale. Equivalent to w*s + bias but matches CompressedWeightsBlock's 669// pattern so for_gather_matmul weights still fuse into GatherMatmulCompressed, and 670// avoids the round(min/scale) error of an integer zp. Convert bias -> zero-point IN 671// PLACE in the (possibly buffer-backed) zp tensor to avoid a duplicate allocation. 672auto * bias_zp_data = zp .data < ov::float16 > (); 673const auto * scale_data = scales .data < ov::float16 > (); 674const size_t n = zp .get_size (); 675for (size_t i = 0 ;i < n ;i ++ ) { 676float s = static_cast < float > (scale_data [i ]); 677float b = static_cast < float > (bias_zp_data [i ]); 678bias_zp_data [i ]= ov::float16 (s != 0.0f ?- b /s :0.0f ); 679 } 680auto zero_points_f16 = std::make_shared < ov::op::v0::Constant > (zp ); 681auto w_zp = 682 std::make_shared < ov::op::v1::Subtract > (weights_f16 ,zero_points_f16 , ov::op::AutoBroadcastType ::NUMPY ); 683result = std::make_shared < ov::op::v1::Multiply > (w_zp ,scales_f16 , ov::op::AutoBroadcastType ::NUMPY ); 684 }else { 685// Zero point path: (w - zp) * s 686auto zero_points_node = std::make_shared < ov::op::v0::Constant > (zp ); 687float zp_value ; 688if (ov::op::util::get_single_value (zero_points_node ,zp_value )) { 689zero_points_node = ov::op::v0::Constant ::create (zero_points_node -> get_element_type (), {}, {zp_value }); 690 } 691auto zero_points_f16 = std::make_shared < ov::op::v0::Convert > (zero_points_node , ov::element::f16 ); 692auto w_zp = 693 std::make_shared < ov::op::v1::Subtract > (weights_f16 ,zero_points_f16 , ov::op::AutoBroadcastType ::NUMPY ); 694auto mul = std::make_shared < ov::op::v1::Multiply > (w_zp ,scales_f16 , ov::op::AutoBroadcastType ::NUMPY ); 695result = mul ; 696 } 697 } 698 699if (packed_shape .size ()!= orig_weight_shape .size ()) { 700// If not requantized channel-wise case, reshape back to original shape 701auto final_shape = std::make_shared < ov::op::v0::Constant > (ov::element::i64 , ov::Shape {orig_weight_shape .size ()}, 702orig_weight_shape ); 703auto reshaped = std::make_shared < ov::op::v1::Reshape > (result ,final_shape , false); 704result = reshaped ; 705 } 706 707if (for_gather_matmul ) { 708return result ; 709 } 710return std::make_shared < ov::op::v0::Convert > (result , ov::element::f32 ); 711} 712 713ov::Output < ov::Node > make_mxfp4_weights (ov::Tensor & weight , ov::Tensor & scales ) { 714const ov::Shape final_shape = weight .get_shape (); 715GGML_ASSERT (!final_shape .empty ()); 716GGML_ASSERT (final_shape .back () %MXFP4_BLOCK_SIZE == 0 ); 717 718 ov::Shape packed_shape = final_shape ; 719packed_shape .back () /=MXFP4_BLOCK_SIZE ; 720packed_shape .push_back (MXFP4_BLOCK_SIZE ); 721 722 ov::Shape scale_shape = packed_shape ; 723scale_shape .back ()= 1 ; 724scales .set_shape (scale_shape ); 725 726auto weights_node = std::make_shared < ov::op::v0::Constant > (ov::element::f4e2m1 ,packed_shape , 727static_cast < uint8_t *> (weight .data ()),nullptr ); 728weights_node -> get_rt_info ()["__gguf_tensor_holder" ]= weight ; 729auto weights_f32 = std::make_shared < ov::op::v0::Convert > (weights_node , ov::element::f32 ); 730 731auto scales_node = std::make_shared < ov::op::v0::Constant > (scales ); 732auto scales_f32 = std::make_shared < ov::op::v0::Convert > (scales_node , ov::element::f32 ); 733 ov::Output < ov::Node > result = 734 std::make_shared < ov::op::v1::Multiply > (weights_f32 ,scales_f32 , ov::op::AutoBroadcastType ::NUMPY ); 735 736auto final_shape_node = 737 std::make_shared < ov::op::v0::Constant > (ov::element::i64 , ov::Shape {final_shape .size ()},final_shape ); 738return std::make_shared < ov::op::v1::Reshape > (result ,final_shape_node , false); 739} 740 741ov::Output < ov::Node > make_mxfp4_moe_packed_weights (ov::Tensor & weight ) { 742auto weights_node = std::make_shared < ov::op::v0::Constant > (ov::element::u8 ,weight .get_shape (), 743static_cast < uint8_t *> (weight .data ()),nullptr ); 744weights_node -> get_rt_info ()["__gguf_tensor_holder" ]= weight ; 745weights_node -> get_rt_info ()["__ggml_openvino_mxfp4_moe_packed" ]= true; 746return weights_node ; 747} 748 749// Extract quantized weights from tensor and create weight subgraph 750std::shared_ptr < ov::Node > extract_quantized_weights (const ggml_tensor * tensor , 751const void * data , 752 ov::Tensor & weights , 753 ov::Tensor & scales , 754 ov::Tensor & zp , 755bool use_bias ) { 756// Create a temporary tensor for extraction functions that read from tensor->data 757ggml_tensor temp_tensor = * tensor ; 758temp_tensor .data = const_cast < void *> (data ); 759 760if (tensor -> type == GGML_TYPE_MXFP4 ) { 761extract_mxfp4_data (& temp_tensor ,weights ,scales ); 762auto result = make_mxfp4_weights (weights ,scales ).get_node_shared_ptr (); 763result -> set_friendly_name (tensor -> name ); 764return result ; 765 } 766 767// Determine block size based on tensor type 768int64_t weights_per_block ; 769bool is_u4 ; 770switch (tensor -> type ) { 771case GGML_TYPE_Q4_0 : 772case GGML_TYPE_Q4_1 : 773case GGML_TYPE_Q4_K : 774is_u4 = true; 775weights_per_block = 32 ; 776break ; 777case GGML_TYPE_Q8_0 : 778case GGML_TYPE_Q5_1 : 779case GGML_TYPE_Q5_K : 780is_u4 = false; 781weights_per_block = 32 ; 782break ; 783case GGML_TYPE_Q6_K : 784is_u4 = false; 785weights_per_block = 16 ; 786break ; 787default : 788throw std::runtime_error ("Unsupported quantized type for extraction: " + 789 std::string (ggml_type_name (tensor -> type ))); 790 } 791 792// 3D MoE expert weights (for_gather_matmul) always use the exact f16 zero-point extraction 793// (see make_int8_weights/make_int4_weights) rather than the rounded integer zero point -- 794// round(min/scale) error is what corrupts Q4_K/Q5_1 experts, and the f16-zp form still fuses 795// into GatherMatmulCompressed since it stays a Subtract, not an Add. 796const bool for_gather_matmul = tensor -> ne [2 ]> 1 ; 797use_bias = use_bias || for_gather_matmul ; 798 799// Extract quantized data 800switch (tensor -> type ) { 801case GGML_TYPE_Q4_0 : 802extract_q4_0_data (& temp_tensor ,weights ,scales ,zp ); 803break ; 804case GGML_TYPE_Q4_1 : 805extract_q4_1_data (& temp_tensor ,weights ,scales ,zp ,use_bias ); 806break ; 807case GGML_TYPE_Q4_K : 808extract_q4_k_data (& temp_tensor ,weights ,scales ,zp ,use_bias ); 809break ; 810case GGML_TYPE_Q5_1 : 811extract_q5_1_data (& temp_tensor ,weights ,scales ,zp ,use_bias ); 812break ; 813case GGML_TYPE_Q8_0 : 814extract_q8_0_data (& temp_tensor ,weights ,scales ,zp ); 815break ; 816case GGML_TYPE_Q6_K : 817extract_q6_k_data (& temp_tensor ,weights ,scales ,zp ); 818break ; 819case GGML_TYPE_Q5_K : 820extract_q5_k_data (& temp_tensor ,weights ,scales ,zp ,use_bias ); 821break ; 822default : 823throw std::runtime_error ("Unsupported quantized type: " + std::string (ggml_type_name (tensor -> type ))); 824 } 825 826// Create the OpenVINO weight subgraph. 3D expert weights (MoE) are routed through the 827// GatherMatmul-oriented path: dequantized in f16, with constant folding disabled on the chain. 828 ov::Output < ov::Node > weight_node ; 829if (is_u4 ) { 830weight_node = make_int4_weights (weights ,scales ,zp ,weights_per_block ,use_bias ,for_gather_matmul ); 831 }else { 832weight_node = make_int8_weights (weights ,scales ,zp ,weights_per_block ,use_bias ,for_gather_matmul ); 833 } 834 835auto result = weight_node .get_node_shared_ptr (); 836result -> set_friendly_name (tensor -> name ); 837return result ; 838} 839 840// Requantize weights to target format, writing to provided buffers 841std::shared_ptr < ov::Node > requantize_to_buffers (const ggml_tensor * tensor , 842const void * data , 843ExtraQuantType requant_type , 844int64_t block_size , 845 ov::Tensor & weights , 846 ov::Tensor & scales , 847 ov::Tensor & zp ) { 848int64_t n_elements = ggml_nelements (tensor ); 849const int64_t ne0 = tensor -> ne [0 ];// elements per row 850const int64_t n_rows = n_elements /ne0 ; 851const auto * type_traits = ggml_get_type_traits (tensor -> type ); 852const size_t src_row_bytes = ggml_row_size (tensor -> type ,ne0 ); 853 854bool is_u4 = (requant_type == ExtraQuantType ::Q4_0_C || requant_type == ExtraQuantType ::Q4_0_128 ); 855 856// Streaming dequant (opt-in via GGML_OPENVINO_REDUCE_COMPILE_MEM or 857// GGML_OPENVINO_MEMORY_OPTIMIZE): instead of 858// materializing the full n_elements F32 array (e.g. ~1 GB for token_embd), dequantize 859// a chunk of complete rows into a small scratch and quantize/convert it straight into 860// the output buffers, capping the transient F32 footprint at CHUNK_ROWS*ne0 floats. 861// 862// Only valid (and only used) for the Q8_0_C / Q8_1_C / F16 targets whose block size 863// divides a row (channel-wise _C uses block_size == ne0) so no target block straddles 864// a row boundary, and Q8/F16 have no cross-block packing. The u4 (Q4_0) path packs two 865// weights per byte with running zp ORs that assume a single whole-array call, so it is 866// never streamed. When the flag is off, behavior is identical to the original 867// full-materialization path. 868const bool stream_requant = ggml_openvino_reduce_compile_mem_enabled ()&& !is_u4 && 869 !(block_size > 0 && ne0 %block_size != 0 ); 870 871if (!stream_requant ) { 872// Full materialization (original behavior): dequantize the whole tensor to F32, 873// then convert/quantize in one call. 874 std::vector < float > weights_f32 (n_elements ); 875type_traits -> to_float (data ,weights_f32 .data (),n_elements ); 876if (requant_type == ExtraQuantType ::F16 ) { 877ggml_get_type_traits (GGML_TYPE_F16 )-> from_float_ref (weights_f32 .data (),weights .data (),n_elements ); 878auto result = std::make_shared < ov::op::v0::Constant > (weights ); 879result -> set_friendly_name (tensor -> name ); 880return result ; 881 } 882if (is_u4 ) { 883quantize_q4_0 (weights_f32 .data (),weights ,scales ,zp ,n_elements ,block_size ); 884 }else if (requant_type == ExtraQuantType ::Q8_1_C ) { 885quantize_q8_1 (weights_f32 .data (),weights ,scales ,zp ,n_elements ,block_size ); 886 }else { 887quantize_q8_0 (weights_f32 .data (),weights ,scales ,zp ,n_elements ,block_size ); 888 } 889 }else { 890// Streaming path for Q8_0_C / Q8_1_C / F16 (covers token_embd, output.weight, 891// and per-layer Q6_K/Q5_K requant — the large transient cases). 892const int64_t CHUNK_ROWS = std::min < int64_t > (n_rows ,256 ); 893 std::vector < float > scratch (CHUNK_ROWS * ne0 ); 894// F16 destination: 2 bytes/element, advanced per chunk by r0*ne0 elements. 895auto * f16_base = static_cast < uint8_t *> (weights .data ()); 896for (int64_t r0 = 0 ;r0 < n_rows ;r0 += CHUNK_ROWS ) { 897const int64_t rows = std::min (CHUNK_ROWS ,n_rows - r0 ); 898const int64_t elems = rows * ne0 ; 899const auto * src = static_cast < const uint8_t *> (data )+ r0 * src_row_bytes ; 900type_traits -> to_float (src ,scratch .data (),elems ); 901 902if (requant_type == ExtraQuantType ::F16 ) { 903ggml_get_type_traits (GGML_TYPE_F16 ) 904-> from_float_ref (scratch .data (),f16_base + (r0 * ne0 )* sizeof (uint16_t ),elems ); 905 }else { 906const int64_t block_offset = (r0 * ne0 ) /block_size ; 907if (requant_type == ExtraQuantType ::Q8_1_C ) { 908quantize_q8_1 (scratch .data (),weights ,scales ,zp ,elems ,block_size ,block_offset ); 909 }else { 910quantize_q8_0 (scratch .data (),weights ,scales ,zp ,elems ,block_size ,block_offset ); 911 } 912 } 913 } 914if (requant_type == ExtraQuantType ::F16 ) { 915auto result = std::make_shared < ov::op::v0::Constant > (weights ); 916result -> set_friendly_name (tensor -> name ); 917return result ; 918 } 919 } 920 921// Create the OpenVINO weight subgraph 922 ov::Output < ov::Node > weight_node ; 923if (is_u4 ) { 924weight_node = make_int4_weights (weights ,scales ,zp ,block_size ); 925 }else { 926weight_node = make_int8_weights (weights ,scales ,zp ,block_size ); 927 } 928 929auto result = weight_node .get_node_shared_ptr (); 930result -> set_friendly_name (tensor -> name ); 931return result ; 932} 933 934OvWeight process_weight_tensor (const ggml_tensor * tensor ,const void * data ,void * output_base_ptr ,bool use_bias ) { 935GGML_ASSERT (tensor != nullptr ); 936GGML_ASSERT (data != nullptr ); 937 938OvWeight result ; 939 940// Get shape for weights: [rows, cols], or [n_expert, rows, cols] for 3D MoE expert weights. 941 ov::Shape node_shape = (tensor -> ne [2 ]> 1 ) ? 942 ov::Shape {static_cast < size_t > (tensor -> ne [2 ]),static_cast < size_t > (tensor -> ne [1 ]), 943static_cast < size_t > (tensor -> ne [0 ])} : 944 ov::Shape {static_cast < size_t > (tensor -> ne [1 ]),static_cast < size_t > (tensor -> ne [0 ])}; 945 946// Handle F16/F32/BF16 weights 947if (tensor -> type == GGML_TYPE_F32 || tensor -> type == GGML_TYPE_F16 || tensor -> type == GGML_TYPE_BF16 ) { 948 ov::element::Type element_type ; 949switch (tensor -> type ) { 950case GGML_TYPE_F32 : 951element_type = ov::element::f32 ; 952break ; 953case GGML_TYPE_F16 : 954element_type = ov::element::f16 ; 955break ; 956case GGML_TYPE_BF16 : 957element_type = ov::element::bf16 ; 958break ; 959default : 960OPENVINO_THROW ("Unexpected tensor type in F16/F32/BF16 path" ); 961 } 962 963if (output_base_ptr && output_base_ptr != data ) { 964// Using external buffer - copy data and create shared-memory constant 965size_t tensor_bytes = ggml_nbytes (tensor ); 966memcpy (output_base_ptr ,data ,tensor_bytes ); 967result .weights = ov::Tensor (element_type ,node_shape ,output_base_ptr ); 968 }else { 969result .weights = ov::Tensor (element_type ,node_shape ,data ); 970 } 971result .weight_node = std::make_shared < ov::op::v0::Constant > (result .weights ); 972return result ; 973 } 974 975// Handle quantized weights 976if (!ggml_is_quantized (tensor -> type )) { 977OPENVINO_THROW ("Unsupported weight tensor type: " ,ggml_type_name (tensor -> type )); 978 } 979 980result .layout = ggml_openvino_get_extracted_layout (tensor ,use_bias ); 981const auto & layout = result .layout ; 982if (layout .total_size == 0 ) { 983OPENVINO_THROW ("Unsupported quantized type: " ,ggml_type_name (tensor -> type )); 984 } 985 986// 3D MoE expert weights (for_gather_matmul) always use the exact f16 zero-point path (see 987// extract_quantized_weights) -- must be kept in sync with the "use_bias || for_gather_matmul" 988// check in ggml_openvino_get_extracted_layout, which sizes/offsets the zp slot accordingly. 989// Requantized tensors (layout.is_requant) are handled by requantize_to_buffers instead, whose 990// zp sizing/type is unaffected by for_gather_matmul, so they are excluded here. 991const bool for_gather_matmul = tensor -> ne [2 ]> 1 ; 992const bool zp_is_f16 = !layout .is_requant && (use_bias || for_gather_matmul ); 993 994const bool is_3d_mxfp4_moe = tensor -> type == GGML_TYPE_MXFP4 && (tensor -> ne [2 ]> 1 || tensor -> ne [3 ]> 1 ); 995if (is_3d_mxfp4_moe ) { 996 ov::Shape packed_shape = {static_cast < size_t > (tensor -> ne [3 ]), 997static_cast < size_t > (tensor -> ne [2 ]), 998static_cast < size_t > (tensor -> ne [1 ]), 999static_cast < size_t > (tensor -> ne [0 ] /MXFP4_BLOCK_SIZE ), 1000MXFP4_BLOCK_BYTES }; 1001const size_t tensor_bytes = ggml_nbytes (tensor ); 1002if (output_base_ptr ) { 1003auto * buf_base = static_cast < uint8_t *> (output_base_ptr ); 1004memcpy (buf_base + layout .weights_offset ,data ,tensor_bytes ); 1005result .weights = ov::Tensor (ov::element::u8 ,packed_shape ,buf_base + layout .weights_offset ); 1006 }else { 1007result .weights = ov::Tensor (ov::element::u8 ,packed_shape ); 1008memcpy (result .weights .data (),data ,tensor_bytes ); 1009 } 1010result .weight_node = make_mxfp4_moe_packed_weights (result .weights ).get_node_shared_ptr (); 1011result .weight_node -> set_friendly_name (tensor -> name ); 1012return result ; 1013 } 1014 1015if (use_bias ) { 1016OPENVINO_ASSERT (!layout .is_requant , 1017"use_bias is only used for test-backend-ops, which should not have requantization" ); 1018// bias node will be created on the fly and not use backend buffer 1019output_base_ptr = nullptr ; 1020 } 1021 1022// F16 requant path - no separate scales/zp needed in result 1023if (layout .is_requant && layout .requant_type .has_value ()&& layout .requant_type .value ()== ExtraQuantType ::F16 ) { 1024if (output_base_ptr ) { 1025result .weights = ov::Tensor (ov::element::f16 ,node_shape , 1026static_cast < uint8_t *> (output_base_ptr )+ layout .weights_offset ); 1027 }else { 1028result .weights = ov::Tensor (ov::element::f16 ,node_shape ); 1029 } 1030 ov::Tensor dummy_scales ,dummy_zp ;// Not used for F16 1031result .weight_node = 1032requantize_to_buffers (tensor ,data ,ExtraQuantType ::F16 ,0 ,result .weights ,dummy_scales ,dummy_zp ); 1033return result ; 1034 } 1035 1036// Quantized path (normal extraction or quantized requant) 1037// Create weight/scale/zp tensors - shared between both paths 1038// For symmetric quantization, use signed types (i4/i8) and no ZP tensor 1039 ov::element::Type weight_type = tensor -> type == GGML_TYPE_MXFP4 ? 1040 ov::element::f4e2m1 : 1041 (layout .is_symmetric ? (layout .is_u4 ? ov::element::i4 : ov::element::i8 ) : 1042 (layout .is_u4 ? ov::element::u4 : ov::element::u8 )); 1043 ov::Shape scale_shape = node_shape ; 1044scale_shape .back () /=layout .weights_per_block ; 1045 1046if (tensor -> type == GGML_TYPE_MXFP4 ) { 1047if (tensor -> ne [2 ]== 1 && tensor -> ne [3 ]== 1 ) { 1048node_shape = {static_cast < size_t > (tensor -> ne [1 ]),static_cast < size_t > (tensor -> ne [0 ])}; 1049 }else { 1050node_shape .clear (); 1051for (int i = GGML_MAX_DIMS - 1 ;i >=0 ;-- i ) { 1052node_shape .push_back (static_cast < size_t > (tensor -> ne [i ])); 1053 } 1054 } 1055 1056scale_shape = node_shape ; 1057scale_shape .back () /=layout .weights_per_block ; 1058 } 1059 1060if (output_base_ptr ) { 1061uint8_t * buf_base = static_cast < uint8_t *> (output_base_ptr ); 1062result .weights = ov::Tensor (weight_type ,node_shape ,buf_base + layout .weights_offset ); 1063const ov::element::Type scale_type = tensor -> type == GGML_TYPE_MXFP4 ? ov::element::f8e8m0 : ov::element::f16 ; 1064result .scales = ov::Tensor (scale_type ,scale_shape ,buf_base + layout .scales_offset ); 1065if (!layout .is_symmetric ) { 1066 ov::element::Type zp_type = 1067zp_is_f16 ? ov::element::f16 : (layout .is_u4 ? ov::element::u4 : ov::element::u8 ); 1068result .zp = ov::Tensor (zp_type ,scale_shape ,buf_base + layout .zp_offset ); 1069 } 1070// else: result.zp remains default-constructed (empty) for symmetric 1071 }else { 1072result .weights = ov::Tensor (weight_type ,node_shape ); 1073const ov::element::Type scale_type = tensor -> type == GGML_TYPE_MXFP4 ? ov::element::f8e8m0 : ov::element::f16 ; 1074result .scales = ov::Tensor (scale_type ,scale_shape ); 1075if (!layout .is_symmetric ) { 1076if (zp_is_f16 ) { 1077result .zp = ov::Tensor (ov::element::f16 ,scale_shape ); 1078 }else { 1079 ov::element::Type zp_type = layout .is_u4 ? ov::element::u4 : ov::element::u8 ; 1080result .zp = ov::Tensor (zp_type ,scale_shape ); 1081 } 1082 } 1083// else: result.zp remains default-constructed (empty) for symmetric 1084 } 1085 1086if (layout .is_requant && layout .requant_type .has_value ()) { 1087result .weight_node = requantize_to_buffers (tensor ,data ,layout .requant_type .value (),layout .weights_per_block , 1088result .weights ,result .scales ,result .zp ); 1089 }else { 1090result .weight_node = 1091extract_quantized_weights (tensor ,data ,result .weights ,result .scales ,result .zp ,use_bias ); 1092 } 1093 1094return result ; 1095} 1096 1097void quantize_q4_0 (const float * x , 1098 ov::Tensor & weights_arr , 1099 ov::Tensor & scales_arr , 1100 ov::Tensor & zp_arr , 1101int64_t k , 1102int64_t qk ) { 1103assert (k %qk == 0 ); 1104const int nb = k /qk ; 1105 1106auto * weights = static_cast < uint8_t *> (weights_arr .data ()); 1107auto * scales = scales_arr .data < ov::element_type_traits < ov::element::f16 > ::value_type > (); 1108bool is_symmetric = (weights_arr .get_element_type ()== ov::element::i4 );// Signed i4 path 1109 1110if (!is_symmetric ) { 1111auto * zp = static_cast < uint8_t *> (zp_arr .data ()); 1112for (int i = 0 ;i < nb ;i ++ ) { 1113float amax = 0.0f ; 1114float max = 0.0f ; 1115for (int j = 0 ;j < qk ;j ++ ) { 1116const float v = x [i * qk + j ]; 1117if (amax < fabsf (v )) { 1118amax = fabsf (v ); 1119max = v ; 1120 } 1121 } 1122const float d = max /-8 ; 1123if (d == 0 ) { 1124scales [i ]= ov::float16 (1.0f ); 1125if (i %2 == 0 ) { 1126zp [i /2 ]= 8 ; 1127 }else { 1128zp [i /2 ] |= (8 <<4 ); 1129 } 1130memset (weights + i * qk /2 ,8 | (8 <<4 ),qk /2 ); 1131continue ; 1132 } 1133const float id = 1.0f /d ; 1134scales [i ]= ov::float16 (d ); 1135if (i %2 == 0 ) { 1136zp [i /2 ]= 8 ; 1137 }else { 1138zp [i /2 ] |= (8 <<4 ); 1139 } 1140for (int j = 0 ;j < qk /2 ;++ j ) { 1141const float x0 = x [i * qk + 2 * j ]* id ; 1142const float x1 = x [i * qk + 2 * j + 1 ]* id ; 1143const uint8_t xi0 = MIN (15 , (int8_t ) (x0 + 8.5f )); 1144const uint8_t xi1 = MIN (15 , (int8_t ) (x1 + 8.5f )); 1145weights [i * qk /2 + j ]= xi0 | (xi1 <<4 ); 1146 } 1147 } 1148 }else { 1149// Symmetric: produce signed i4 values in [-8, 7] 1150for (int i = 0 ;i < nb ;i ++ ) { 1151float amax = 0.0f ; 1152float max = 0.0f ; 1153for (int j = 0 ;j < qk ;j ++ ) { 1154const float v = x [i * qk + j ]; 1155if (amax < fabsf (v )) { 1156amax = fabsf (v ); 1157max = v ; 1158 } 1159 } 1160const float d = max /-8 ; 1161if (d == 0 ) { 1162scales [i ]= ov::float16 (1.0f ); 1163// i4 value 0 packed: 0x00 1164memset (weights + i * qk /2 ,0 ,qk /2 ); 1165continue ; 1166 } 1167const float id = 1.0f /d ; 1168scales [i ]= ov::float16 (d ); 1169for (int j = 0 ;j < qk /2 ;++ j ) { 1170const float x0 = x [i * qk + 2 * j ]* id ; 1171const float x1 = x [i * qk + 2 * j + 1 ]* id ; 1172// Signed i4: range [-8, 7]. Quantize as round(x*id), then pack as 4-bit two's complement. 1173int8_t si0 = (int8_t ) std::max (-8 , std::min (7 , (int )roundf (x0 ))); 1174int8_t si1 = (int8_t ) std::max (-8 , std::min (7 , (int )roundf (x1 ))); 1175weights [i * qk /2 + j ]= (si0 & 0x0F ) | ((si1 & 0x0F ) <<4 ); 1176 } 1177 } 1178 } 1179} 1180 1181void quantize_q8_0 (const float * x , 1182 ov::Tensor & weights_arr , 1183 ov::Tensor & scales_arr , 1184 ov::Tensor & zp_arr , 1185int64_t k , 1186int64_t qk , 1187int64_t block_offset ) { 1188assert (k %qk == 0 ); 1189const int nb = k /qk ; 1190 1191// block_offset lets a caller quantize a chunk of blocks into the right place in the 1192// output buffers (used for streaming requant). x points at this chunk's first block; 1193// outputs are advanced by block_offset blocks. Q8 has one scale/zp per block (no 1194// nibble packing), so any block boundary is safe. 1195auto * weights = static_cast < uint8_t *> (weights_arr .data ())+ block_offset * qk ; 1196auto * scales = scales_arr .data < ov::element_type_traits < ov::element::f16 > ::value_type > ()+ block_offset ; 1197bool is_symmetric = (weights_arr .get_element_type ()== ov::element::i8 );// Signed i8 path 1198 1199if (!is_symmetric ) { 1200auto * zp = static_cast < uint8_t *> (zp_arr .data ())+ block_offset ; 1201for (int i = 0 ;i < nb ;i ++ ) { 1202float amax = 0.0f ; 1203for (int j = 0 ;j < qk ;j ++ ) { 1204const float v = x [i * qk + j ]; 1205amax = std::max (amax ,fabsf (v )); 1206 } 1207const float d = amax /127.0f ; 1208const float id = d ?1.0f /d :0.0f ; 1209scales [i ]= ov::float16 (d ); 1210zp [i ]= 128 ; 1211for (int j = 0 ;j < qk ;++ j ) { 1212const float x0 = x [i * qk + j ]* id ; 1213const int8_t xi0 = roundf (x0 ); 1214weights [i * qk + j ]= (uint8_t ) (xi0 + 128 ); 1215 } 1216 } 1217 }else { 1218// Symmetric: store signed int8 values directly 1219auto * signed_weights = reinterpret_cast < int8_t *> (weights ); 1220for (int i = 0 ;i < nb ;i ++ ) { 1221float amax = 0.0f ; 1222for (int j = 0 ;j < qk ;j ++ ) { 1223const float v = x [i * qk + j ]; 1224amax = std::max (amax ,fabsf (v )); 1225 } 1226const float d = amax /127.0f ; 1227const float id = d ?1.0f /d :0.0f ; 1228scales [i ]= ov::float16 (d ); 1229for (int j = 0 ;j < qk ;++ j ) { 1230const float x0 = x [i * qk + j ]* id ; 1231signed_weights [i * qk + j ]= (int8_t )roundf (x0 ); 1232 } 1233 } 1234 } 1235} 1236 1237void quantize_q8_1 (const float * x , 1238 ov::Tensor & weights_arr , 1239 ov::Tensor & scales_arr , 1240 ov::Tensor & zp_arr , 1241int64_t k , 1242int64_t qk , 1243int64_t block_offset ) { 1244assert (k %qk == 0 ); 1245const int nb = k /qk ; 1246 1247// See quantize_q8_0: block_offset places this chunk's output at the right block. 1248auto * weights = static_cast < uint8_t *> (weights_arr .data ())+ block_offset * qk ; 1249auto * scales = scales_arr .data < ov::element_type_traits < ov::element::f16 > ::value_type > ()+ block_offset ; 1250auto * zp = static_cast < uint8_t *> (zp_arr .data ())+ block_offset ; 1251for (int i = 0 ;i < nb ;i ++ ) { 1252float min = std::numeric_limits < float > ::max (); 1253float max = std::numeric_limits < float > ::lowest (); 1254 1255for (int j = 0 ;j < qk ;j ++ ) { 1256const float v = x [i * qk + j ]; 1257min = std::min (v ,min ); 1258max = std::max (v ,max ); 1259 } 1260 1261const float d = (max - min ) / ((1 <<8 )- 1 ); 1262const float id = d ?1.0f /d :0.0f ; 1263scales [i ]= ov::float16 (d ); 1264// zp = -min / scale (Q8_1 is asymmetric) 1265zp [i ]= (d != 0.0f ) ? (uint8_t ) std::round (- min /d ) :0 ; 1266 1267for (int j = 0 ;j < qk ;++ j ) { 1268const float x0 = (x [i * qk + j ]- min )* id ; 1269const uint8_t xi0 = roundf (x0 ); 1270weights [i * qk + j ]= xi0 ; 1271 } 1272 } 1273}