// Fused packed W4A32 reference execution. No full decoded weight buffer. // Sparse connectivity reduces dot products; INT4 alone does not reduce MAC count. #ifdef __APPLE__ #define ACCELERATE_NEW_LAPACK #define ACCELERATE_LAPACK_ILP64 #include #else #include #endif #include #include #include #include #if defined(__aarch64__) || defined(__arm64__) #include #endif struct Layer { int din, dout, width, group, rank, mode; const float *center, *unit, *bias, *weight, *scales, *a, *b; const uint8_t *codes; const uint32_t *indices; }; static bool zero_center(const Layer& l) { if (!l.center) return true; for (int i = 0; i < l.din; ++i) if (l.center[i] != 0.0f) return false; return true; } extern "C" int relative_integer_dot_supported() { #ifdef __ARM_FEATURE_DOTPROD return 1; #else return 0; #endif } extern "C" int relative_gated_delta_supported() { #if defined(__aarch64__) || defined(__arm64__) return 1; #else return 0; #endif } // CPU streaming equivalent of mlx_lm.models.gated_delta.gated_delta_ops. // State rows stay local to one (batch, head, value-dimension) sequence row. extern "C" int relative_gated_delta(const float* q, const float* k, const float* v, const float* g, const float* beta, const float* state_in, const uint8_t* mask, int batch, int time, int hk_count, int hv_count, int dk, int dv, int vector_gate, int has_mask, float* y, float* state_out) { if (batch < 1 || time < 1 || hk_count < 1 || hv_count < 1 || dk < 1 || dv < 1 || hv_count % hk_count || (vector_gate != 0 && vector_gate != 1) || (has_mask != 0 && has_mask != 1) || !q || !k || !v || !g || !beta || !state_in || !y || !state_out || (has_mask && !mask)) return 1; if (!relative_gated_delta_supported()) return 2; #if !defined(__aarch64__) && !defined(__arm64__) return 2; #else const int repeat = hv_count / hk_count; const size_t state_row_size = static_cast(dk); std::vector row(state_row_size), candidate(state_row_size); for (int b = 0; b < batch; ++b) { for (int h = 0; h < hv_count; ++h) { const int hk = h / repeat; for (int d = 0; d < dv; ++d) { const size_t state_offset = ((static_cast(b) * hv_count + h) * dv + d) * dk; std::copy(state_in + state_offset, state_in + state_offset + dk, row.begin()); for (int t = 0; t < time; ++t) { const size_t q_offset = ((static_cast(b) * time + t) * hk_count + hk) * dk; const size_t v_offset = (static_cast(b) * time + t) * hv_count * dv + static_cast(h) * dv + d; const size_t gate_offset = (static_cast(b) * time + t) * hv_count + h; const float* kt = k + q_offset; const float* qt = q + q_offset; float kv_mem = 0.0f; float32x4_t kv0 = vdupq_n_f32(0.0f), kv1 = kv0; float32x4_t kv2 = kv0, kv3 = kv0, kv_tail = kv0; int i = 0; for (; i + 16 <= dk; i += 16) { float32x4_t gate0, gate1, gate2, gate3; if (vector_gate) { const float* gt = g + gate_offset * dk + i; gate0 = vld1q_f32(gt); gate1 = vld1q_f32(gt + 4); gate2 = vld1q_f32(gt + 8); gate3 = vld1q_f32(gt + 12); } else { const float32x4_t gate = vdupq_n_f32(g[gate_offset]); gate0 = gate1 = gate2 = gate3 = gate; } const float* rt = row.data() + i; const float32x4_t c0 = vmulq_f32(vld1q_f32(rt), gate0); const float32x4_t c1 = vmulq_f32(vld1q_f32(rt + 4), gate1); const float32x4_t c2 = vmulq_f32(vld1q_f32(rt + 8), gate2); const float32x4_t c3 = vmulq_f32(vld1q_f32(rt + 12), gate3); float* ct = candidate.data() + i; vst1q_f32(ct, c0); vst1q_f32(ct + 4, c1); vst1q_f32(ct + 8, c2); vst1q_f32(ct + 12, c3); kv0 = vaddq_f32(kv0, vmulq_f32(c0, vld1q_f32(kt + i))); kv1 = vaddq_f32(kv1, vmulq_f32(c1, vld1q_f32(kt + i + 4))); kv2 = vaddq_f32(kv2, vmulq_f32(c2, vld1q_f32(kt + i + 8))); kv3 = vaddq_f32(kv3, vmulq_f32(c3, vld1q_f32(kt + i + 12))); } for (; i + 4 <= dk; i += 4) { float32x4_t gate; if (vector_gate) gate = vld1q_f32(g + gate_offset * dk + i); else gate = vdupq_n_f32(g[gate_offset]); const float32x4_t decayed = vmulq_f32(vld1q_f32(row.data() + i), gate); vst1q_f32(candidate.data() + i, decayed); kv_tail = vaddq_f32(kv_tail, vmulq_f32(decayed, vld1q_f32(kt + i))); } kv_mem = vaddvq_f32(vaddq_f32(vaddq_f32(kv0, kv1), vaddq_f32(kv2, kv3))) + vaddvq_f32(kv_tail); for (; i < dk; ++i) { const float gate = g[vector_gate ? gate_offset * dk + i : gate_offset]; candidate[i] = row[i] * gate; kv_mem += candidate[i] * kt[i]; } const float delta = (v[v_offset] - kv_mem) * beta[gate_offset]; float32x4_t out0 = vdupq_n_f32(0.0f), out1 = out0; float32x4_t out2 = out0, out3 = out0, out_tail = out0; i = 0; const float32x4_t delta4 = vdupq_n_f32(delta); for (; i + 16 <= dk; i += 16) { float* ct = candidate.data() + i; const float32x4_t c0 = vaddq_f32(vld1q_f32(ct), vmulq_f32(vld1q_f32(kt + i), delta4)); const float32x4_t c1 = vaddq_f32(vld1q_f32(ct + 4), vmulq_f32(vld1q_f32(kt + i + 4), delta4)); const float32x4_t c2 = vaddq_f32(vld1q_f32(ct + 8), vmulq_f32(vld1q_f32(kt + i + 8), delta4)); const float32x4_t c3 = vaddq_f32(vld1q_f32(ct + 12), vmulq_f32(vld1q_f32(kt + i + 12), delta4)); vst1q_f32(ct, c0); vst1q_f32(ct + 4, c1); vst1q_f32(ct + 8, c2); vst1q_f32(ct + 12, c3); out0 = vaddq_f32(out0, vmulq_f32(c0, vld1q_f32(qt + i))); out1 = vaddq_f32(out1, vmulq_f32(c1, vld1q_f32(qt + i + 4))); out2 = vaddq_f32(out2, vmulq_f32(c2, vld1q_f32(qt + i + 8))); out3 = vaddq_f32(out3, vmulq_f32(c3, vld1q_f32(qt + i + 12))); } for (; i + 4 <= dk; i += 4) { const float32x4_t c = vaddq_f32(vld1q_f32(candidate.data() + i), vmulq_f32(vld1q_f32(kt + i), delta4)); vst1q_f32(candidate.data() + i, c); out_tail = vaddq_f32(out_tail, vmulq_f32(c, vld1q_f32(qt + i))); } float out = vaddvq_f32(vaddq_f32(vaddq_f32(out0, out1), vaddq_f32(out2, out3))) + vaddvq_f32(out_tail); for (; i < dk; ++i) { candidate[i] += kt[i] * delta; out += candidate[i] * qt[i]; } const size_t y_offset = (static_cast(b) * time + t) * hv_count * dv + static_cast(h) * dv + d; y[y_offset] = out; if (!has_mask || mask[static_cast(b) * time + t]) row.swap(candidate); } std::copy(row.begin(), row.end(), state_out + state_offset); } } } return 0; #endif } #ifdef __ARM_FEATURE_DOTPROD // Decode одного токена: четыре независимых аккумулятора без внутреннего // динамического цикла по выходам, с тем же порядком сумм по группам. static void integer_single_int8(const Layer& l, const int8_t* x, const float* scales, float* y) { const int groups=(l.din+l.group-1)/l.group; int j=0; for (; j+4<=l.dout; j+=4) { const auto* w0=reinterpret_cast(l.codes)+j*l.din; const auto* w1=w0+l.din; const auto* w2=w1+l.din; const auto* w3=w2+l.din; float sums[4]={y[j],y[j+1],y[j+2],y[j+3]}; for (int g=0; g(l.codes)+j*l.din; float sum_out=y[j]; for (int g=0; g static void integer_base_quantized(const Layer& l, const int8_t* activations, const float* scales, float* y) { if constexpr (Rows==1) { if (l.mode==3) { integer_single_int8(l,activations,scales,y); return; } } const int groups=(l.din+l.group-1)/l.group; const int stride=l.mode==3 ? l.din : (l.din+1)/2; for (int j=0; j>4)-7; for (int row=0; row(weights+k)); else { uint8x8_t packed=vld1_u8(weights+k/2); uint8x8x2_t q=vzip_u8(vand_u8(packed,vdup_n_u8(15)),vshr_n_u8(packed,4)); w=vsubq_s8(vreinterpretq_s8_u8(vcombine_u8(q.val[0],q.val[1])),vdupq_n_s8(7)); } for (int row=0; row(weights)[k] : int((weights[k/2]>>((k&1)*4))&15)-7; for (int row=0; row static void integer_base(const Layer& l, const float* x, float* y) { const int groups=(l.din+l.group-1)/l.group; std::vector activations(Rows*l.din); std::vector scales(Rows*groups); for (int row=0; row(l,activations.data(),scales.data(),y); } #endif static void linear(const Layer& l, const float* x, float* y) { std::vector normalized; const float* r = x; if (l.center) { normalized.resize(l.din); for (int i = 0; i < l.din; ++i) normalized[i] = (x[i] - l.center[i]) * l.unit[i]; r = normalized.data(); } std::copy(l.bias, l.bias + l.dout, y); if (l.mode >= 2) { #ifdef __ARM_FEATURE_DOTPROD integer_base<1>(l, r, y); #endif } else if (l.mode == 0) { if (l.indices) { for (int j = 0; j < l.dout; ++j) for (int k = 0; k < l.width; ++k) y[j] += r[l.indices[j*l.width+k]] * l.weight[j*l.width+k]; } else cblas_sgemv(CblasRowMajor, CblasNoTrans, l.dout, l.din, 1.0f, l.weight, l.din, r, 1, 1.0f, y, 1); } else { int stride = (l.width + 1) / 2; int groups = (l.width + l.group - 1) / l.group; for (int j = 0; j < l.dout; ++j) { const uint8_t* row = l.codes + j * stride; for (int g = 0; g < groups; ++g) { float sum = 0; int end = std::min((g + 1) * l.group, l.width); int k = g * l.group; #ifdef __aarch64__ // SIMD packed decode and FMA, with scalar tails for odd groups. if (k & 1) { int q = (row[k/2] >> 4) - 7; int i = l.indices ? l.indices[j*l.width+k] : k; sum += r[i]*q; ++k; } float32x4_t acc = vdupq_n_f32(0); for (; k + 16 <= end; k += 16) { uint8x8_t packed = vld1_u8(row+k/2); uint8x8x2_t nibbles = vzip_u8(vand_u8(packed,vdup_n_u8(15)),vshr_n_u8(packed,4)); for (int h = 0; h < 2; ++h) { int16x8_t q = vmovl_s8(vsub_s8(vreinterpret_s8_u8(nibbles.val[h]),vdup_n_s8(7))); float32x4_t low, high; if (l.indices) { float values[8]; for (int z = 0; z < 8; ++z) values[z] = r[l.indices[j*l.width+k+h*8+z]]; low = vld1q_f32(values); high = vld1q_f32(values+4); } else { low = vld1q_f32(r+k+h*8); high = vld1q_f32(r+k+h*8+4); } acc = vfmaq_f32(acc,low,vcvtq_f32_s32(vmovl_s16(vget_low_s16(q)))); acc = vfmaq_f32(acc,high,vcvtq_f32_s32(vmovl_s16(vget_high_s16(q)))); } } sum += vaddvq_f32(acc); #endif for (; k < end; ++k) { int q = ((row[k / 2] >> ((k & 1) * 4)) & 15) - 7; int i = l.indices ? l.indices[j * l.width + k] : k; sum += r[i] * q; } y[j] += sum * l.scales[j * groups + g]; } } } if (l.rank) { std::vector h(l.rank); cblas_sgemv(CblasRowMajor, CblasNoTrans, l.rank, l.din, 1.0f, l.a, l.din, r, 1, 0.0f, h.data(), 1); cblas_sgemv(CblasRowMajor, CblasNoTrans, l.dout, l.rank, 1.0f, l.b, l.rank, h.data(), 1, 1.0f, y, 1); } } extern "C" int relative_lookup_rows(const Layer* layer, const int32_t* rows, int count, float* output) { if (!layer || count < 0 || (count && (!rows || !output))) return 1; const auto& l = *layer; if (l.din < 1 || l.dout < 1 || l.group < 1 || l.rank || !zero_center(l) || l.indices || (l.mode != 1 && l.mode != 2 && l.mode != 3)) return 2; if (!l.codes || !l.scales) return 3; const int groups = (l.din + l.group - 1) / l.group; const int stride = l.mode == 3 ? l.din : (l.din + 1) / 2; for (int n = 0; n < count; ++n) { const int j = rows[n]; if (j < 0 || j >= l.dout) return 4; for (int k = 0; k < l.din; ++k) { float q; if (l.mode == 3) { q = reinterpret_cast(l.codes + j*stride)[k]; } else { q = ((l.codes[j*stride+k/2] >> ((k&1)*4)) & 15) - 7; } const float unit = l.unit ? l.unit[k] : 1.0f; output[n*l.din+k] = q*l.scales[j*groups+k/l.group]*unit; } } return 0; } extern "C" int relative_forward_many(const Layer* layers, int count, const float* input, float** outputs, int rows) { if (!layers || count < 1 || rows < 0 || (rows && (!input || !outputs))) return 1; if (!relative_integer_dot_supported()) return 5; const auto& first = layers[0]; if (first.din < 1 || first.group < 1 || first.rank || !zero_center(first) || (first.mode != 2 && first.mode != 3)) return 2; const int groups = (first.din + first.group - 1) / first.group; for (int k = 0; k < count; ++k) { const auto& l = layers[k]; if (l.din != first.din || l.group != first.group || l.dout < 1 || l.rank || !zero_center(l) || l.indices || !l.bias || !l.codes || !l.scales || (l.mode != 2 && l.mode != 3) || std::min(l.group,l.din) > INT32_MAX/(127*127) || (rows && !outputs[k])) return 3; for (int i = 0; i < first.din; ++i) { const float a = first.unit ? first.unit[i] : 1.0f; const float b = l.unit ? l.unit[i] : 1.0f; if (a != b) return 4; } } std::vector activations(rows*first.din); std::vector activation_scales(rows*groups); for (int row = 0; row < rows; ++row) { for (int g = 0; g < groups; ++g) { const int start = g*first.group, end = std::min(start+first.group, first.din); float maximum = 0.0f; for (int i = start; i < end; ++i) { const float unit = first.unit ? first.unit[i] : 1.0f; maximum = std::max(maximum, std::abs(input[row*first.din+i]*unit)); } const float scale = maximum ? maximum/127.0f : 1.0f; activation_scales[row*groups+g] = scale; for (int i = start; i < end; ++i) { const float unit = first.unit ? first.unit[i] : 1.0f; activations[row*first.din+i] = int8_t(std::max(-127.0f, std::min(127.0f, std::round(input[row*first.din+i]*unit/scale)))); } } } for (int n = 0; n < count; ++n) { const auto& l = layers[n]; for (int row = 0; row < rows; ++row) std::copy(l.bias, l.bias+l.dout, outputs[n]+row*l.dout); #ifdef __ARM_FEATURE_DOTPROD int row = 0; for (; row+4 <= rows; row += 4) integer_base_quantized<4>(l,activations.data()+row*l.din, activation_scales.data()+row*groups,outputs[n]+row*l.dout); for (; row < rows; ++row) integer_base_quantized<1>(l,activations.data()+row*l.din, activation_scales.data()+row*groups,outputs[n]+row*l.dout); #endif } return 0; } extern "C" int relative_forward(const Layer* layers, int count, const float* input, float* output, int rows) { if (!layers || count < 1 || rows < 0) return 1; for (int k = 0; k < count; ++k) { const auto& l = layers[k]; if (l.din < 1 || l.dout < 1 || l.width < 1 || l.group < 1 || (k && layers[k-1].dout != l.din)) return 2; if (l.mode>=2 && (!relative_integer_dot_supported() || l.indices)) return 3; } // Prefill uses the same optimized dense BLAS path as ordinary FP32 models. if (count == 1 && rows > 1 && !layers[0].mode && !layers[0].indices && !layers[0].center && !layers[0].rank) { const auto& l = layers[0]; for (int row = 0; row < rows; ++row) std::copy(l.bias, l.bias+l.dout, output+row*l.dout); cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasTrans, rows, l.dout, l.din, 1.0f, input, l.din, l.weight, l.din, 1.0f, output, l.dout); return 0; } if (count==1 && rows>1 && layers[0].mode>=2) { #ifdef __ARM_FEATURE_DOTPROD const auto& l=layers[0]; std::vector normalized; const float* x=input; if (l.center) { normalized.resize(rows*l.din); for (int row=0; row(l,x+row*l.din,output+row*l.dout); for (; row(l,x+row*l.din,output+row*l.dout); if (l.rank) { std::vector correction(rows*l.rank); cblas_sgemm(CblasRowMajor,CblasNoTrans,CblasTrans,rows,l.rank,l.din,1.0f,x,l.din,l.a,l.din,0.0f,correction.data(),l.rank); cblas_sgemm(CblasRowMajor,CblasNoTrans,CblasTrans,rows,l.dout,l.rank,1.0f,correction.data(),l.rank,l.b,l.rank,1.0f,output,l.dout); } return 0; #endif } if (count == 1 && rows > 1 && layers[0].mode==1 && !layers[0].indices) { const auto& l = layers[0]; std::vector normalized; const float* x = input; if (l.center) { normalized.resize(rows*l.din); for (int row = 0; row < rows; ++row) for (int k = 0; k < l.din; ++k) normalized[row*l.din+k] = (input[row*l.din+k]-l.center[k])*l.unit[k]; x = normalized.data(); } for (int row = 0; row < rows; ++row) std::copy(l.bias, l.bias+l.dout, output+row*l.dout); // Only a bounded input tile is decoded, never the full weight matrix. const int tile_width = std::min(32, l.din); std::vector tile(tile_width*l.dout); const int stride = (l.din+1)/2; const int groups = (l.din+l.group-1)/l.group; for (int start = 0; start < l.din; start += tile_width) { int width = std::min(tile_width, l.din-start); for (int j = 0; j < l.dout; ++j) for (int k = 0; k < width; ++k) { int ix = start+k; int q = ((l.codes[j*stride+ix/2] >> ((ix&1)*4))&15)-7; tile[j*width+k] = q*l.scales[j*groups+ix/l.group]; } cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasTrans, rows, l.dout, width, 1.0f, x+start, l.din, tile.data(), width, 1.0f, output, l.dout); } if (l.rank) { std::vector correction(rows*l.rank); cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasTrans, rows, l.rank, l.din, 1.0f, x, l.din, l.a, l.din, 0.0f, correction.data(), l.rank); cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasTrans, rows, l.dout, l.rank, 1.0f, correction.data(), l.rank, l.b, l.rank, 1.0f, output, l.dout); } return 0; } for (int row = 0; row < rows; ++row) { const float* x = input + row * layers[0].din; std::vector current, next; for (int k = 0; k < count; ++k) { next.resize(layers[k].dout); linear(layers[k], x, next.data()); if (k + 1 < count) for (float& v : next) v = std::max(0.0f, v); current.swap(next); x = current.data(); } std::copy(current.begin(), current.end(), output + row * layers[count-1].dout); } return 0; }