src/cpu/cpu_jit_quant.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "cpu_jit.h" | ||
| 2 | #include <brotensor/detail/cpu/thread_pool.h> | ||
| 3 | #include <brotensor/tensor.h> | ||
| 4 | #include <brotensor/ops.h> | ||
| 5 | |||
| 6 | #include <algorithm> | ||
| 7 | #include <cmath> | ||
| 8 | #include <cstdint> | ||
| 9 | #include <cstring> | ||
| 10 | #include <iostream> | ||
| 11 | #include <memory> | ||
| 12 | #include <mutex> | ||
| 13 | #include <stdexcept> | ||
| 14 | #include <vector> | ||
| 15 | |||
| 16 | namespace brotensor::detail::cpu::jit { | ||
| 17 | namespace { | ||
| 18 | |||
| 19 | ✗ | static inline void ref_dequant_q8_0_block(const void* blk_ptr, float* out32) { | |
| 20 | uint16_t d_raw; | ||
| 21 | ✗ | std::memcpy(&d_raw, blk_ptr, 2); | |
| 22 | ✗ | const float d = fp16_bits_to_fp32(d_raw); | |
| 23 | ✗ | const int8_t* qs = reinterpret_cast<const int8_t*>(static_cast<const char*>(blk_ptr) + 2); | |
| 24 | ✗ | for (int i = 0; i < 32; ++i) { | |
| 25 | ✗ | out32[i] = static_cast<float>(qs[i]) * d; | |
| 26 | ✗ | } | |
| 27 | ✗ | } | |
| 28 | |||
| 29 | ✗ | static inline void ref_dequant_q4k_block(const void* blk_ptr, float* out256) { | |
| 30 | ✗ | const uint8_t* p = static_cast<const uint8_t*>(blk_ptr); | |
| 31 | uint16_t d_raw, dmin_raw; | ||
| 32 | ✗ | std::memcpy(&d_raw, p, 2); | |
| 33 | ✗ | std::memcpy(&dmin_raw, p + 2, 2); | |
| 34 | ✗ | const float d = fp16_bits_to_fp32(d_raw); | |
| 35 | ✗ | const float dmin = fp16_bits_to_fp32(dmin_raw); | |
| 36 | |||
| 37 | ✗ | const uint8_t* scales = p + 4; | |
| 38 | ✗ | const uint8_t* qs = p + 16; | |
| 39 | |||
| 40 | uint8_t sc[8], m[8]; | ||
| 41 | ✗ | for (int j = 0; j < 8; ++j) { | |
| 42 | ✗ | if (j < 4) { | |
| 43 | ✗ | sc[j] = scales[j] & 0x3F; | |
| 44 | ✗ | m[j] = scales[j + 4] & 0x3F; | |
| 45 | ✗ | } else { | |
| 46 | ✗ | sc[j] = static_cast<uint8_t>((scales[j + 4] & 0x0F) | ((scales[j - 4] >> 6) << 4)); | |
| 47 | ✗ | m[j] = static_cast<uint8_t>((scales[j + 4] >> 4) | ((scales[j - 0] >> 6) << 4)); | |
| 48 | } | ||
| 49 | ✗ | } | |
| 50 | |||
| 51 | ✗ | for (int p_idx = 0; p_idx < 4; ++p_idx) { | |
| 52 | ✗ | const int is_lo = 2 * p_idx; | |
| 53 | ✗ | const int is_hi = 2 * p_idx + 1; | |
| 54 | ✗ | const float w_lo = static_cast<float>(sc[is_lo]) * d; | |
| 55 | ✗ | const float w_hi = static_cast<float>(sc[is_hi]) * d; | |
| 56 | ✗ | const float b_lo = static_cast<float>(m [is_lo]) * dmin; | |
| 57 | ✗ | const float b_hi = static_cast<float>(m [is_hi]) * dmin; | |
| 58 | |||
| 59 | ✗ | for (int l = 0; l < 32; ++l) { | |
| 60 | ✗ | const uint8_t byte = qs[p_idx * 32 + l]; | |
| 61 | ✗ | const int n_lo = byte & 0x0F; | |
| 62 | ✗ | const int n_hi = (byte >> 4) & 0x0F; | |
| 63 | ✗ | out256[is_lo * 32 + l] = w_lo * static_cast<float>(n_lo) - b_lo; | |
| 64 | ✗ | out256[is_hi * 32 + l] = w_hi * static_cast<float>(n_hi) - b_hi; | |
| 65 | ✗ | } | |
| 66 | ✗ | } | |
| 67 | ✗ | } | |
| 68 | |||
| 69 | ✗ | static void ref_gemv_q8_0(const void* W, const float* X, float* Y, int N, int K) { | |
| 70 | ✗ | const int bpr = K / 32; | |
| 71 | ✗ | const char* W_bytes = static_cast<const char*>(W); | |
| 72 | float blk_buf[32]; | ||
| 73 | ✗ | for (int r = 0; r < N; ++r) { | |
| 74 | ✗ | double acc = 0.0; | |
| 75 | ✗ | const char* row_ptr = W_bytes + static_cast<size_t>(r) * bpr * 34; | |
| 76 | ✗ | for (int b = 0; b < bpr; ++b) { | |
| 77 | ✗ | ref_dequant_q8_0_block(row_ptr + b * 34, blk_buf); | |
| 78 | ✗ | const float* x_sub = X + b * 32; | |
| 79 | ✗ | for (int i = 0; i < 32; ++i) { | |
| 80 | ✗ | acc += static_cast<double>(blk_buf[i]) * static_cast<double>(x_sub[i]); | |
| 81 | ✗ | } | |
| 82 | ✗ | } | |
| 83 | ✗ | Y[r] = static_cast<float>(acc); | |
| 84 | ✗ | } | |
| 85 | ✗ | } | |
| 86 | |||
| 87 | ✗ | static void ref_gemv_q4k(const void* W, const float* X, float* Y, int N, int K) { | |
| 88 | ✗ | const int bpr = K / 256; | |
| 89 | ✗ | const char* W_bytes = static_cast<const char*>(W); | |
| 90 | float blk_buf[256]; | ||
| 91 | ✗ | for (int r = 0; r < N; ++r) { | |
| 92 | ✗ | double acc = 0.0; | |
| 93 | ✗ | const char* row_ptr = W_bytes + static_cast<size_t>(r) * bpr * 144; | |
| 94 | ✗ | for (int b = 0; b < bpr; ++b) { | |
| 95 | ✗ | ref_dequant_q4k_block(row_ptr + b * 144, blk_buf); | |
| 96 | ✗ | const float* x_sub = X + b * 256; | |
| 97 | ✗ | for (int i = 0; i < 256; ++i) { | |
| 98 | ✗ | acc += static_cast<double>(blk_buf[i]) * static_cast<double>(x_sub[i]); | |
| 99 | ✗ | } | |
| 100 | ✗ | } | |
| 101 | ✗ | Y[r] = static_cast<float>(acc); | |
| 102 | ✗ | } | |
| 103 | ✗ | } | |
| 104 | |||
| 105 | } // namespace | ||
| 106 | } // namespace brotensor::detail::cpu::jit | ||
| 107 | |||
| 108 | #if BROTENSOR_HAS_BRASS_JIT | ||
| 109 | |||
| 110 | #include <brass/codegen/ml_fusion.hpp> | ||
| 111 | #include <brass/codegen/kernel_jit.hpp> | ||
| 112 | |||
| 113 | namespace brotensor::detail::cpu::jit { | ||
| 114 | |||
| 115 | namespace { | ||
| 116 | |||
| 117 | using namespace brass::codegen; | ||
| 118 | |||
| 119 | 1 | struct QuantJitKernels { | |
| 120 | 1 | bool available = false; | |
| 121 | 1 | MlFusionCompiler::GemvQ8_0Fn gemv_q8_0_fn = nullptr; | |
| 122 | 1 | MlFusionCompiler::GemvQ4_KFn gemv_q4_k_fn = nullptr; | |
| 123 | std::unique_ptr<MlFusionCompiler> compiler; | ||
| 124 | KernelFunction kfn_q8_0; | ||
| 125 | KernelFunction kfn_q4_k; | ||
| 126 | }; | ||
| 127 | |||
| 128 | 256 | static const QuantJitKernels& get_quant_kernels() { | |
| 129 |
4/6✓ Branch 0 taken 1 time.
✓ Branch 1 taken 255 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
|
257 | static QuantJitKernels kernels = []() { |
| 130 | 1 | QuantJitKernels k; | |
| 131 | try { | ||
| 132 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | k.compiler = std::make_unique<MlFusionCompiler>(); |
| 133 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | k.kfn_q8_0 = k.compiler->compile_gemv_q8_0(); |
| 134 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | k.kfn_q4_k = k.compiler->compile_gemv_q4_k(); |
| 135 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | if (k.kfn_q8_0.is_valid()) { |
| 136 | 1 | k.gemv_q8_0_fn = k.kfn_q8_0.as<MlFusionCompiler::GemvQ8_0Fn>(); | |
| 137 | 1 | } | |
| 138 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | if (k.kfn_q4_k.is_valid()) { |
| 139 | 1 | k.gemv_q4_k_fn = k.kfn_q4_k.as<MlFusionCompiler::GemvQ4_KFn>(); | |
| 140 | 1 | } | |
| 141 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | k.available = (k.gemv_q8_0_fn != nullptr && k.gemv_q4_k_fn != nullptr); |
| 142 |
0/2✗ Branch 0 not taken.
✗ Branch 1 not taken.
|
1 | } catch (const std::exception& e) { |
| 143 | ✗ | std::cerr << "brotensor: Brass JIT quant kernels initialization failed: " << e.what() << "\n"; | |
| 144 | ✗ | k.available = false; | |
| 145 | ✗ | } | |
| 146 | 1 | return k; | |
| 147 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | }(); |
| 148 | 256 | return kernels; | |
| 149 | ✗ | } | |
| 150 | |||
| 151 | } // namespace | ||
| 152 | |||
| 153 | 128 | void gemv_q8_0(const void* W, const float* X, float* Y, int N, int K) { | |
| 154 |
2/4✓ Branch 0 taken 128 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 128 times.
|
128 | if (N <= 0 || K <= 0) return; |
| 155 | 128 | const auto& k = get_quant_kernels(); | |
| 156 |
2/4✓ Branch 0 taken 128 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 128 times.
|
128 | if (k.available && k.gemv_q8_0_fn != nullptr) { |
| 157 |
2/2✓ Branch 0 taken 96 times.
✓ Branch 1 taken 32 times.
|
128 | if (N >= 8) { |
| 158 | 96 | const size_t row_bytes = static_cast<size_t>(K / 32) * 34; | |
| 159 | 96 | int n_threads = std::min<int>(detail::cpu::ThreadPool::instance().num_threads(), N); | |
| 160 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 96 times.
|
96 | if (n_threads <= 1) { |
| 161 | ✗ | k.gemv_q8_0_fn(W, X, Y, static_cast<uint64_t>(N), static_cast<uint64_t>(K)); | |
| 162 | ✗ | } else { | |
| 163 | 96 | int chunk_size = (N + n_threads - 1) / n_threads; | |
| 164 | 96 | int num_chunks = (N + chunk_size - 1) / chunk_size; | |
| 165 |
1/2✓ Branch 0 taken 96 times.
✗ Branch 1 not taken.
|
379 | detail::cpu::parallel_for(static_cast<std::size_t>(num_chunks), [&](std::size_t c) { |
| 166 | 283 | int r_start = static_cast<int>(c) * chunk_size; | |
| 167 | 283 | int r_end = std::min(r_start + chunk_size, N); | |
| 168 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 283 times.
|
283 | if (r_start >= r_end) return; |
| 169 | 283 | const char* W_bytes = static_cast<const char*>(W); | |
| 170 | 283 | const void* W_slice = W_bytes + static_cast<size_t>(r_start) * row_bytes; | |
| 171 | 283 | float* Y_slice = Y + r_start; | |
| 172 | 283 | k.gemv_q8_0_fn(W_slice, X, Y_slice, static_cast<uint64_t>(r_end - r_start), static_cast<uint64_t>(K)); | |
| 173 | 283 | }); | |
| 174 | } | ||
| 175 | 96 | } else { | |
| 176 | 32 | k.gemv_q8_0_fn(W, X, Y, static_cast<uint64_t>(N), static_cast<uint64_t>(K)); | |
| 177 | } | ||
| 178 | 128 | return; | |
| 179 | } | ||
| 180 | ✗ | ref_gemv_q8_0(W, X, Y, N, K); | |
| 181 | 128 | } | |
| 182 | |||
| 183 | 128 | void gemv_q4_k(const void* W, const float* X, float* Y, int N, int K) { | |
| 184 |
2/4✓ Branch 0 taken 128 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 128 times.
|
128 | if (N <= 0 || K <= 0) return; |
| 185 | 128 | const auto& k = get_quant_kernels(); | |
| 186 |
2/4✓ Branch 0 taken 128 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 128 times.
|
128 | if (k.available && k.gemv_q4_k_fn != nullptr) { |
| 187 |
2/2✓ Branch 0 taken 96 times.
✓ Branch 1 taken 32 times.
|
128 | if (N >= 8) { |
| 188 | 96 | const size_t row_bytes = static_cast<size_t>(K / 256) * 144; | |
| 189 | 96 | int n_threads = std::min<int>(detail::cpu::ThreadPool::instance().num_threads(), N); | |
| 190 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 96 times.
|
96 | if (n_threads <= 1) { |
| 191 | ✗ | k.gemv_q4_k_fn(W, X, Y, static_cast<uint64_t>(N), static_cast<uint64_t>(K)); | |
| 192 | ✗ | } else { | |
| 193 | 96 | int chunk_size = (N + n_threads - 1) / n_threads; | |
| 194 | 96 | int num_chunks = (N + chunk_size - 1) / chunk_size; | |
| 195 |
1/2✓ Branch 0 taken 96 times.
✗ Branch 1 not taken.
|
362 | detail::cpu::parallel_for(static_cast<std::size_t>(num_chunks), [&](std::size_t c) { |
| 196 | 266 | int r_start = static_cast<int>(c) * chunk_size; | |
| 197 | 266 | int r_end = std::min(r_start + chunk_size, N); | |
| 198 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 266 times.
|
266 | if (r_start >= r_end) return; |
| 199 | 266 | const char* W_bytes = static_cast<const char*>(W); | |
| 200 | 266 | const void* W_slice = W_bytes + static_cast<size_t>(r_start) * row_bytes; | |
| 201 | 266 | float* Y_slice = Y + r_start; | |
| 202 | 266 | k.gemv_q4_k_fn(W_slice, X, Y_slice, static_cast<uint64_t>(r_end - r_start), static_cast<uint64_t>(K)); | |
| 203 | 266 | }); | |
| 204 | } | ||
| 205 | 96 | } else { | |
| 206 | 32 | k.gemv_q4_k_fn(W, X, Y, static_cast<uint64_t>(N), static_cast<uint64_t>(K)); | |
| 207 | } | ||
| 208 | 128 | return; | |
| 209 | } | ||
| 210 | ✗ | ref_gemv_q4k(W, X, Y, N, K); | |
| 211 | 128 | } | |
| 212 | |||
| 213 | } // namespace brotensor::detail::cpu::jit | ||
| 214 | |||
| 215 | #else // !BROTENSOR_HAS_BRASS_JIT | ||
| 216 | |||
| 217 | namespace brotensor::detail::cpu::jit { | ||
| 218 | |||
| 219 | void gemv_q8_0(const void* W, const float* X, float* Y, int N, int K) { | ||
| 220 | if (N <= 0 || K <= 0) return; | ||
| 221 | ref_gemv_q8_0(W, X, Y, N, K); | ||
| 222 | } | ||
| 223 | |||
| 224 | void gemv_q4_k(const void* W, const float* X, float* Y, int N, int K) { | ||
| 225 | if (N <= 0 || K <= 0) return; | ||
| 226 | ref_gemv_q4k(W, X, Y, N, K); | ||
| 227 | } | ||
| 228 | |||
| 229 | } // namespace brotensor::detail::cpu::jit | ||
| 230 | |||
| 231 | #endif // BROTENSOR_HAS_BRASS_JIT | ||
| 232 | |||
| 233 | // ─── OpsVTable Registrations for CPU Quantization Ops ─────────────────────── | ||
| 234 | |||
| 235 | namespace brotensor::detail::cpu { | ||
| 236 | |||
| 237 | namespace { | ||
| 238 | |||
| 239 | 5 | static void validate_w_q8_0(const Tensor& W, const char* op) { | |
| 240 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | if (W.dtype != Dtype::Q8_0) { |
| 241 | ✗ | throw std::runtime_error(std::string("brotensor: ") + op + ": W must be Dtype::Q8_0"); | |
| 242 | } | ||
| 243 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | if (W.cols % 32 != 0) { |
| 244 | ✗ | throw std::runtime_error(std::string("brotensor: ") + op + ": W cols must be a multiple of 32"); | |
| 245 | } | ||
| 246 | 5 | } | |
| 247 | |||
| 248 | 5 | static void validate_w_q4k(const Tensor& W, const char* op) { | |
| 249 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | if (W.dtype != Dtype::Q4_K) { |
| 250 | ✗ | throw std::runtime_error(std::string("brotensor: ") + op + ": W must be Dtype::Q4_K"); | |
| 251 | } | ||
| 252 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | if (W.cols % 256 != 0) { |
| 253 | ✗ | throw std::runtime_error(std::string("brotensor: ") + op + ": W cols must be a multiple of 256"); | |
| 254 | } | ||
| 255 | 5 | } | |
| 256 | |||
| 257 | 128 | static inline void local_dequant_q8_0_block(const void* blk_ptr, float* out32) { | |
| 258 | uint16_t d_raw; | ||
| 259 | 128 | std::memcpy(&d_raw, blk_ptr, 2); | |
| 260 | 128 | const float d = fp16_bits_to_fp32(d_raw); | |
| 261 | 128 | const int8_t* qs = reinterpret_cast<const int8_t*>(static_cast<const char*>(blk_ptr) + 2); | |
| 262 |
2/2✓ Branch 0 taken 3885 times.
✓ Branch 1 taken 128 times.
|
4013 | for (int i = 0; i < 32; ++i) { |
| 263 | 3885 | out32[i] = static_cast<float>(qs[i]) * d; | |
| 264 | 3885 | } | |
| 265 | 128 | } | |
| 266 | |||
| 267 | 16 | static inline void local_dequant_q4k_block(const void* blk_ptr, float* out256) { | |
| 268 | 16 | const uint8_t* p = static_cast<const uint8_t*>(blk_ptr); | |
| 269 | uint16_t d_raw, dmin_raw; | ||
| 270 | 16 | std::memcpy(&d_raw, p, 2); | |
| 271 | 16 | std::memcpy(&dmin_raw, p + 2, 2); | |
| 272 | 16 | const float d = fp16_bits_to_fp32(d_raw); | |
| 273 | 16 | const float dmin = fp16_bits_to_fp32(dmin_raw); | |
| 274 | |||
| 275 | 16 | const uint8_t* scales = p + 4; | |
| 276 | 16 | const uint8_t* qs = p + 16; | |
| 277 | |||
| 278 | uint8_t sc[8], m[8]; | ||
| 279 |
2/2✓ Branch 0 taken 128 times.
✓ Branch 1 taken 16 times.
|
144 | for (int j = 0; j < 8; ++j) { |
| 280 |
2/2✓ Branch 0 taken 64 times.
✓ Branch 1 taken 64 times.
|
128 | if (j < 4) { |
| 281 | 64 | sc[j] = scales[j] & 0x3F; | |
| 282 | 64 | m[j] = scales[j + 4] & 0x3F; | |
| 283 | 64 | } else { | |
| 284 | 64 | sc[j] = static_cast<uint8_t>((scales[j + 4] & 0x0F) | ((scales[j - 4] >> 6) << 4)); | |
| 285 | 64 | m[j] = static_cast<uint8_t>((scales[j + 4] >> 4) | ((scales[j - 0] >> 6) << 4)); | |
| 286 | } | ||
| 287 | 128 | } | |
| 288 | |||
| 289 |
2/2✓ Branch 0 taken 60 times.
✓ Branch 1 taken 16 times.
|
76 | for (int p_idx = 0; p_idx < 4; ++p_idx) { |
| 290 | 60 | const int is_lo = 2 * p_idx; | |
| 291 | 60 | const int is_hi = 2 * p_idx + 1; | |
| 292 | 60 | const float w_lo = static_cast<float>(sc[is_lo]) * d; | |
| 293 | 60 | const float w_hi = static_cast<float>(sc[is_hi]) * d; | |
| 294 | 60 | const float b_lo = static_cast<float>(m [is_lo]) * dmin; | |
| 295 | 60 | const float b_hi = static_cast<float>(m [is_hi]) * dmin; | |
| 296 | |||
| 297 |
2/2✓ Branch 0 taken 1834 times.
✓ Branch 1 taken 60 times.
|
1894 | for (int l = 0; l < 32; ++l) { |
| 298 | 1834 | const uint8_t byte = qs[p_idx * 32 + l]; | |
| 299 | 1834 | const int n_lo = byte & 0x0F; | |
| 300 | 1834 | const int n_hi = (byte >> 4) & 0x0F; | |
| 301 | 1834 | out256[is_lo * 32 + l] = w_lo * static_cast<float>(n_lo) - b_lo; | |
| 302 | 1834 | out256[is_hi * 32 + l] = w_hi * static_cast<float>(n_hi) - b_hi; | |
| 303 | 1834 | } | |
| 304 | 60 | } | |
| 305 | 16 | } | |
| 306 | |||
| 307 | } // namespace | ||
| 308 | |||
| 309 | 1 | void dequant_q8_0_to_fp16(const Tensor& W_q8, Tensor& W_fp16) { | |
| 310 | 1 | validate_w_q8_0(W_q8, "dequant_q8_0_to_fp16"); | |
| 311 | 1 | const int out = W_q8.rows; | |
| 312 | 1 | const int K = W_q8.cols; | |
| 313 |
3/6✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
|
1 | if (W_fp16.rows != out || W_fp16.cols != K || W_fp16.dtype != Dtype::FP16) { |
| 314 | ✗ | W_fp16.resize(out, K, Dtype::FP16); | |
| 315 | ✗ | } | |
| 316 |
2/4✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
|
1 | if (out == 0 || K == 0) return; |
| 317 | 1 | const int bpr = K / 32; | |
| 318 | 1 | const char* src = static_cast<const char*>(W_q8.data); | |
| 319 | 1 | uint16_t* dst = static_cast<uint16_t*>(W_fp16.data); | |
| 320 | |||
| 321 | 9 | auto dequant_row = [&](int r) { | |
| 322 | float buf[32]; | ||
| 323 | 8 | const char* rsrc = src + static_cast<size_t>(r) * bpr * 34; | |
| 324 | 8 | uint16_t* rdst = dst + static_cast<size_t>(r) * K; | |
| 325 |
2/2✓ Branch 0 taken 126 times.
✓ Branch 1 taken 8 times.
|
134 | for (int b = 0; b < bpr; ++b) { |
| 326 | 126 | local_dequant_q8_0_block(rsrc + b * 34, buf); | |
| 327 |
2/2✓ Branch 0 taken 3032 times.
✓ Branch 1 taken 126 times.
|
3158 | for (int i = 0; i < 32; ++i) { |
| 328 | 3032 | rdst[b * 32 + i] = fp32_to_fp16_bits(buf[i]); | |
| 329 | 3032 | } | |
| 330 | 126 | } | |
| 331 | 8 | }; | |
| 332 | |||
| 333 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | if (out >= 8) { |
| 334 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
9 | detail::cpu::parallel_for(static_cast<std::size_t>(out), [&](std::size_t r) { |
| 335 | 8 | dequant_row(static_cast<int>(r)); | |
| 336 | 8 | }); | |
| 337 | 1 | } else { | |
| 338 | ✗ | for (int r = 0; r < out; ++r) dequant_row(r); | |
| 339 | } | ||
| 340 | 1 | } | |
| 341 | |||
| 342 | 1 | void dequant_q4k_to_fp16(const Tensor& W_q4k, Tensor& W_fp16) { | |
| 343 | 1 | validate_w_q4k(W_q4k, "dequant_q4k_to_fp16"); | |
| 344 | 1 | const int out = W_q4k.rows; | |
| 345 | 1 | const int K = W_q4k.cols; | |
| 346 |
3/6✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
|
1 | if (W_fp16.rows != out || W_fp16.cols != K || W_fp16.dtype != Dtype::FP16) { |
| 347 | ✗ | W_fp16.resize(out, K, Dtype::FP16); | |
| 348 | ✗ | } | |
| 349 |
2/4✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
|
1 | if (out == 0 || K == 0) return; |
| 350 | 1 | const int bpr = K / 256; | |
| 351 | 1 | const char* src = static_cast<const char*>(W_q4k.data); | |
| 352 | 1 | uint16_t* dst = static_cast<uint16_t*>(W_fp16.data); | |
| 353 | |||
| 354 | 9 | auto dequant_row = [&](int r) { | |
| 355 | float buf[256]; | ||
| 356 | 8 | const char* rsrc = src + static_cast<size_t>(r) * bpr * 144; | |
| 357 | 8 | uint16_t* rdst = dst + static_cast<size_t>(r) * K; | |
| 358 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 8 times.
|
24 | for (int b = 0; b < bpr; ++b) { |
| 359 | 16 | local_dequant_q4k_block(rsrc + b * 144, buf); | |
| 360 |
2/2✓ Branch 0 taken 2730 times.
✓ Branch 1 taken 16 times.
|
2746 | for (int i = 0; i < 256; ++i) { |
| 361 | 2730 | rdst[b * 256 + i] = fp32_to_fp16_bits(buf[i]); | |
| 362 | 2730 | } | |
| 363 | 16 | } | |
| 364 | 8 | }; | |
| 365 | |||
| 366 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | if (out >= 8) { |
| 367 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
9 | detail::cpu::parallel_for(static_cast<std::size_t>(out), [&](std::size_t r) { |
| 368 | 8 | dequant_row(static_cast<int>(r)); | |
| 369 | 8 | }); | |
| 370 | 1 | } else { | |
| 371 | ✗ | for (int r = 0; r < out; ++r) dequant_row(r); | |
| 372 | } | ||
| 373 | 1 | } | |
| 374 | |||
| 375 | 4 | void linear_forward_q8_0_fp16(const Tensor& W_q8, const Tensor* bias, | |
| 376 | const Tensor& x, Tensor& y) { | ||
| 377 | 4 | validate_w_q8_0(W_q8, "linear_forward_q8_0_fp16"); | |
| 378 | 4 | const int out = W_q8.rows; | |
| 379 | 4 | const int K = W_q8.cols; | |
| 380 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (x.size() != K) { |
| 381 | ✗ | throw std::runtime_error("brotensor: linear_forward_q8_0_fp16: x size mismatch"); | |
| 382 | } | ||
| 383 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
4 | if (bias && bias->size() > 0 && bias->size() != out) { |
| 384 | ✗ | throw std::runtime_error("brotensor: linear_forward_q8_0_fp16: bias size mismatch"); | |
| 385 | } | ||
| 386 |
3/8✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
4 | if (y.rows != out || y.cols != 1 || (y.dtype != Dtype::FP16 && y.dtype != Dtype::FP32)) { |
| 387 | ✗ | y.resize(out, 1, Dtype::FP16); | |
| 388 | ✗ | } | |
| 389 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (out == 0) return; |
| 390 | |||
| 391 | 4 | std::vector<float> x_f32(K); | |
| 392 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (x.dtype == Dtype::FP16) { |
| 393 | 4 | const uint16_t* xp = static_cast<const uint16_t*>(x.data); | |
| 394 |
3/4✓ Branch 0 taken 4 times.
✓ Branch 1 taken 16384 times.
✓ Branch 2 taken 16384 times.
✗ Branch 3 not taken.
|
16388 | for (int i = 0; i < K; ++i) x_f32[i] = fp16_bits_to_fp32(xp[i]); |
| 395 |
0/2✗ Branch 0 not taken.
✗ Branch 1 not taken.
|
4 | } else if (x.dtype == Dtype::FP32) { |
| 396 | ✗ | std::memcpy(x_f32.data(), x.data, K * sizeof(float)); | |
| 397 | ✗ | } else { | |
| 398 | ✗ | throw std::runtime_error("brotensor: linear_forward_q8_0_fp16: x must be FP16 or FP32"); | |
| 399 | } | ||
| 400 | |||
| 401 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | std::vector<float> y_f32(out); |
| 402 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | jit::gemv_q8_0(W_q8.data, x_f32.data(), y_f32.data(), out, K); |
| 403 | |||
| 404 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
4 | if (bias && bias->size() > 0) { |
| 405 | ✗ | if (bias->dtype == Dtype::FP16) { | |
| 406 | ✗ | const uint16_t* bp = static_cast<const uint16_t*>(bias->data); | |
| 407 | ✗ | for (int i = 0; i < out; ++i) y_f32[i] += fp16_bits_to_fp32(bp[i]); | |
| 408 | ✗ | } else if (bias->dtype == Dtype::FP32) { | |
| 409 | ✗ | const float* bp = static_cast<const float*>(bias->data); | |
| 410 | ✗ | for (int i = 0; i < out; ++i) y_f32[i] += bp[i]; | |
| 411 | ✗ | } | |
| 412 | ✗ | } | |
| 413 | |||
| 414 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (y.dtype == Dtype::FP16) { |
| 415 | 4 | uint16_t* yp = static_cast<uint16_t*>(y.data); | |
| 416 |
3/4✓ Branch 0 taken 4 times.
✓ Branch 1 taken 169 times.
✓ Branch 2 taken 169 times.
✗ Branch 3 not taken.
|
173 | for (int i = 0; i < out; ++i) yp[i] = fp32_to_fp16_bits(y_f32[i]); |
| 417 | 4 | } else { | |
| 418 | ✗ | std::memcpy(y.data, y_f32.data(), out * sizeof(float)); | |
| 419 | } | ||
| 420 | 4 | } | |
| 421 | |||
| 422 | ✗ | void linear_forward_batched_q8_0_fp16(const Tensor& W_q8, const Tensor* bias, | |
| 423 | const Tensor& X_BD, Tensor& Y_BD) { | ||
| 424 | ✗ | validate_w_q8_0(W_q8, "linear_forward_batched_q8_0_fp16"); | |
| 425 | ✗ | const int B = X_BD.rows; | |
| 426 | ✗ | const int K = X_BD.cols; | |
| 427 | ✗ | const int out = W_q8.rows; | |
| 428 | ✗ | if (W_q8.cols != K) { | |
| 429 | ✗ | throw std::runtime_error("brotensor: linear_forward_batched_q8_0_fp16: shape mismatch"); | |
| 430 | } | ||
| 431 | ✗ | if (bias && bias->size() > 0 && bias->size() != out) { | |
| 432 | ✗ | throw std::runtime_error("brotensor: linear_forward_batched_q8_0_fp16: bias size mismatch"); | |
| 433 | } | ||
| 434 | ✗ | if (Y_BD.rows != B || Y_BD.cols != out || (Y_BD.dtype != Dtype::FP16 && Y_BD.dtype != Dtype::FP32)) { | |
| 435 | ✗ | Y_BD.resize(B, out, Dtype::FP16); | |
| 436 | ✗ | } | |
| 437 | ✗ | if (B == 0 || out == 0) return; | |
| 438 | |||
| 439 | ✗ | for (int b = 0; b < B; ++b) { | |
| 440 | ✗ | std::vector<float> x_f32(K); | |
| 441 | ✗ | if (X_BD.dtype == Dtype::FP16) { | |
| 442 | ✗ | const uint16_t* xp = static_cast<const uint16_t*>(X_BD.data) + static_cast<size_t>(b) * K; | |
| 443 | ✗ | for (int i = 0; i < K; ++i) x_f32[i] = fp16_bits_to_fp32(xp[i]); | |
| 444 | ✗ | } else if (X_BD.dtype == Dtype::FP32) { | |
| 445 | ✗ | const float* xp = static_cast<const float*>(X_BD.data) + static_cast<size_t>(b) * K; | |
| 446 | ✗ | std::memcpy(x_f32.data(), xp, K * sizeof(float)); | |
| 447 | ✗ | } else { | |
| 448 | ✗ | throw std::runtime_error("brotensor: linear_forward_batched_q8_0_fp16: X must be FP16 or FP32"); | |
| 449 | } | ||
| 450 | |||
| 451 | ✗ | std::vector<float> y_f32(out); | |
| 452 | ✗ | jit::gemv_q8_0(W_q8.data, x_f32.data(), y_f32.data(), out, K); | |
| 453 | |||
| 454 | ✗ | if (bias && bias->size() > 0) { | |
| 455 | ✗ | if (bias->dtype == Dtype::FP16) { | |
| 456 | ✗ | const uint16_t* bp = static_cast<const uint16_t*>(bias->data); | |
| 457 | ✗ | for (int i = 0; i < out; ++i) y_f32[i] += fp16_bits_to_fp32(bp[i]); | |
| 458 | ✗ | } else if (bias->dtype == Dtype::FP32) { | |
| 459 | ✗ | const float* bp = static_cast<const float*>(bias->data); | |
| 460 | ✗ | for (int i = 0; i < out; ++i) y_f32[i] += bp[i]; | |
| 461 | ✗ | } | |
| 462 | ✗ | } | |
| 463 | |||
| 464 | ✗ | if (Y_BD.dtype == Dtype::FP16) { | |
| 465 | ✗ | uint16_t* yp = static_cast<uint16_t*>(Y_BD.data) + static_cast<size_t>(b) * out; | |
| 466 | ✗ | for (int i = 0; i < out; ++i) yp[i] = fp32_to_fp16_bits(y_f32[i]); | |
| 467 | ✗ | } else { | |
| 468 | ✗ | float* yp = static_cast<float*>(Y_BD.data) + static_cast<size_t>(b) * out; | |
| 469 | ✗ | std::memcpy(yp, y_f32.data(), out * sizeof(float)); | |
| 470 | } | ||
| 471 | ✗ | } | |
| 472 | ✗ | } | |
| 473 | |||
| 474 | 4 | void linear_forward_q4k_fp16(const Tensor& W_q4k, const Tensor* bias, | |
| 475 | const Tensor& x, Tensor& y) { | ||
| 476 | 4 | validate_w_q4k(W_q4k, "linear_forward_q4k_fp16"); | |
| 477 | 4 | const int out = W_q4k.rows; | |
| 478 | 4 | const int K = W_q4k.cols; | |
| 479 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (x.size() != K) { |
| 480 | ✗ | throw std::runtime_error("brotensor: linear_forward_q4k_fp16: x size mismatch"); | |
| 481 | } | ||
| 482 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
4 | if (bias && bias->size() > 0 && bias->size() != out) { |
| 483 | ✗ | throw std::runtime_error("brotensor: linear_forward_q4k_fp16: bias size mismatch"); | |
| 484 | } | ||
| 485 |
3/8✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
4 | if (y.rows != out || y.cols != 1 || (y.dtype != Dtype::FP16 && y.dtype != Dtype::FP32)) { |
| 486 | ✗ | y.resize(out, 1, Dtype::FP16); | |
| 487 | ✗ | } | |
| 488 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (out == 0) return; |
| 489 | |||
| 490 | 4 | std::vector<float> x_f32(K); | |
| 491 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (x.dtype == Dtype::FP16) { |
| 492 | 4 | const uint16_t* xp = static_cast<const uint16_t*>(x.data); | |
| 493 |
3/4✓ Branch 0 taken 4 times.
✓ Branch 1 taken 16384 times.
✓ Branch 2 taken 16384 times.
✗ Branch 3 not taken.
|
16388 | for (int i = 0; i < K; ++i) x_f32[i] = fp16_bits_to_fp32(xp[i]); |
| 494 |
0/2✗ Branch 0 not taken.
✗ Branch 1 not taken.
|
4 | } else if (x.dtype == Dtype::FP32) { |
| 495 | ✗ | std::memcpy(x_f32.data(), x.data, K * sizeof(float)); | |
| 496 | ✗ | } else { | |
| 497 | ✗ | throw std::runtime_error("brotensor: linear_forward_q4k_fp16: x must be FP16 or FP32"); | |
| 498 | } | ||
| 499 | |||
| 500 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | std::vector<float> y_f32(out); |
| 501 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | jit::gemv_q4_k(W_q4k.data, x_f32.data(), y_f32.data(), out, K); |
| 502 | |||
| 503 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
4 | if (bias && bias->size() > 0) { |
| 504 | ✗ | if (bias->dtype == Dtype::FP16) { | |
| 505 | ✗ | const uint16_t* bp = static_cast<const uint16_t*>(bias->data); | |
| 506 | ✗ | for (int i = 0; i < out; ++i) y_f32[i] += fp16_bits_to_fp32(bp[i]); | |
| 507 | ✗ | } else if (bias->dtype == Dtype::FP32) { | |
| 508 | ✗ | const float* bp = static_cast<const float*>(bias->data); | |
| 509 | ✗ | for (int i = 0; i < out; ++i) y_f32[i] += bp[i]; | |
| 510 | ✗ | } | |
| 511 | ✗ | } | |
| 512 | |||
| 513 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (y.dtype == Dtype::FP16) { |
| 514 | 4 | uint16_t* yp = static_cast<uint16_t*>(y.data); | |
| 515 |
3/4✓ Branch 0 taken 4 times.
✓ Branch 1 taken 169 times.
✓ Branch 2 taken 169 times.
✗ Branch 3 not taken.
|
173 | for (int i = 0; i < out; ++i) yp[i] = fp32_to_fp16_bits(y_f32[i]); |
| 516 | 4 | } else { | |
| 517 | ✗ | std::memcpy(y.data, y_f32.data(), out * sizeof(float)); | |
| 518 | } | ||
| 519 | 4 | } | |
| 520 | |||
| 521 | ✗ | void linear_forward_batched_q4k_fp16(const Tensor& W_q4k, const Tensor* bias, | |
| 522 | const Tensor& X_BD, Tensor& Y_BD) { | ||
| 523 | ✗ | validate_w_q4k(W_q4k, "linear_forward_batched_q4k_fp16"); | |
| 524 | ✗ | const int B = X_BD.rows; | |
| 525 | ✗ | const int K = X_BD.cols; | |
| 526 | ✗ | const int out = W_q4k.rows; | |
| 527 | ✗ | if (W_q4k.cols != K) { | |
| 528 | ✗ | throw std::runtime_error("brotensor: linear_forward_batched_q4k_fp16: shape mismatch"); | |
| 529 | } | ||
| 530 | ✗ | if (bias && bias->size() > 0 && bias->size() != out) { | |
| 531 | ✗ | throw std::runtime_error("brotensor: linear_forward_batched_q4k_fp16: bias size mismatch"); | |
| 532 | } | ||
| 533 | ✗ | if (Y_BD.rows != B || Y_BD.cols != out || (Y_BD.dtype != Dtype::FP16 && Y_BD.dtype != Dtype::FP32)) { | |
| 534 | ✗ | Y_BD.resize(B, out, Dtype::FP16); | |
| 535 | ✗ | } | |
| 536 | ✗ | if (B == 0 || out == 0) return; | |
| 537 | |||
| 538 | ✗ | for (int b = 0; b < B; ++b) { | |
| 539 | ✗ | std::vector<float> x_f32(K); | |
| 540 | ✗ | if (X_BD.dtype == Dtype::FP16) { | |
| 541 | ✗ | const uint16_t* xp = static_cast<const uint16_t*>(X_BD.data) + static_cast<size_t>(b) * K; | |
| 542 | ✗ | for (int i = 0; i < K; ++i) x_f32[i] = fp16_bits_to_fp32(xp[i]); | |
| 543 | ✗ | } else if (X_BD.dtype == Dtype::FP32) { | |
| 544 | ✗ | const float* xp = static_cast<const float*>(X_BD.data) + static_cast<size_t>(b) * K; | |
| 545 | ✗ | std::memcpy(x_f32.data(), xp, K * sizeof(float)); | |
| 546 | ✗ | } else { | |
| 547 | ✗ | throw std::runtime_error("brotensor: linear_forward_batched_q4k_fp16: X must be FP16 or FP32"); | |
| 548 | } | ||
| 549 | |||
| 550 | ✗ | std::vector<float> y_f32(out); | |
| 551 | ✗ | jit::gemv_q4_k(W_q4k.data, x_f32.data(), y_f32.data(), out, K); | |
| 552 | |||
| 553 | ✗ | if (bias && bias->size() > 0) { | |
| 554 | ✗ | if (bias->dtype == Dtype::FP16) { | |
| 555 | ✗ | const uint16_t* bp = static_cast<const uint16_t*>(bias->data); | |
| 556 | ✗ | for (int i = 0; i < out; ++i) y_f32[i] += fp16_bits_to_fp32(bp[i]); | |
| 557 | ✗ | } else if (bias->dtype == Dtype::FP32) { | |
| 558 | ✗ | const float* bp = static_cast<const float*>(bias->data); | |
| 559 | ✗ | for (int i = 0; i < out; ++i) y_f32[i] += bp[i]; | |
| 560 | ✗ | } | |
| 561 | ✗ | } | |
| 562 | |||
| 563 | ✗ | if (Y_BD.dtype == Dtype::FP16) { | |
| 564 | ✗ | uint16_t* yp = static_cast<uint16_t*>(Y_BD.data) + static_cast<size_t>(b) * out; | |
| 565 | ✗ | for (int i = 0; i < out; ++i) yp[i] = fp32_to_fp16_bits(y_f32[i]); | |
| 566 | ✗ | } else { | |
| 567 | ✗ | float* yp = static_cast<float*>(Y_BD.data) + static_cast<size_t>(b) * out; | |
| 568 | ✗ | std::memcpy(yp, y_f32.data(), out * sizeof(float)); | |
| 569 | } | ||
| 570 | ✗ | } | |
| 571 | ✗ | } | |
| 572 | |||
| 573 | } // namespace brotensor::detail::cpu | ||
| 574 |