mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-07 16:37:57 +02:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6ccff9b0e | ||
|
|
cef6c528f8 | ||
|
|
adb9affc96 | ||
|
|
dd53768baf |
@@ -305,6 +305,7 @@ const std::vector<ggml_type> kv_cache_types = {
|
||||
GGML_TYPE_F32,
|
||||
GGML_TYPE_F16,
|
||||
GGML_TYPE_BF16,
|
||||
GGML_TYPE_F8_E4M3,
|
||||
GGML_TYPE_Q8_0,
|
||||
GGML_TYPE_Q4_0,
|
||||
GGML_TYPE_Q4_1,
|
||||
|
||||
+154
-20
@@ -7,6 +7,7 @@ import ast
|
||||
import logging
|
||||
import contextlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
@@ -162,6 +163,7 @@ class ModelBase:
|
||||
self._is_mxfp4 = False
|
||||
self._fp8_as_q8 = fp8_as_q8
|
||||
self._fp8_dequantized: set[str] = set()
|
||||
self._fp8_e4m3_preserved: set[str] = set()
|
||||
|
||||
# Apply heuristics to figure out typical tensor encoding based on first tensor's dtype
|
||||
# NOTE: can't use field "torch_dtype" in config.json, because some finetunes lie.
|
||||
@@ -310,9 +312,29 @@ class ModelBase:
|
||||
logger.info(f" + {scale_name} (per-expert scale, shape [{len(scales)}])")
|
||||
self.gguf_writer.add_tensor(scale_name, scale_vals)
|
||||
|
||||
def _prepare_kv_cache_scales(self):
|
||||
for name in list(self.model_tensors.keys()):
|
||||
if not name.endswith((".k_scale", ".v_scale")):
|
||||
continue
|
||||
|
||||
new_name = self.tensor_map.get_name(key=name, try_suffixes=(".k_scale", ".v_scale"))
|
||||
if new_name is None:
|
||||
continue
|
||||
|
||||
scale = LazyTorchTensor.to_eager(self.model_tensors.pop(name)())
|
||||
if scale.dtype != torch.float32 or scale.numel() != 1:
|
||||
raise ValueError(f"KV cache scale {name!r} must be a scalar FP32 tensor")
|
||||
|
||||
value = float(scale.item())
|
||||
if not math.isfinite(value) or value == 0.0:
|
||||
raise ValueError(f"KV cache scale {name!r} must be finite and nonzero")
|
||||
|
||||
logger.info(f" + {new_name} (KV cache scale, shape [1])")
|
||||
self.gguf_writer.add_tensor(new_name, scale.flatten().numpy())
|
||||
|
||||
def dequant_model(self):
|
||||
# If all quantized tensors were already handled (e.g. pure NVFP4), skip
|
||||
if self._is_nvfp4 and not any(k.endswith((".weight_scale", ".weight_scale_inv")) for k in self.model_tensors):
|
||||
if self._is_nvfp4 and not any(k.endswith((".weight_scale", ".weight_scale_inv", ".input_scale", ".activation_scale", "_activation_scale", ".k_scale", ".v_scale")) for k in self.model_tensors):
|
||||
return
|
||||
|
||||
tensors_to_remove: list[str] = []
|
||||
@@ -487,16 +509,27 @@ class ModelBase:
|
||||
groups = quant_config["config_groups"]
|
||||
nvfp4_compressed_tensors = (
|
||||
quant_format == "nvfp4-pack-quantized"
|
||||
or quant_format == "mixed-precision"
|
||||
and bool(groups)
|
||||
and all(g.get("format") == "nvfp4-pack-quantized" for g in groups.values() if isinstance(g, dict))
|
||||
or bool(groups)
|
||||
and any(g.get("format") == "nvfp4-pack-quantized" for g in groups.values() if isinstance(g, dict))
|
||||
and all(
|
||||
g.get("format") == "nvfp4-pack-quantized"
|
||||
or g.get("format") == "float-quantized"
|
||||
and g.get("weights", {}).get("type") == "float"
|
||||
and g.get("weights", {}).get("num_bits") == 8
|
||||
for g in groups.values() if isinstance(g, dict)
|
||||
)
|
||||
)
|
||||
|
||||
if len(groups) > 1 and not nvfp4_compressed_tensors:
|
||||
raise NotImplementedError("Can't handle multiple config groups for compressed-tensors yet")
|
||||
weight_config = tuple(groups.values())[0]["weights"]
|
||||
|
||||
if quant_format == "float-quantized" or quant_format == "int-quantized" or quant_format == "naive-quantized":
|
||||
if nvfp4_compressed_tensors:
|
||||
# NVFP4 tensors were handled by _generate_nvfp4_tensors and FP8 weight scales by _prepare_fp8_e4m3_tensors.
|
||||
for name in self.model_tensors.keys():
|
||||
if name.endswith((".input_scale", ".k_scale", ".v_scale")):
|
||||
tensors_to_remove.append(name)
|
||||
elif quant_format == "float-quantized" or quant_format == "int-quantized" or quant_format == "naive-quantized":
|
||||
block_size = weight_config.get("block_structure", None)
|
||||
strategy = weight_config.get("strategy")
|
||||
assert strategy == "channel" or strategy == "block"
|
||||
@@ -537,9 +570,6 @@ class ModelBase:
|
||||
tensors_to_remove += [base_name + n for n in ("_packed", "_shape", "_scale")]
|
||||
if (base_name + "_zero_point") in self.model_tensors:
|
||||
tensors_to_remove.append(base_name + "_zero_point")
|
||||
elif nvfp4_compressed_tensors:
|
||||
# Don't error from compressed-tensors, we'll handle them in _generate_nvfp4_tensors
|
||||
pass
|
||||
else:
|
||||
raise NotImplementedError(f"Quant format {quant_format!r} for method {quant_method!r} is not yet supported")
|
||||
elif quant_method == "modelopt":
|
||||
@@ -561,7 +591,7 @@ class ModelBase:
|
||||
tensors_to_remove.append(name)
|
||||
if is_fp8_weight:
|
||||
self._fp8_dequantized.add(weight_name)
|
||||
if name.endswith((".input_scale", ".k_scale", ".v_scale")):
|
||||
if name.endswith((".input_scale", ".activation_scale", "_activation_scale", ".k_scale", ".v_scale")):
|
||||
tensors_to_remove.append(name)
|
||||
elif quant_method is not None:
|
||||
raise NotImplementedError(f"Quant method is not yet supported: {quant_method!r}")
|
||||
@@ -573,6 +603,88 @@ class ModelBase:
|
||||
for name, value in new_tensors.items():
|
||||
self.model_tensors[name] = value
|
||||
|
||||
def _prepare_fp8_e4m3_tensors(self):
|
||||
if self._fp8_as_q8:
|
||||
return
|
||||
|
||||
scale_tensors: dict[str, list[tuple[int, float]] | np.ndarray] = {}
|
||||
input_scale_tensors: dict[str, list[tuple[int, float]] | np.ndarray] = {}
|
||||
consumed: list[str] = []
|
||||
|
||||
for scale_name in list(self.model_tensors.keys()):
|
||||
weight_name = None
|
||||
if scale_name.endswith(".weight_scale"):
|
||||
weight_name = scale_name.removesuffix("_scale")
|
||||
elif scale_name.endswith("_scale_inv"):
|
||||
weight_name = scale_name.removesuffix("_scale_inv")
|
||||
elif scale_name.endswith(".qscale_weight"):
|
||||
weight_name = scale_name.removesuffix("qscale_weight") + "weight"
|
||||
|
||||
if weight_name is None or weight_name not in self.model_tensors:
|
||||
continue
|
||||
|
||||
weight = LazyTorchTensor.to_eager(self.model_tensors[weight_name]())
|
||||
if weight.dtype != torch.float8_e4m3fn or weight.ndim < 2:
|
||||
continue
|
||||
|
||||
scale = LazyTorchTensor.to_eager(self.model_tensors[scale_name]()).float().flatten()
|
||||
if scale.numel() != 1:
|
||||
continue
|
||||
|
||||
weight_prefix = weight_name.removesuffix(".weight")
|
||||
# Transformers fine-grained FP8 uses activation_scale while ModelOpt uses input_scale.
|
||||
# Ref: https://github.com/vllm-project/vllm/blob/main/vllm/model_executor/models/mistral3.py#L357-L361
|
||||
input_scale_name = next((name for name in (
|
||||
weight_prefix + ".input_scale",
|
||||
weight_prefix + ".activation_scale",
|
||||
weight_prefix + "_activation_scale",
|
||||
weight_prefix + ".qscale_act",
|
||||
) if name in self.model_tensors), None)
|
||||
input_scale = None
|
||||
if input_scale_name is not None:
|
||||
input_scale = LazyTorchTensor.to_eager(self.model_tensors[input_scale_name]()).float().flatten()
|
||||
if input_scale.numel() != 1:
|
||||
raise ValueError(f"FP8 input scale {input_scale_name!r} must be a scalar")
|
||||
consumed.append(input_scale_name)
|
||||
|
||||
self._fp8_e4m3_preserved.add(weight_name)
|
||||
consumed.append(scale_name)
|
||||
|
||||
expert_match = re.search(r"\.layers\.(\d+)\.mlp\.experts\.(\d+)\.(gate_proj|up_proj|down_proj)\.weight$", weight_name)
|
||||
if expert_match:
|
||||
bid = int(expert_match.group(1))
|
||||
expert_id = int(expert_match.group(2))
|
||||
proj_type = expert_match.group(3)
|
||||
merged_name = f"model.layers.{bid}.mlp.experts.{proj_type}.weight"
|
||||
new_name = self.map_tensor_name(merged_name)
|
||||
target_name = new_name.replace(".weight", ".scale")
|
||||
entries = scale_tensors.setdefault(target_name, [])
|
||||
assert isinstance(entries, list)
|
||||
cast(list[tuple[int, float]], entries).append((expert_id, float(scale[0])))
|
||||
if input_scale is not None:
|
||||
target_name = new_name.replace(".weight", ".input_scale")
|
||||
entries = input_scale_tensors.setdefault(target_name, [])
|
||||
assert isinstance(entries, list)
|
||||
cast(list[tuple[int, float]], entries).append((expert_id, float(input_scale[0])))
|
||||
else:
|
||||
new_name = self.map_tensor_name(weight_name)
|
||||
scale_tensors[new_name.replace(".weight", ".scale")] = scale.numpy()
|
||||
if input_scale is not None:
|
||||
input_scale_tensors[new_name.replace(".weight", ".input_scale")] = input_scale.numpy()
|
||||
|
||||
for name in consumed:
|
||||
self.model_tensors.pop(name, None)
|
||||
|
||||
for name, values in chain(scale_tensors.items(), input_scale_tensors.items()):
|
||||
if isinstance(values, list):
|
||||
values.sort(key=lambda item: item[0])
|
||||
scale = np.array([item[1] for item in values], dtype=np.float32)
|
||||
else:
|
||||
scale = values.astype(np.float32)
|
||||
if not np.allclose(scale, 1.0, atol=1e-6):
|
||||
logger.info(f" + {name} (FP8 scale, shape [{scale.size}])")
|
||||
self.gguf_writer.add_tensor(name, scale)
|
||||
|
||||
@classmethod
|
||||
def filter_tensors(cls, item: tuple[str, Callable[[], Tensor]]) -> tuple[str, Callable[[], Tensor]] | None:
|
||||
name, gen = item
|
||||
@@ -649,6 +761,8 @@ class ModelBase:
|
||||
|
||||
def tensor_force_quant(self, name: str, new_name: str, bid: int | None, n_dims: int) -> gguf.GGMLQuantizationType | bool:
|
||||
del new_name, bid # unused
|
||||
if name in self._fp8_e4m3_preserved and n_dims >= 2:
|
||||
return gguf.GGMLQuantizationType.F8_E4M3
|
||||
# Force FP8-original tensors to Q8_0 when requested; Q8_0 is faster than F16/BF16.
|
||||
if self._fp8_as_q8 and name in self._fp8_dequantized and n_dims >= 2:
|
||||
return gguf.GGMLQuantizationType.Q8_0
|
||||
@@ -856,9 +970,15 @@ class ModelBase:
|
||||
# per-layer NVFP4/FP8) instead of a single global "NVFP4" value.
|
||||
nvfp4_compressed_tensors = quant_method == "compressed-tensors" and (
|
||||
quant_format == "nvfp4-pack-quantized"
|
||||
or quant_format == "mixed-precision"
|
||||
and bool(quant_groups)
|
||||
and all(g.get("format") == "nvfp4-pack-quantized" for g in quant_groups.values() if isinstance(g, dict))
|
||||
or bool(quant_groups)
|
||||
and any(g.get("format") == "nvfp4-pack-quantized" for g in quant_groups.values() if isinstance(g, dict))
|
||||
and all(
|
||||
g.get("format") == "nvfp4-pack-quantized"
|
||||
or g.get("format") == "float-quantized"
|
||||
and g.get("weights", {}).get("type") == "float"
|
||||
and g.get("weights", {}).get("num_bits") == 8
|
||||
for g in quant_groups.values() if isinstance(g, dict)
|
||||
)
|
||||
)
|
||||
if quant_algo != "NVFP4":
|
||||
if nvfp4_compressed_tensors:
|
||||
@@ -869,6 +989,8 @@ class ModelBase:
|
||||
self._is_nvfp4 = quant_algo in ("NVFP4", "W4A16_NVFP4")
|
||||
self._is_mxfp4 = quant_method == "mxfp4"
|
||||
|
||||
self._prepare_kv_cache_scales()
|
||||
|
||||
# NVFP4 weights are repacked and written directly to gguf_writer.
|
||||
# This must run before dequant_model so NVFP4 tensors are removed
|
||||
# from model_tensors, leaving only non-NVFP4 (e.g. FP8) for dequant.
|
||||
@@ -897,6 +1019,7 @@ class ModelBase:
|
||||
self.model_tensors[input_scale_name] = inverse_scale(self.model_tensors.pop(name))
|
||||
self._generate_nvfp4_tensors()
|
||||
|
||||
self._prepare_fp8_e4m3_tensors()
|
||||
self.dequant_model()
|
||||
|
||||
# Handle empty tensor_map for models with block_count=0 (like MobileNetV5)
|
||||
@@ -910,10 +1033,15 @@ class ModelBase:
|
||||
if name.endswith((".attention.masked_bias", ".attention.bias", ".rotary_emb.inv_freq")):
|
||||
continue
|
||||
|
||||
preserve_fp8_e4m3 = not self._fp8_as_q8 and data_torch.dtype == torch.float8_e4m3fn
|
||||
if preserve_fp8_e4m3:
|
||||
self._fp8_e4m3_preserved.add(name)
|
||||
data_torch = LazyTorchTensor.to_eager(data_torch)
|
||||
|
||||
old_dtype = data_torch.dtype
|
||||
|
||||
# convert any unsupported data types to float32
|
||||
if data_torch.dtype not in (torch.float16, torch.float32):
|
||||
if data_torch.dtype not in (torch.float16, torch.float32) and name not in self._fp8_e4m3_preserved:
|
||||
data_torch = data_torch.to(torch.float32)
|
||||
|
||||
# use the first number-like part of the tensor name as the block id
|
||||
@@ -926,7 +1054,10 @@ class ModelBase:
|
||||
for new_name, data_torch in (self.modify_tensors(data_torch, name, bid)):
|
||||
# TODO: why do we squeeze here?
|
||||
# data = data_torch.squeeze().numpy()
|
||||
data = data_torch.numpy()
|
||||
if preserve_fp8_e4m3:
|
||||
data = data_torch.view(torch.uint8).numpy()
|
||||
else:
|
||||
data = data_torch.numpy()
|
||||
|
||||
n_dims = len(data.shape)
|
||||
data_qtype: gguf.GGMLQuantizationType | bool = self.tensor_force_quant(name, new_name, bid, n_dims)
|
||||
@@ -1010,12 +1141,13 @@ class ModelBase:
|
||||
quantize = data.quantize if isinstance(data, gguf.LazyChunkedTensor) else (
|
||||
lambda qtype, d=data: gguf.quants.quantize(d, qtype))
|
||||
|
||||
try:
|
||||
data = quantize(data_qtype)
|
||||
except gguf.QuantError as e:
|
||||
logger.warning("%s, %s", e, "falling back to F16")
|
||||
data_qtype = gguf.GGMLQuantizationType.F16
|
||||
data = quantize(data_qtype)
|
||||
if not (data_qtype == gguf.GGMLQuantizationType.F8_E4M3 and data.dtype == np.uint8):
|
||||
try:
|
||||
data = quantize(data_qtype)
|
||||
except gguf.QuantError as e:
|
||||
logger.warning("%s, %s", e, "falling back to F16")
|
||||
data_qtype = gguf.GGMLQuantizationType.F16
|
||||
data = quantize(data_qtype)
|
||||
|
||||
shape = gguf.quant_shape_from_byte_shape(data.shape, data_qtype) if data.dtype == np.uint8 else data.shape
|
||||
|
||||
@@ -1049,6 +1181,8 @@ class ModelBase:
|
||||
self.ftype = gguf.LlamaFileType.MOSTLY_NVFP4
|
||||
elif self._is_mxfp4:
|
||||
self.ftype = gguf.LlamaFileType.MOSTLY_MXFP4_MOE
|
||||
elif self._fp8_e4m3_preserved:
|
||||
self.ftype = gguf.LlamaFileType.MOSTLY_F8_E4M3
|
||||
|
||||
# Generate parameter weight class (useful for leader boards) if not yet determined
|
||||
if self.metadata.size_label is None and total_params > 0:
|
||||
|
||||
+5
-1
@@ -381,6 +381,8 @@ extern "C" {
|
||||
GGML_API void ggml_fp32_to_bf16_row_ref(const float *, ggml_bf16_t *, int64_t);
|
||||
GGML_API void ggml_fp32_to_bf16_row(const float *, ggml_bf16_t *, int64_t);
|
||||
|
||||
typedef struct { uint8_t bits; } ggml_fp8_e4m3_t;
|
||||
|
||||
struct ggml_object;
|
||||
struct ggml_context;
|
||||
struct ggml_cgraph;
|
||||
@@ -430,7 +432,8 @@ extern "C" {
|
||||
GGML_TYPE_NVFP4 = 40, // NVFP4 (4 blocks, E4M3 scale)
|
||||
GGML_TYPE_Q1_0 = 41,
|
||||
GGML_TYPE_Q2_0 = 42,
|
||||
GGML_TYPE_COUNT = 43,
|
||||
GGML_TYPE_F8_E4M3 = 43,
|
||||
GGML_TYPE_COUNT = 44,
|
||||
};
|
||||
|
||||
// precision
|
||||
@@ -475,6 +478,7 @@ extern "C" {
|
||||
GGML_FTYPE_MOSTLY_NVFP4 = 26, // except 1d tensors
|
||||
GGML_FTYPE_MOSTLY_Q1_0 = 27, // except 1d tensors
|
||||
GGML_FTYPE_MOSTLY_Q2_0 = 28, // except 1d tensors
|
||||
GGML_FTYPE_MOSTLY_F8_E4M3 = 29, // except 1d tensors
|
||||
};
|
||||
|
||||
// available tensor operations:
|
||||
|
||||
@@ -296,6 +296,12 @@ static const struct ggml_type_traits_cpu type_traits_cpu[GGML_TYPE_COUNT] = {
|
||||
.vec_dot_type = GGML_TYPE_Q8_0,
|
||||
.nrows = 1,
|
||||
},
|
||||
[GGML_TYPE_F8_E4M3] = {
|
||||
.from_float = quantize_row_f8_e4m3,
|
||||
.vec_dot = ggml_vec_dot_f8_e4m3_f32,
|
||||
.vec_dot_type = GGML_TYPE_F32,
|
||||
.nrows = 1,
|
||||
},
|
||||
[GGML_TYPE_Q2_K] = {
|
||||
.from_float = quantize_row_q2_K,
|
||||
.vec_dot = ggml_vec_dot_q2_K_q8_K,
|
||||
@@ -2848,7 +2854,7 @@ struct ggml_cplan ggml_graph_plan(
|
||||
case GGML_OP_CPY:
|
||||
case GGML_OP_DUP:
|
||||
{
|
||||
if (ggml_is_quantized(node->type) ||
|
||||
if (ggml_is_quantized(node->type) || node->type == GGML_TYPE_F8_E4M3 ||
|
||||
// F16 -> BF16 and BF16 -> F16 copies go through intermediate F32
|
||||
(node->src[0]->type == GGML_TYPE_F16 && node->src[1] && node->src[1]->type == GGML_TYPE_BF16) ||
|
||||
(node->src[0]->type == GGML_TYPE_BF16 && node->src[1] && node->src[1]->type == GGML_TYPE_F16) ||
|
||||
@@ -2862,7 +2868,7 @@ struct ggml_cplan ggml_graph_plan(
|
||||
case GGML_OP_ADD_ID:
|
||||
case GGML_OP_ADD1:
|
||||
{
|
||||
if (ggml_is_quantized(node->src[0]->type)) {
|
||||
if (ggml_is_quantized(node->src[0]->type) || node->src[0]->type == GGML_TYPE_F8_E4M3) {
|
||||
cur = ggml_type_size(GGML_TYPE_F32) * node->src[0]->ne[0] * n_tasks;
|
||||
}
|
||||
} break;
|
||||
@@ -2914,7 +2920,7 @@ struct ggml_cplan ggml_graph_plan(
|
||||
} break;
|
||||
case GGML_OP_OUT_PROD:
|
||||
{
|
||||
if (ggml_is_quantized(node->src[0]->type) ||
|
||||
if (ggml_is_quantized(node->src[0]->type) || node->src[0]->type == GGML_TYPE_F8_E4M3 ||
|
||||
node->src[0]->type == GGML_TYPE_F16) {
|
||||
cur = ggml_type_size(GGML_TYPE_F32) * node->src[0]->ne[0] * n_tasks;
|
||||
}
|
||||
|
||||
@@ -564,7 +564,7 @@ void ggml_compute_forward_dup(
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
if (ggml_is_quantized(src0->type) && dst->type == GGML_TYPE_F32) {
|
||||
if ((ggml_is_quantized(src0->type) || src0->type == GGML_TYPE_F8_E4M3) && dst->type == GGML_TYPE_F32) {
|
||||
ggml_compute_forward_dup_from_q(params, dst);
|
||||
break;
|
||||
}
|
||||
@@ -605,7 +605,7 @@ static void ggml_compute_forward_add_q_f32(
|
||||
GGML_ASSERT(nb1 <= nb2);
|
||||
GGML_ASSERT(nb2 <= nb3);
|
||||
|
||||
GGML_ASSERT(ggml_is_quantized(src0->type));
|
||||
GGML_ASSERT(ggml_is_quantized(src0->type) || src0->type == GGML_TYPE_F8_E4M3);
|
||||
GGML_ASSERT(src1->type == GGML_TYPE_F32);
|
||||
|
||||
// rows per thread
|
||||
@@ -673,6 +673,7 @@ void ggml_compute_forward_add(
|
||||
case GGML_TYPE_Q8_0:
|
||||
case GGML_TYPE_MXFP4:
|
||||
case GGML_TYPE_NVFP4:
|
||||
case GGML_TYPE_F8_E4M3:
|
||||
case GGML_TYPE_Q2_K:
|
||||
case GGML_TYPE_Q3_K:
|
||||
case GGML_TYPE_Q4_K:
|
||||
@@ -950,7 +951,7 @@ static void ggml_compute_forward_add1_q_f32(
|
||||
GGML_ASSERT(nb1 <= nb2);
|
||||
GGML_ASSERT(nb2 <= nb3);
|
||||
|
||||
GGML_ASSERT(ggml_is_quantized(src0->type));
|
||||
GGML_ASSERT(ggml_is_quantized(src0->type) || src0->type == GGML_TYPE_F8_E4M3);
|
||||
GGML_ASSERT(dst->type == src0->type);
|
||||
GGML_ASSERT(src1->type == GGML_TYPE_F32);
|
||||
|
||||
@@ -1125,6 +1126,7 @@ void ggml_compute_forward_add1(
|
||||
case GGML_TYPE_Q8_1:
|
||||
case GGML_TYPE_MXFP4:
|
||||
case GGML_TYPE_NVFP4:
|
||||
case GGML_TYPE_F8_E4M3:
|
||||
case GGML_TYPE_Q2_K:
|
||||
case GGML_TYPE_Q3_K:
|
||||
case GGML_TYPE_Q4_K:
|
||||
@@ -1256,6 +1258,7 @@ void ggml_compute_forward_acc(
|
||||
case GGML_TYPE_Q8_1:
|
||||
case GGML_TYPE_MXFP4:
|
||||
case GGML_TYPE_NVFP4:
|
||||
case GGML_TYPE_F8_E4M3:
|
||||
case GGML_TYPE_Q2_K:
|
||||
case GGML_TYPE_Q3_K:
|
||||
case GGML_TYPE_Q4_K:
|
||||
@@ -4658,6 +4661,7 @@ void ggml_compute_forward_out_prod(
|
||||
case GGML_TYPE_Q8_0:
|
||||
case GGML_TYPE_MXFP4:
|
||||
case GGML_TYPE_NVFP4:
|
||||
case GGML_TYPE_F8_E4M3:
|
||||
case GGML_TYPE_Q2_K:
|
||||
case GGML_TYPE_Q3_K:
|
||||
case GGML_TYPE_Q4_K:
|
||||
@@ -4935,6 +4939,7 @@ void ggml_compute_forward_set(
|
||||
case GGML_TYPE_Q8_1:
|
||||
case GGML_TYPE_MXFP4:
|
||||
case GGML_TYPE_NVFP4:
|
||||
case GGML_TYPE_F8_E4M3:
|
||||
case GGML_TYPE_Q2_K:
|
||||
case GGML_TYPE_Q3_K:
|
||||
case GGML_TYPE_Q4_K:
|
||||
@@ -5160,6 +5165,7 @@ void ggml_compute_forward_get_rows(
|
||||
case GGML_TYPE_Q8_1:
|
||||
case GGML_TYPE_MXFP4:
|
||||
case GGML_TYPE_NVFP4:
|
||||
case GGML_TYPE_F8_E4M3:
|
||||
case GGML_TYPE_Q2_K:
|
||||
case GGML_TYPE_Q3_K:
|
||||
case GGML_TYPE_Q4_K:
|
||||
@@ -5917,6 +5923,7 @@ void ggml_compute_forward_clamp(
|
||||
case GGML_TYPE_Q8_1:
|
||||
case GGML_TYPE_MXFP4:
|
||||
case GGML_TYPE_NVFP4:
|
||||
case GGML_TYPE_F8_E4M3:
|
||||
case GGML_TYPE_Q2_K:
|
||||
case GGML_TYPE_Q3_K:
|
||||
case GGML_TYPE_Q4_K:
|
||||
|
||||
@@ -62,6 +62,10 @@ void quantize_row_nvfp4(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, i
|
||||
quantize_row_nvfp4_ref(x, y, k);
|
||||
}
|
||||
|
||||
void quantize_row_f8_e4m3(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t k) {
|
||||
quantize_row_f8_e4m3_ref(x, y, k);
|
||||
}
|
||||
|
||||
//
|
||||
// 2-6 bit quantization in super-blocks
|
||||
//
|
||||
@@ -362,6 +366,23 @@ void ggml_vec_dot_nvfp4_q8_0_generic(int n, float * GGML_RESTRICT s, size_t bs,
|
||||
*s = sumf;
|
||||
}
|
||||
|
||||
void ggml_vec_dot_f8_e4m3_f32(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc) {
|
||||
assert(nrc == 1);
|
||||
UNUSED(nrc);
|
||||
UNUSED(bx);
|
||||
UNUSED(by);
|
||||
UNUSED(bs);
|
||||
|
||||
const ggml_fp8_e4m3_t * GGML_RESTRICT x = vx;
|
||||
const float * GGML_RESTRICT y = vy;
|
||||
|
||||
float sumf = 0.0f;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
sumf += ggml_f8_e4m3_to_fp32(x[i].bits) * y[i];
|
||||
}
|
||||
*s = sumf;
|
||||
}
|
||||
|
||||
void ggml_vec_dot_q5_0_q8_0_generic(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc) {
|
||||
const int qk = QK8_0;
|
||||
const int nb = n / qk;
|
||||
|
||||
@@ -23,6 +23,7 @@ void quantize_row_q8_1(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, in
|
||||
|
||||
void quantize_row_mxfp4(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t k);
|
||||
void quantize_row_nvfp4(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t k);
|
||||
void quantize_row_f8_e4m3(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t k);
|
||||
|
||||
void quantize_row_q2_K(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t k);
|
||||
void quantize_row_q3_K(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t k);
|
||||
@@ -40,6 +41,7 @@ void quantize_row_iq4_xs (const float * GGML_RESTRICT x, void * GGML_RESTRICT y,
|
||||
// Dot product
|
||||
void ggml_vec_dot_q1_0_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
|
||||
void ggml_vec_dot_q2_0_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
|
||||
void ggml_vec_dot_f8_e4m3_f32(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
|
||||
void ggml_vec_dot_q4_0_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
|
||||
void ggml_vec_dot_q4_1_q8_1(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
|
||||
void ggml_vec_dot_q5_0_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
|
||||
|
||||
@@ -552,6 +552,57 @@ static inline uint8_t ggml_fp32_to_ue4m3(float x) {
|
||||
return (uint8_t) ((ue4m3_exp << 3) | ue4m3_man);
|
||||
}
|
||||
|
||||
static inline float ggml_f8_e4m3_to_fp32(uint8_t x) {
|
||||
const uint8_t ax = x & 0x7F;
|
||||
if (ax == 0x7F) {
|
||||
return NAN;
|
||||
}
|
||||
|
||||
const int exp = (ax >> 3) & 0xF;
|
||||
const int man = ax & 0x7;
|
||||
const float value = exp == 0
|
||||
? ldexpf((float) man, -9)
|
||||
: ldexpf(1.0f + (float) man / 8.0f, exp - 7);
|
||||
return x & 0x80 ? -value : value;
|
||||
}
|
||||
|
||||
static inline int ggml_round_to_nearest_even(float x) {
|
||||
const int value = (int) floorf(x);
|
||||
const float fraction = x - value;
|
||||
return fraction > 0.5f || (fraction == 0.5f && (value & 1)) ? value + 1 : value;
|
||||
}
|
||||
|
||||
static inline uint8_t ggml_fp32_to_f8_e4m3(float x) {
|
||||
const uint8_t sign = signbit(x) ? 0x80 : 0;
|
||||
x = fabsf(x);
|
||||
|
||||
if (isnan(x)) {
|
||||
return sign | 0x7F;
|
||||
}
|
||||
if (x == 0.0f) {
|
||||
return sign;
|
||||
}
|
||||
if (isinf(x) || x >= 448.0f) {
|
||||
return sign | 0x7E;
|
||||
}
|
||||
if (x < 0.015625f) {
|
||||
return sign | (uint8_t) ggml_round_to_nearest_even(x * 512.0f);
|
||||
}
|
||||
|
||||
int exp;
|
||||
const float mantissa = frexpf(x, &exp) * 2.0f;
|
||||
int encoded_exp = exp + 6;
|
||||
int encoded_man = ggml_round_to_nearest_even((mantissa - 1.0f) * 8.0f);
|
||||
if (encoded_man == 8) {
|
||||
encoded_man = 0;
|
||||
encoded_exp++;
|
||||
}
|
||||
if (encoded_exp > 15 || (encoded_exp == 15 && encoded_man > 6)) {
|
||||
return sign | 0x7E;
|
||||
}
|
||||
return sign | (uint8_t) (encoded_exp << 3) | (uint8_t) encoded_man;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts brain16 to float32.
|
||||
*
|
||||
|
||||
@@ -416,6 +416,12 @@ void quantize_row_nvfp4_ref(const float * GGML_RESTRICT x, block_nvfp4 * GGML_RE
|
||||
}
|
||||
}
|
||||
|
||||
void quantize_row_f8_e4m3_ref(const float * GGML_RESTRICT x, ggml_fp8_e4m3_t * GGML_RESTRICT y, int64_t k) {
|
||||
for (int64_t i = 0; i < k; ++i) {
|
||||
y[i].bits = ggml_fp32_to_f8_e4m3(x[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void dequantize_row_q1_0(const block_q1_0 * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k) {
|
||||
static const int qk = QK1_0;
|
||||
|
||||
@@ -611,6 +617,12 @@ void dequantize_row_nvfp4(const block_nvfp4 * GGML_RESTRICT x, float * GGML_REST
|
||||
}
|
||||
}
|
||||
|
||||
void dequantize_row_f8_e4m3(const ggml_fp8_e4m3_t * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k) {
|
||||
for (int64_t i = 0; i < k; ++i) {
|
||||
y[i] = ggml_f8_e4m3_to_fp32(x[i].bits);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 2-6 bit quantization in super-blocks
|
||||
//
|
||||
@@ -2311,6 +2323,12 @@ size_t quantize_nvfp4(const float * GGML_RESTRICT src, void * GGML_RESTRICT dst,
|
||||
return nrow * ggml_row_size(GGML_TYPE_NVFP4, n_per_row);
|
||||
}
|
||||
|
||||
size_t quantize_f8_e4m3(const float * GGML_RESTRICT src, void * GGML_RESTRICT dst, int64_t nrow, int64_t n_per_row, const float * quant_weights) {
|
||||
GGML_UNUSED(quant_weights);
|
||||
quantize_row_f8_e4m3_ref(src, dst, (int64_t)nrow*n_per_row);
|
||||
return nrow * ggml_row_size(GGML_TYPE_F8_E4M3, n_per_row);
|
||||
}
|
||||
|
||||
// ====================== Ternary (de)-quantization (BitNet b1.58 and TriLMs)
|
||||
|
||||
void quantize_row_tq1_0_ref(const float * GGML_RESTRICT x, block_tq1_0 * GGML_RESTRICT y, int64_t k) {
|
||||
@@ -5567,6 +5585,16 @@ bool ggml_validate_row_data(enum ggml_type type, const void * data, size_t nbyte
|
||||
GGML_UNUSED(data);
|
||||
GGML_UNUSED(nb);
|
||||
} break;
|
||||
case GGML_TYPE_F8_E4M3:
|
||||
{
|
||||
const ggml_fp8_e4m3_t * q = (const ggml_fp8_e4m3_t *) data;
|
||||
for (size_t i = 0; i < nb; ++i) {
|
||||
if ((q[i].bits & 0x7F) == 0x7F) {
|
||||
fprintf(stderr, "%s: found NaN at index %zu in row of %zu F8_E4M3 values\n", __func__, i, nb);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case GGML_TYPE_Q2_K:
|
||||
{
|
||||
VALIDATE_ROW_DATA_DM_F16_IMPL(block_q2_K, data, nb, d, dmin);
|
||||
|
||||
@@ -25,6 +25,7 @@ GGML_API void quantize_row_q8_1_ref(const float * GGML_RESTRICT x, block_q8_1 *
|
||||
|
||||
GGML_API void quantize_row_mxfp4_ref(const float * GGML_RESTRICT x, block_mxfp4 * GGML_RESTRICT y, int64_t k);
|
||||
GGML_API void quantize_row_nvfp4_ref(const float * GGML_RESTRICT x, block_nvfp4 * GGML_RESTRICT y, int64_t k);
|
||||
GGML_API void quantize_row_f8_e4m3_ref(const float * GGML_RESTRICT x, ggml_fp8_e4m3_t * GGML_RESTRICT y, int64_t k);
|
||||
|
||||
GGML_API void quantize_row_q2_K_ref(const float * GGML_RESTRICT x, block_q2_K * GGML_RESTRICT y, int64_t k);
|
||||
GGML_API void quantize_row_q3_K_ref(const float * GGML_RESTRICT x, block_q3_K * GGML_RESTRICT y, int64_t k);
|
||||
@@ -54,6 +55,7 @@ GGML_API void dequantize_row_q8_0(const block_q8_0 * GGML_RESTRICT x, float * GG
|
||||
|
||||
GGML_API void dequantize_row_mxfp4(const block_mxfp4 * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k);
|
||||
GGML_API void dequantize_row_nvfp4(const block_nvfp4 * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k);
|
||||
GGML_API void dequantize_row_f8_e4m3(const ggml_fp8_e4m3_t * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k);
|
||||
|
||||
GGML_API void dequantize_row_q2_K(const block_q2_K * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k);
|
||||
GGML_API void dequantize_row_q3_K(const block_q3_K * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k);
|
||||
@@ -104,6 +106,7 @@ GGML_API size_t quantize_q8_0(const float * GGML_RESTRICT src, void * GGML_RESTR
|
||||
|
||||
GGML_API size_t quantize_mxfp4(const float * GGML_RESTRICT src, void * GGML_RESTRICT dst, int64_t nrows, int64_t n_per_row, const float * imatrix);
|
||||
GGML_API size_t quantize_nvfp4(const float * GGML_RESTRICT src, void * GGML_RESTRICT dst, int64_t nrows, int64_t n_per_row, const float * imatrix);
|
||||
GGML_API size_t quantize_f8_e4m3(const float * GGML_RESTRICT src, void * GGML_RESTRICT dst, int64_t nrows, int64_t n_per_row, const float * imatrix);
|
||||
|
||||
GGML_API void iq2xs_init_impl(enum ggml_type type);
|
||||
GGML_API void iq2xs_free_impl(enum ggml_type type);
|
||||
|
||||
@@ -765,6 +765,14 @@ static const struct ggml_type_traits type_traits[GGML_TYPE_COUNT] = {
|
||||
.to_float = (ggml_to_float_t) dequantize_row_nvfp4,
|
||||
.from_float_ref = (ggml_from_float_t)quantize_row_nvfp4_ref,
|
||||
},
|
||||
[GGML_TYPE_F8_E4M3] = {
|
||||
.type_name = "f8_e4m3",
|
||||
.blck_size = 1,
|
||||
.type_size = sizeof(ggml_fp8_e4m3_t),
|
||||
.is_quantized = false,
|
||||
.to_float = (ggml_to_float_t) dequantize_row_f8_e4m3,
|
||||
.from_float_ref = (ggml_from_float_t) quantize_row_f8_e4m3_ref,
|
||||
},
|
||||
[GGML_TYPE_Q2_K] = {
|
||||
.type_name = "q2_K",
|
||||
.blck_size = QK_K,
|
||||
@@ -1440,6 +1448,7 @@ enum ggml_type ggml_ftype_to_ggml_type(enum ggml_ftype ftype) {
|
||||
case GGML_FTYPE_MOSTLY_Q8_0: wtype = GGML_TYPE_Q8_0; break;
|
||||
case GGML_FTYPE_MOSTLY_MXFP4: wtype = GGML_TYPE_MXFP4; break;
|
||||
case GGML_FTYPE_MOSTLY_NVFP4: wtype = GGML_TYPE_NVFP4; break;
|
||||
case GGML_FTYPE_MOSTLY_F8_E4M3: wtype = GGML_TYPE_F8_E4M3; break;
|
||||
case GGML_FTYPE_MOSTLY_Q2_K: wtype = GGML_TYPE_Q2_K; break;
|
||||
case GGML_FTYPE_MOSTLY_Q3_K: wtype = GGML_TYPE_Q3_K; break;
|
||||
case GGML_FTYPE_MOSTLY_Q4_K: wtype = GGML_TYPE_Q4_K; break;
|
||||
@@ -8009,6 +8018,7 @@ size_t ggml_quantize_chunk(
|
||||
case GGML_TYPE_Q8_0: result = quantize_q8_0 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break;
|
||||
case GGML_TYPE_MXFP4: result = quantize_mxfp4 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break;
|
||||
case GGML_TYPE_NVFP4: result = quantize_nvfp4 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break;
|
||||
case GGML_TYPE_F8_E4M3: result = quantize_f8_e4m3(src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break;
|
||||
case GGML_TYPE_Q2_K: result = quantize_q2_K (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break;
|
||||
case GGML_TYPE_Q3_K: result = quantize_q3_K (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break;
|
||||
case GGML_TYPE_Q4_K: result = quantize_q4_K (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break;
|
||||
|
||||
@@ -5573,6 +5573,7 @@ class GGMLQuantizationType(IntEnum):
|
||||
NVFP4 = 40
|
||||
Q1_0 = 41
|
||||
Q2_0 = 42
|
||||
F8_E4M3 = 43
|
||||
|
||||
|
||||
class ExpertGatingFuncType(IntEnum):
|
||||
@@ -5629,6 +5630,7 @@ class LlamaFileType(IntEnum):
|
||||
MOSTLY_NVFP4 = 39 # except 1d tensors
|
||||
MOSTLY_Q1_0 = 40 # except 1d tensors
|
||||
MOSTLY_Q2_0 = 41 # except 1d tensors
|
||||
MOSTLY_F8_E4M3 = 42 # except 1d tensors
|
||||
|
||||
GUESSED = 1024 # not specified in the model file
|
||||
|
||||
@@ -5766,6 +5768,7 @@ GGML_QUANT_SIZES: dict[GGMLQuantizationType, tuple[int, int]] = {
|
||||
GGMLQuantizationType.NVFP4: (64, 4 + 32),
|
||||
GGMLQuantizationType.Q1_0: (128, 2 + 16),
|
||||
GGMLQuantizationType.Q2_0: (64, 2 + 16),
|
||||
GGMLQuantizationType.F8_E4M3: (1, 1),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -764,6 +764,23 @@ class NVFP4(__Quant, qtype=GGMLQuantizationType.NVFP4):
|
||||
return (d * vals.astype(np.float32)).reshape(n_super, 64)
|
||||
|
||||
|
||||
class F8_E4M3(__Quant, qtype=GGMLQuantizationType.F8_E4M3):
|
||||
@classmethod
|
||||
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
||||
bits = blocks.astype(np.uint8)
|
||||
sign = np.where(bits & 0x80, -1.0, 1.0)
|
||||
magnitude = bits & 0x7F
|
||||
exponent = magnitude >> 3
|
||||
mantissa = magnitude & 0x07
|
||||
values = np.where(
|
||||
exponent == 0,
|
||||
np.ldexp(mantissa.astype(np.float32), -9),
|
||||
np.ldexp(1.0 + mantissa.astype(np.float32) / 8.0, exponent.astype(np.int32) - 7),
|
||||
)
|
||||
values = np.where(magnitude == 0x7F, np.nan, values)
|
||||
return sign * values
|
||||
|
||||
|
||||
class IQ2_XXS(__Quant, qtype=GGMLQuantizationType.IQ2_XXS):
|
||||
ksigns: bytes = (
|
||||
b"\x00\x81\x82\x03\x84\x05\x06\x87\x88\x09\x0a\x8b\x0c\x8d\x8e\x0f"
|
||||
|
||||
@@ -84,6 +84,7 @@ byteswap_tensors = {
|
||||
gguf.GGMLQuantizationType.TQ2_0: byteswap_tq2_0,
|
||||
gguf.GGMLQuantizationType.MXFP4: byteswap_noop,
|
||||
gguf.GGMLQuantizationType.NVFP4: byteswap_noop,
|
||||
gguf.GGMLQuantizationType.F8_E4M3: byteswap_noop,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -156,6 +156,7 @@ extern "C" {
|
||||
LLAMA_FTYPE_MOSTLY_NVFP4 = 39, // except 1d tensors
|
||||
LLAMA_FTYPE_MOSTLY_Q1_0 = 40, // except 1d tensors
|
||||
LLAMA_FTYPE_MOSTLY_Q2_0 = 41, // except 1d tensors
|
||||
LLAMA_FTYPE_MOSTLY_F8_E4M3 = 42, // except 1d tensors
|
||||
|
||||
LLAMA_FTYPE_GUESSED = 1024, // not specified in the model file
|
||||
};
|
||||
|
||||
@@ -3696,6 +3696,33 @@ llama_context * llama_init_from_model(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const bool fp8_cache = params.type_k == GGML_TYPE_F8_E4M3 || params.type_v == GGML_TYPE_F8_E4M3;
|
||||
if (fp8_cache && (model->hparams.is_mla() || model->arch == LLM_ARCH_DEEPSEEK4)) {
|
||||
LLAMA_LOG_ERROR("%s: FP8 cache is not supported for MLA models\n", __func__);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (fp8_cache) {
|
||||
for (uint32_t il = 0; il < model->hparams.n_layer_all; ++il) {
|
||||
if (!model->hparams.has_kv(il)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (ggml_tensor * scale : { model->layers[il].k_cache_scale, model->layers[il].v_cache_scale }) {
|
||||
if (!scale || !scale->data) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float value;
|
||||
ggml_backend_tensor_get(scale, &value, 0, sizeof(value));
|
||||
if (!std::isfinite(value) || value == 0.0f) {
|
||||
LLAMA_LOG_ERROR("%s: FP8 cache scale '%s' must be finite and nonzero\n", __func__, ggml_get_name(scale));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ggml_is_quantized(params.type_v) && params.flash_attn_type != LLAMA_FLASH_ATTN_TYPE_ENABLED) {
|
||||
if (params.flash_attn_type == LLAMA_FLASH_ATTN_TYPE_AUTO) {
|
||||
LLAMA_LOG_INFO("%s: enabling flash_attn since it is required for quantized V cache\n", __func__);
|
||||
|
||||
+64
-12
@@ -1724,12 +1724,16 @@ ggml_tensor * llm_graph_context::build_ffn(
|
||||
return false;
|
||||
};
|
||||
|
||||
GGML_ASSERT(!up_s || !up_b || !up || up->type != GGML_TYPE_NVFP4);
|
||||
GGML_ASSERT(!gate_s || !gate_b || !gate || gate->type != GGML_TYPE_NVFP4);
|
||||
GGML_ASSERT(!down_s || !down_b || !down || down->type != GGML_TYPE_NVFP4);
|
||||
GGML_ASSERT(!up_s || !up || up->type != GGML_TYPE_NVFP4 || !has_lora(up));
|
||||
GGML_ASSERT(!gate_s || !gate || gate->type != GGML_TYPE_NVFP4 || !has_lora(gate));
|
||||
GGML_ASSERT(!down_s || !down || down->type != GGML_TYPE_NVFP4 || !has_lora(down));
|
||||
auto is_scaled_low_precision = [](ggml_tensor * w) {
|
||||
return w && (w->type == GGML_TYPE_NVFP4 || w->type == GGML_TYPE_F8_E4M3);
|
||||
};
|
||||
|
||||
GGML_ASSERT(!up_s || !up_b || !is_scaled_low_precision(up));
|
||||
GGML_ASSERT(!gate_s || !gate_b || !is_scaled_low_precision(gate));
|
||||
GGML_ASSERT(!down_s || !down_b || !is_scaled_low_precision(down));
|
||||
GGML_ASSERT(!up_s || !is_scaled_low_precision(up) || !has_lora(up));
|
||||
GGML_ASSERT(!gate_s || !is_scaled_low_precision(gate) || !has_lora(gate));
|
||||
GGML_ASSERT(!down_s || !is_scaled_low_precision(down) || !has_lora(down));
|
||||
|
||||
ggml_tensor * tmp = up ? build_lora_mm(up, cur) : cur;
|
||||
cb(tmp, "ffn_up", il);
|
||||
@@ -2784,6 +2788,49 @@ llm_graph_input_attn_kv * llm_graph_context::build_attn_inp_kv() const {
|
||||
return (llm_graph_input_attn_kv *) res->add_input(std::move(inp));
|
||||
}
|
||||
|
||||
static void build_attn_scale_fp8_inputs(
|
||||
ggml_context * ctx,
|
||||
const llama_kv_cache_context * mctx,
|
||||
ggml_tensor * & q,
|
||||
ggml_tensor * & k,
|
||||
ggml_tensor * & v,
|
||||
int32_t il) {
|
||||
if (mctx->type_k() == GGML_TYPE_F8_E4M3) {
|
||||
// Store K/s and move its dequant scale to Q: (Q*s) dot (K/s) = Q dot K.
|
||||
ggml_tensor * scale = mctx->get_k_scale(il);
|
||||
if (scale) {
|
||||
q = ggml_mul(ctx, q, scale);
|
||||
if (k) {
|
||||
k = ggml_div(ctx, k, scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mctx->type_v() == GGML_TYPE_F8_E4M3 && v) {
|
||||
// Store V/s here and restore its dequant scale after the weighted sum.
|
||||
ggml_tensor * scale = mctx->get_v_scale(il);
|
||||
if (scale) {
|
||||
v = ggml_div(ctx, v, scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ggml_tensor * build_attn_scale_fp8_output(
|
||||
ggml_context * ctx,
|
||||
const llama_kv_cache_context * mctx,
|
||||
ggml_tensor * cur,
|
||||
int32_t il) {
|
||||
if (mctx->type_v() == GGML_TYPE_F8_E4M3) {
|
||||
// P*(V/s)*s = P*V.
|
||||
ggml_tensor * scale = mctx->get_v_scale(il);
|
||||
if (scale) {
|
||||
cur = ggml_mul(ctx, cur, scale);
|
||||
}
|
||||
}
|
||||
|
||||
return cur;
|
||||
}
|
||||
|
||||
ggml_tensor * llm_graph_context::build_attn(
|
||||
llm_graph_input_attn_kv * inp,
|
||||
ggml_tensor * wo,
|
||||
@@ -2808,6 +2855,10 @@ ggml_tensor * llm_graph_context::build_attn(
|
||||
v_cur = llama_mul_mat_hadamard(ctx0, v_cur, inp->self_v_rot);
|
||||
}
|
||||
|
||||
const auto * mctx_cur = inp->mctx;
|
||||
|
||||
build_attn_scale_fp8_inputs(ctx0, mctx_cur, q_cur, k_cur, v_cur, il);
|
||||
|
||||
// these nodes are added to the graph together so that they are not reordered
|
||||
// by doing so, the number of splits in the graph is reduced
|
||||
// expand k later to enable rope fusion which directly writes into k-v cache
|
||||
@@ -2815,8 +2866,6 @@ ggml_tensor * llm_graph_context::build_attn(
|
||||
ggml_build_forward_expand(gf, v_cur);
|
||||
ggml_build_forward_expand(gf, k_cur);
|
||||
|
||||
const auto * mctx_cur = inp->mctx;
|
||||
|
||||
// store to KV cache
|
||||
{
|
||||
const auto & k_idxs = inp->get_k_idxs();
|
||||
@@ -2833,6 +2882,7 @@ ggml_tensor * llm_graph_context::build_attn(
|
||||
ggml_tensor * v = mctx_cur->get_v(ctx0, il);
|
||||
|
||||
ggml_tensor * cur = build_attn_mha(q, k, v, kq_b, kq_mask, sinks, v_mla, 0, kq_scale, il);
|
||||
cur = build_attn_scale_fp8_output(ctx0, mctx_cur, cur, il);
|
||||
cb(cur, "kqv_out", il);
|
||||
|
||||
if (inp->self_v_rot) {
|
||||
@@ -3052,6 +3102,11 @@ ggml_tensor * llm_graph_context::build_attn(
|
||||
}
|
||||
}
|
||||
|
||||
const auto * mctx_iswa = inp->mctx;
|
||||
const auto * mctx_cur = is_swa ? mctx_iswa->get_swa() : mctx_iswa->get_base();
|
||||
|
||||
build_attn_scale_fp8_inputs(ctx0, mctx_cur, q_cur, k_cur, v_cur, il);
|
||||
|
||||
// these nodes are added to the graph together so that they are not reordered
|
||||
// by doing so, the number of splits in the graph is reduced
|
||||
ggml_build_forward_expand(gf, q_cur);
|
||||
@@ -3064,10 +3119,6 @@ ggml_tensor * llm_graph_context::build_attn(
|
||||
ggml_build_forward_expand(gf, v_cur);
|
||||
}
|
||||
|
||||
const auto * mctx_iswa = inp->mctx;
|
||||
|
||||
const auto * mctx_cur = is_swa ? mctx_iswa->get_swa() : mctx_iswa->get_base();
|
||||
|
||||
// optionally store to KV cache
|
||||
if (k_cur) {
|
||||
const auto & k_idxs = is_swa ? inp->get_k_idxs_swa() : inp->get_k_idxs();
|
||||
@@ -3088,6 +3139,7 @@ ggml_tensor * llm_graph_context::build_attn(
|
||||
ggml_tensor * v = mctx_cur->get_v(ctx0, il);
|
||||
|
||||
ggml_tensor * cur = build_attn_mha(q, k, v, kq_b, kq_mask, sinks, v_mla, 0, kq_scale, il);
|
||||
cur = build_attn_scale_fp8_output(ctx0, mctx_cur, cur, il);
|
||||
cb(cur, "kqv_out", il);
|
||||
|
||||
if (v_rot) {
|
||||
|
||||
@@ -318,6 +318,7 @@ llama_kv_cache::llama_kv_cache(
|
||||
LLAMA_LOG_WARN("%s: attention rotation force disabled (LLAMA_ATTN_ROT_DISABLE)\n", __func__);
|
||||
}
|
||||
|
||||
// Do not rotate scalar FP8 caches. Their static scales are calibrated on unrotated K and V.
|
||||
attn_rot_k =
|
||||
!attn_rot_disable &&
|
||||
n_embd_head_k_all > 0 &&
|
||||
@@ -1315,6 +1316,14 @@ ggml_tensor * llama_kv_cache::get_v(ggml_context * ctx, int32_t il, uint32_t n_k
|
||||
ggml_row_size(v->type, kv_size*n_embd_v_gqa)*sinfo.s0);
|
||||
}
|
||||
|
||||
ggml_tensor * llama_kv_cache::get_k_scale(int32_t il) const {
|
||||
return model.layers[il].k_cache_scale;
|
||||
}
|
||||
|
||||
ggml_tensor * llama_kv_cache::get_v_scale(int32_t il) const {
|
||||
return model.layers[il].v_cache_scale;
|
||||
}
|
||||
|
||||
ggml_tensor * llama_kv_cache::cpy_k(ggml_context * ctx, ggml_tensor * k_cur, ggml_tensor * k_idxs, int32_t il, const slot_info & sinfo) const {
|
||||
GGML_UNUSED(sinfo);
|
||||
|
||||
@@ -2758,6 +2767,14 @@ ggml_tensor * llama_kv_cache_context::get_v(ggml_context * ctx, int32_t il) cons
|
||||
return kv->get_v(ctx, il, n_kv, sinfos[i_cur]);
|
||||
}
|
||||
|
||||
ggml_tensor * llama_kv_cache_context::get_k_scale(int32_t il) const {
|
||||
return kv->get_k_scale(il);
|
||||
}
|
||||
|
||||
ggml_tensor * llama_kv_cache_context::get_v_scale(int32_t il) const {
|
||||
return kv->get_v_scale(il);
|
||||
}
|
||||
|
||||
ggml_tensor * llama_kv_cache_context::cpy_k(ggml_context * ctx, ggml_tensor * k_cur, ggml_tensor * k_idxs, int32_t il) const {
|
||||
return kv->cpy_k(ctx, k_cur, k_idxs, il, sinfos[i_cur]);
|
||||
}
|
||||
|
||||
@@ -189,6 +189,9 @@ public:
|
||||
ggml_tensor * get_k(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const;
|
||||
ggml_tensor * get_v(ggml_context * ctx, int32_t il, uint32_t n_kv, const slot_info & sinfo) const;
|
||||
|
||||
ggml_tensor * get_k_scale(int32_t il) const;
|
||||
ggml_tensor * get_v_scale(int32_t il) const;
|
||||
|
||||
// store k_cur and v_cur in the cache based on the provided head location
|
||||
ggml_tensor * cpy_k(ggml_context * ctx, ggml_tensor * k_cur, ggml_tensor * k_idxs, int32_t il, const slot_info & sinfo) const;
|
||||
ggml_tensor * cpy_v(ggml_context * ctx, ggml_tensor * v_cur, ggml_tensor * v_idxs, int32_t il, const slot_info & sinfo) const;
|
||||
@@ -398,6 +401,9 @@ public:
|
||||
ggml_tensor * get_k(ggml_context * ctx, int32_t il) const;
|
||||
ggml_tensor * get_v(ggml_context * ctx, int32_t il) const;
|
||||
|
||||
ggml_tensor * get_k_scale(int32_t il) const;
|
||||
ggml_tensor * get_v_scale(int32_t il) const;
|
||||
|
||||
// store k_cur and v_cur in the cache based on the provided head location
|
||||
// note: the heads in k_cur and v_cur should be laid out contiguously in memory
|
||||
// - k_cur [n_embd_head_k, n_head_k, n_tokens]
|
||||
|
||||
@@ -39,6 +39,7 @@ const char * llama_ftype_name(llama_ftype ftype) {
|
||||
case LLAMA_FTYPE_MOSTLY_BF16: name = LLAMA_FTYPE_PREFIX "BF16"; break;
|
||||
case LLAMA_FTYPE_MOSTLY_Q1_0: name = LLAMA_FTYPE_PREFIX "Q1_0"; break;
|
||||
case LLAMA_FTYPE_MOSTLY_Q2_0: name = LLAMA_FTYPE_PREFIX "Q2_0"; break;
|
||||
case LLAMA_FTYPE_MOSTLY_F8_E4M3: name = LLAMA_FTYPE_PREFIX "F8_E4M3"; break;
|
||||
case LLAMA_FTYPE_MOSTLY_Q4_0: name = LLAMA_FTYPE_PREFIX "Q4_0"; break;
|
||||
case LLAMA_FTYPE_MOSTLY_Q4_1: name = LLAMA_FTYPE_PREFIX "Q4_1"; break;
|
||||
case LLAMA_FTYPE_MOSTLY_Q5_0: name = LLAMA_FTYPE_PREFIX "Q5_0"; break;
|
||||
@@ -772,6 +773,7 @@ llama_model_loader::llama_model_loader(
|
||||
case GGML_TYPE_NVFP4: ftype = LLAMA_FTYPE_MOSTLY_NVFP4; break;
|
||||
case GGML_TYPE_Q1_0: ftype = LLAMA_FTYPE_MOSTLY_Q1_0; break;
|
||||
case GGML_TYPE_Q2_0: ftype = LLAMA_FTYPE_MOSTLY_Q2_0; break;
|
||||
case GGML_TYPE_F8_E4M3: ftype = LLAMA_FTYPE_MOSTLY_F8_E4M3; break;
|
||||
default:
|
||||
{
|
||||
LLAMA_LOG_WARN("%s: unknown type %s\n", __func__, ggml_type_name(type_max));
|
||||
|
||||
+13
-3
@@ -1524,6 +1524,16 @@ bool llama_model_base::load_tensors(llama_model_loader & ml) {
|
||||
for (int i = 0; i < n_layer_all; ++i) {
|
||||
auto & layer = layers[i];
|
||||
|
||||
if (hparams.has_kv(i)) {
|
||||
layer.k_cache_scale = create_tensor(tn(LLM_TENSOR_ATTN_K, "k_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
||||
layer.v_cache_scale = create_tensor(tn(LLM_TENSOR_ATTN_V, "v_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
||||
|
||||
if ((layer.k_cache_scale && layer.k_cache_scale->type != GGML_TYPE_F32) ||
|
||||
(layer.v_cache_scale && layer.v_cache_scale->type != GGML_TYPE_F32)) {
|
||||
throw std::runtime_error(format("KV cache scales for layer %d must be F32", i));
|
||||
}
|
||||
}
|
||||
|
||||
// attention weight scales (per-tensor, shape {1})
|
||||
if (!layer.wq_s && layer.wq) {
|
||||
layer.wq_s = create_tensor(tn(LLM_TENSOR_ATTN_Q, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
||||
@@ -1661,7 +1671,7 @@ bool llama_model_base::load_tensors(llama_model_loader & ml) {
|
||||
}
|
||||
}
|
||||
// output scales
|
||||
if (output && output->type == GGML_TYPE_NVFP4) {
|
||||
if (output && (output->type == GGML_TYPE_NVFP4 || output->type == GGML_TYPE_F8_E4M3)) {
|
||||
// weight scale
|
||||
if (!output_s) {
|
||||
output_s = create_tensor(tn(LLM_TENSOR_OUTPUT, "scale"), {1}, TENSOR_NOT_REQUIRED);
|
||||
@@ -1674,11 +1684,11 @@ bool llama_model_base::load_tensors(llama_model_loader & ml) {
|
||||
}
|
||||
ml.done_getting_tensors();
|
||||
|
||||
// Tied NVFP4 output is valid when no separate LM-head scale tensors are present.
|
||||
// Tied low-precision output is valid when no separate LM-head scale tensors are present.
|
||||
// If sidecar scales exist, the output weight must be an actual output tensor.
|
||||
GGML_ASSERT(!(output && tok_embd &&
|
||||
strcmp(output->name, tok_embd->name) == 0 &&
|
||||
output->type == GGML_TYPE_NVFP4 &&
|
||||
(output->type == GGML_TYPE_NVFP4 || output->type == GGML_TYPE_F8_E4M3) &&
|
||||
(output_s || output_in_s)));
|
||||
// populate tensors_by_name
|
||||
for (auto & [_, ctx_ptr] : ml.ctx_map) {
|
||||
|
||||
@@ -276,6 +276,8 @@ struct llama_layer {
|
||||
struct ggml_tensor * wq = nullptr;
|
||||
struct ggml_tensor * wk = nullptr;
|
||||
struct ggml_tensor * wv = nullptr;
|
||||
struct ggml_tensor * k_cache_scale = nullptr;
|
||||
struct ggml_tensor * v_cache_scale = nullptr;
|
||||
struct ggml_tensor * wo = nullptr;
|
||||
struct ggml_tensor * wqkv = nullptr;
|
||||
struct ggml_tensor * wg = nullptr;
|
||||
|
||||
@@ -84,7 +84,7 @@ static void init_tensor_uniform(ggml_tensor * tensor, float min = -1.0f, float m
|
||||
|
||||
if (tensor->type == GGML_TYPE_F32 || tensor->type == GGML_TYPE_I32) {
|
||||
ggml_backend_tensor_set(tensor, data.data(), 0, nels * sizeof(float));
|
||||
} else if (ggml_is_quantized(tensor->type) || tensor->type == GGML_TYPE_F16 || tensor->type == GGML_TYPE_BF16) {
|
||||
} else if (ggml_is_quantized(tensor->type) || tensor->type == GGML_TYPE_F8_E4M3 || tensor->type == GGML_TYPE_F16 || tensor->type == GGML_TYPE_BF16) {
|
||||
GGML_ASSERT(nels % ggml_blck_size(tensor->type) == 0);
|
||||
|
||||
// dummy importance matrix
|
||||
@@ -258,7 +258,7 @@ static std::vector<float> tensor_to_float(const ggml_tensor * t) {
|
||||
const auto * tt = ggml_get_type_traits(t->type);
|
||||
size_t bs = ggml_blck_size(t->type);
|
||||
std::vector<float> vq(ggml_blck_size(t->type));
|
||||
bool quantized = ggml_is_quantized(t->type);
|
||||
bool uses_type_conversion = ggml_is_quantized(t->type) || t->type == GGML_TYPE_F8_E4M3;
|
||||
|
||||
// access elements by index to avoid gaps in views
|
||||
for (int64_t i3 = 0; i3 < t->ne[3]; i3++) {
|
||||
@@ -280,7 +280,7 @@ static std::vector<float> tensor_to_float(const ggml_tensor * t) {
|
||||
tv.push_back((float)*(int16_t *) &buf[i]);
|
||||
} else if (t->type == GGML_TYPE_I8) {
|
||||
tv.push_back((float)*(int8_t *) &buf[i]);
|
||||
} else if (quantized) {
|
||||
} else if (uses_type_conversion) {
|
||||
tt->to_float(&buf[i], vq.data(), bs);
|
||||
tv.insert(tv.end(), vq.begin(), vq.end());
|
||||
} else {
|
||||
@@ -4639,6 +4639,9 @@ struct test_mul_mat : public test_case {
|
||||
if ((type_a == GGML_TYPE_MXFP4 || type_a == GGML_TYPE_NVFP4) && backend_has_feature(backend, "BLACKWELL_NATIVE_FP4")) {
|
||||
return 2e-2;
|
||||
}
|
||||
if (type_a == GGML_TYPE_F8_E4M3 && backend_has_feature(backend, "NATIVE_FP8")) {
|
||||
return 5e-3;
|
||||
}
|
||||
return max_nmse_err();
|
||||
}
|
||||
|
||||
@@ -4840,6 +4843,9 @@ struct test_mul_mat_id : public test_case {
|
||||
if ((type_a == GGML_TYPE_MXFP4 || type_a == GGML_TYPE_NVFP4) && backend_has_feature(backend, "BLACKWELL_NATIVE_FP4")) {
|
||||
return 2e-2;
|
||||
}
|
||||
if (type_a == GGML_TYPE_F8_E4M3 && backend_has_feature(backend, "NATIVE_FP8")) {
|
||||
return 5e-3;
|
||||
}
|
||||
return max_nmse_err();
|
||||
}
|
||||
|
||||
@@ -8559,6 +8565,7 @@ static const ggml_type all_types[] = {
|
||||
GGML_TYPE_Q1_0,
|
||||
GGML_TYPE_Q2_0,
|
||||
GGML_TYPE_MXFP4, GGML_TYPE_NVFP4,
|
||||
GGML_TYPE_F8_E4M3,
|
||||
GGML_TYPE_Q2_K, GGML_TYPE_Q3_K,
|
||||
GGML_TYPE_Q4_K, GGML_TYPE_Q5_K,
|
||||
GGML_TYPE_Q6_K,
|
||||
@@ -8578,6 +8585,7 @@ static const ggml_type base_types[] = {
|
||||
GGML_TYPE_Q4_1, // for I8MM tests
|
||||
GGML_TYPE_Q4_K,
|
||||
GGML_TYPE_MXFP4, GGML_TYPE_NVFP4, // TODO: or "other"
|
||||
GGML_TYPE_F8_E4M3,
|
||||
GGML_TYPE_IQ2_XXS
|
||||
};
|
||||
|
||||
@@ -10338,6 +10346,9 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
|
||||
test_cases.emplace_back(new test_flash_attn_ext(128, 64, 4, {1, 1}, 128, 2, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q2_0, GGML_TYPE_Q4_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(64, 128, 4, {1, 1}, 128, 2, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q4_0, GGML_TYPE_Q2_0));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(128, 64, 4, {1, 1}, 64, 2, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q2_0, GGML_TYPE_F16));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(64, 64, 4, {1, 1}, 128, 2, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_F8_E4M3, GGML_TYPE_F8_E4M3));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(64, 64, 4, {1, 1}, 128, 2, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_F8_E4M3, GGML_TYPE_F16));
|
||||
test_cases.emplace_back(new test_flash_attn_ext(64, 64, 4, {1, 1}, 128, 2, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_F16, GGML_TYPE_F8_E4M3));
|
||||
|
||||
// q8_0 KV cases: decode and prompt batches, KV pad, permuted KV, feature flags, and long context
|
||||
test_cases.emplace_back(new test_flash_attn_ext(256, 256, 2, {16, 1}, 113, 1, true, false, 0, 0, GGML_PREC_F32, GGML_TYPE_Q8_0, GGML_TYPE_Q8_0));
|
||||
@@ -10434,7 +10445,7 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
|
||||
continue;
|
||||
}
|
||||
for (bool with_lane_scale : {false, true}) {
|
||||
if (with_lane_scale && type != GGML_TYPE_NVFP4) {
|
||||
if (with_lane_scale && type != GGML_TYPE_NVFP4 && type != GGML_TYPE_F8_E4M3) {
|
||||
continue;
|
||||
}
|
||||
test_cases.emplace_back(new test_mul_mat_vec_fusion(type, glu_op, 1, 32, 256,
|
||||
|
||||
@@ -86,10 +86,9 @@ static float dot_product(const float * a1, const float * a2, size_t test_size) {
|
||||
static float dot_product_error(const ggml_type_traits * qfns, const ggml_type_traits_cpu * qfns_cpu, size_t test_size, const float * test_data1, const float * test_data2) {
|
||||
GGML_UNUSED(qfns);
|
||||
|
||||
std::vector<uint8_t> tmp_q1(2*test_size);
|
||||
std::vector<uint8_t> tmp_q2(2*test_size);
|
||||
|
||||
const auto * vdot = ggml_get_type_traits_cpu(qfns_cpu->vec_dot_type);
|
||||
std::vector<uint8_t> tmp_q1(2*test_size);
|
||||
std::vector<uint8_t> tmp_q2(ggml_row_size(qfns_cpu->vec_dot_type, test_size));
|
||||
|
||||
qfns_cpu->from_float(test_data1, tmp_q1.data(), test_size);
|
||||
vdot->from_float(test_data2, tmp_q2.data(), test_size);
|
||||
@@ -126,6 +125,62 @@ static int test_vec_dot_f32(bool verbose) {
|
||||
return num_failed;
|
||||
}
|
||||
|
||||
static float f8_e4m3_to_fp32_ref(uint8_t value) {
|
||||
const uint8_t magnitude = value & 0x7F;
|
||||
if (magnitude == 0x7F) {
|
||||
return NAN;
|
||||
}
|
||||
const int exponent = magnitude >> 3;
|
||||
const int mantissa = magnitude & 0x07;
|
||||
const float result = exponent == 0
|
||||
? ldexpf((float) mantissa, -9)
|
||||
: ldexpf(1.0f + (float) mantissa / 8.0f, exponent - 7);
|
||||
return value & 0x80 ? -result : result;
|
||||
}
|
||||
|
||||
static int test_f8_e4m3(bool verbose) {
|
||||
static_assert(sizeof(ggml_fp8_e4m3_t) == 1);
|
||||
|
||||
const auto * traits = ggml_get_type_traits(GGML_TYPE_F8_E4M3);
|
||||
const auto * traits_cpu = ggml_get_type_traits_cpu(GGML_TYPE_F8_E4M3);
|
||||
std::vector<uint8_t> encoded(256);
|
||||
std::vector<uint8_t> roundtrip(256);
|
||||
std::vector<float> decoded(256);
|
||||
int num_failed = 0;
|
||||
|
||||
const bool traits_failed = traits->blck_size != 1 || traits->type_size != sizeof(ggml_fp8_e4m3_t) || traits->is_quantized;
|
||||
num_failed += traits_failed;
|
||||
if (verbose || traits_failed) {
|
||||
printf("f8_e4m3 scalar type traits: %s\n", RESULT_STR[traits_failed]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
encoded[i] = (uint8_t) i;
|
||||
}
|
||||
traits->to_float(encoded.data(), decoded.data(), decoded.size());
|
||||
traits_cpu->from_float(decoded.data(), roundtrip.data(), decoded.size());
|
||||
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
const float expected = f8_e4m3_to_fp32_ref((uint8_t) i);
|
||||
const bool is_nan = (i & 0x7F) == 0x7F;
|
||||
const bool decode_failed = is_nan ? !isnan(decoded[i])
|
||||
: decoded[i] != expected || (expected == 0.0f && signbit(decoded[i]) != signbit(expected));
|
||||
const bool encode_failed = !is_nan && roundtrip[i] != encoded[i];
|
||||
if (decode_failed || encode_failed) {
|
||||
num_failed++;
|
||||
if (verbose) {
|
||||
printf("f8_e4m3 code 0x%02x failed: decoded=%f expected=%f roundtrip=0x%02x\n",
|
||||
i, decoded[i], expected, roundtrip[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose || num_failed) {
|
||||
printf("f8_e4m3 exhaustive conversion: %s (%d failures)\n", RESULT_STR[num_failed != 0], num_failed);
|
||||
}
|
||||
return num_failed;
|
||||
}
|
||||
|
||||
static int test_vec_dot_q(bool verbose) {
|
||||
int num_failed = 0;
|
||||
|
||||
@@ -220,6 +275,7 @@ int main(int argc, char * argv[]) {
|
||||
int num_failed = 0;
|
||||
|
||||
num_failed += test_vec_dot_f32(verbose);
|
||||
num_failed += test_f8_e4m3(verbose);
|
||||
num_failed += test_vec_dot_q(verbose);
|
||||
|
||||
if (num_failed || verbose) {
|
||||
|
||||
@@ -498,6 +498,9 @@ static ggml_type ggml_type_from_name(const std::string & s) {
|
||||
if (s == "bf16") {
|
||||
return GGML_TYPE_BF16;
|
||||
}
|
||||
if (s == "f8_e4m3") {
|
||||
return GGML_TYPE_F8_E4M3;
|
||||
}
|
||||
if (s == "q8_0") {
|
||||
return GGML_TYPE_Q8_0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user