From 42ab1c12f10778aa6d4118eaaa0c3e1ec3b4611c Mon Sep 17 00:00:00 2001 From: leejet Date: Sun, 27 Sep 2026 20:43:06 +0800 Subject: [PATCH] fix: keep scaled INT8 convrot matmuls on Vulkan (#2070) --- docs/int8_convrot.md | 4 ++-- src/core/ggml_extend.cpp | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/int8_convrot.md b/docs/int8_convrot.md index 3b65113d..c9611b32 100644 --- a/docs/int8_convrot.md +++ b/docs/int8_convrot.md @@ -84,13 +84,13 @@ The floating-point output is reconstructed as Y[r, o] ~= A[r, o] * s_x[r] * s_w[o] + b[o] ``` -The packed runtime activation tensor contains the I8 activation rows and their floating-point row scales. Linear layers that share the same input and convrot group size reuse this packed tensor, avoiding repeated rotation and activation quantization within the graph. +The packed runtime activation tensor contains the I8 activation rows and their floating-point row scales. Linear layers with an input scale of `1` that share the same input and convrot group size reuse this packed tensor, avoiding repeated rotation and activation quantization within the graph. For other input scales, activations are scaled before explicit convrot quantization, and the linear output is unscaled before adding bias. ## Backend support - CPU provides the portable regular Hadamard, activation quantization, INT8 matrix multiplication, and scale restoration implementations. - NVIDIA CUDA devices with compute capability 7.5 or newer use the native accelerated path. For H256, CUDA fuses the rotation, row-wise maximum reduction, and activation quantization. It uses cuBLAS for I8 x I8 to I32 GEMM and a CUDA kernel for scale restoration and bias addition. -- Vulkan and other GPU backends do not currently have dedicated INT8 convrot kernels. They use the backend scheduler to fall back to CPU, which is expected to be substantially slower than the CUDA path. +- Vulkan provides native H256 activation quantization and INT8 matrix multiplication when the build and device support accelerated packed INT8 dot products. Unsupported configurations and GPU backends without these kernels use the backend scheduler to fall back to CPU. LoRA adapters are applied at runtime without modifying the INT8 weights. The INT8 convrot path computes the base linear output, while LoRA, LoHa, LoKr, and raw weight-difference adapters compute their output corrections from the original, unrotated activation and add them to the base output. `--lora-apply-mode auto` selects this path for models containing INT8 tensorwise weights. If `immediately` is requested, sd.cpp falls back to runtime application because merging an adapter would require dequantizing and rotating its weight update, then recalculating the per-row scales and requantizing the result. diff --git a/src/core/ggml_extend.cpp b/src/core/ggml_extend.cpp index c329a342..d87353d4 100644 --- a/src/core/ggml_extend.cpp +++ b/src/core/ggml_extend.cpp @@ -255,14 +255,20 @@ ggml_tensor* ggml_ext_linear_i8_tensorwise(ggml_context* ctx, } ggml_tensor* fused_bias = scale == 1.f ? b : nullptr; + auto mul_mat = [&](ggml_tensor* input) { + if (input->type == GGML_TYPE_F32 && convrot_group_size > 0) { + input = ggml_quantize_i8_convrot(ctx, input, convrot_group_size); + } + return ggml_mul_mat_i8_tensorwise(ctx, w, input, weight_scale, fused_bias, convrot_group_size); + }; if (x->ne[2] * x->ne[3] > 1024) { int64_t ne2 = x->ne[2]; int64_t ne3 = x->ne[3]; x = ggml_reshape_2d(ctx, x, x->ne[0], x->ne[1] * x->ne[2] * x->ne[3]); - x = ggml_mul_mat_i8_tensorwise(ctx, w, x, weight_scale, fused_bias, convrot_group_size); + x = mul_mat(x); x = ggml_reshape_4d(ctx, x, x->ne[0], x->ne[1] / ne2 / ne3, ne2, ne3); } else { - x = ggml_mul_mat_i8_tensorwise(ctx, w, x, weight_scale, fused_bias, convrot_group_size); + x = mul_mat(x); } if (scale != 1.f) {