#include "cpu_pool.h" #include "cpu_threads.h" #include "cpu_kernel_args.h" #include "packed_matmul_cpu.h" #include "../torch-ext/torch_binding.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__aarch64__) || defined(_M_ARM64) #include #endif #if (defined(__x86_64__) || defined(_M_X64)) && !defined(_MSC_VER) #include #define ORBITQUANT_TARGET_AVX2 __attribute__((target("avx2,fma,f16c"))) #define ORBITQUANT_TARGET_AVX512 \ __attribute__((target("avx512f,avx512dq,avx512bw,avx512vl,fma,f16c"))) #endif namespace { using orbitquant::cpu::ActivationArgs; using orbitquant::cpu::ActivationIsa; ActivationIsa select_activation_isa() { const char *requested = std::getenv("ORBITQUANT_CPU_ISA"); if (requested == nullptr || std::strcmp(requested, "auto") == 0) { if (orbitquant::cpu::packed_matmul_x86_avx512_available()) { return ActivationIsa::Avx512; } if (orbitquant::cpu::packed_matmul_x86_avx2_available()) { return ActivationIsa::Avx2; } if (orbitquant::cpu::packed_matmul_neon_available()) { return ActivationIsa::Neon; } return ActivationIsa::Portable; } if (std::strcmp(requested, "scalar") == 0) { return ActivationIsa::Portable; } if (std::strcmp(requested, "avx2") == 0) { STD_TORCH_CHECK( orbitquant::cpu::packed_matmul_x86_avx2_available(), "ORBITQUANT_CPU_ISA=avx2 requested AVX2/FMA/F16C on an unsupported CPU"); return ActivationIsa::Avx2; } if (std::strcmp(requested, "avx512") == 0) { STD_TORCH_CHECK( orbitquant::cpu::packed_matmul_x86_avx512_available(), "ORBITQUANT_CPU_ISA=avx512 requested AVX-512F/DQ/BW/VL on an unsupported CPU"); return ActivationIsa::Avx512; } if (std::strcmp(requested, "neon") == 0) { STD_TORCH_CHECK( orbitquant::cpu::packed_matmul_neon_available(), "ORBITQUANT_CPU_ISA=neon requested NEON on an unsupported CPU"); return ActivationIsa::Neon; } STD_TORCH_CHECK( false, "ORBITQUANT_CPU_ISA must be auto, scalar, avx2, avx512, or neon"); return ActivationIsa::Portable; } orbitquant::cpu::ScalarKind activation_scalar_kind( OrbitQuantTensor const &tensor) { using torch::headeronly::ScalarType; switch (tensor.scalar_type()) { case ScalarType::Float: return orbitquant::cpu::ScalarKind::Float32; case ScalarType::Half: return orbitquant::cpu::ScalarKind::Float16; case ScalarType::BFloat16: return orbitquant::cpu::ScalarKind::BFloat16; default: STD_TORCH_CHECK( false, "CPU activation quantization supports float32, float16, and bfloat16 inputs"); } return orbitquant::cpu::ScalarKind::Float32; } template inline float load_scalar(void const *data, std::int64_t offset) { return static_cast(static_cast(data)[offset]); } template <> inline float load_scalar(void const *data, std::int64_t offset) { return static_cast(data)[offset]; } template inline void store_scalar(void *data, std::int64_t offset, float value) { static_cast(data)[offset] = scalar_t(value); } template <> inline void store_scalar(void *data, std::int64_t offset, float value) { static_cast(data)[offset] = value; } inline void fwht_block_portable(float *values, std::int64_t block_size) { for (std::int64_t half = 1; half < block_size; half *= 2) { for (std::int64_t base = 0; base < block_size; base += 2 * half) { for (std::int64_t offset = 0; offset < half; ++offset) { const float left = values[base + offset]; const float right = values[base + half + offset]; values[base + offset] = left + right; values[base + half + offset] = left - right; } } } } #if defined(__aarch64__) || defined(_M_ARM64) inline void fwht_block_neon(float *values, std::int64_t block_size) { if (block_size < 4) { fwht_block_portable(values, block_size); return; } const uint32x4_t odd_lanes = {0u, 0xFFFFFFFFu, 0u, 0xFFFFFFFFu}; const uint32x4_t high_lanes = {0u, 0u, 0xFFFFFFFFu, 0xFFFFFFFFu}; for (std::int64_t base = 0; base < block_size; base += 4) { float32x4_t value = vld1q_f32(values + base); float32x4_t swapped = vrev64q_f32(value); value = vbslq_f32( odd_lanes, vsubq_f32(swapped, value), vaddq_f32(value, swapped)); swapped = vextq_f32(value, value, 2); value = vbslq_f32( high_lanes, vsubq_f32(swapped, value), vaddq_f32(value, swapped)); vst1q_f32(values + base, value); } for (std::int64_t half = 4; half < block_size; half *= 2) { for (std::int64_t base = 0; base < block_size; base += 2 * half) { for (std::int64_t offset = 0; offset + 4 <= half; offset += 4) { const float32x4_t left = vld1q_f32(values + base + offset); const float32x4_t right = vld1q_f32(values + base + half + offset); vst1q_f32(values + base + offset, vaddq_f32(left, right)); vst1q_f32(values + base + half + offset, vsubq_f32(left, right)); } } } } #endif #if (defined(__x86_64__) || defined(_M_X64)) && !defined(_MSC_VER) ORBITQUANT_TARGET_AVX2 void fwht_block_avx2( float *values, std::int64_t block_size) { if (block_size < 8) { fwht_block_portable(values, block_size); return; } for (std::int64_t base = 0; base < block_size; base += 8) { __m256 value = _mm256_loadu_ps(values + base); __m256 swapped = _mm256_permute_ps(value, 0xB1); value = _mm256_blend_ps( _mm256_add_ps(value, swapped), _mm256_sub_ps(swapped, value), 0xAA); swapped = _mm256_permute_ps(value, 0x4E); value = _mm256_blend_ps( _mm256_add_ps(value, swapped), _mm256_sub_ps(swapped, value), 0xCC); swapped = _mm256_permute2f128_ps(value, value, 0x01); value = _mm256_blend_ps( _mm256_add_ps(value, swapped), _mm256_sub_ps(swapped, value), 0xF0); _mm256_storeu_ps(values + base, value); } for (std::int64_t half = 8; half < block_size; half *= 2) { for (std::int64_t base = 0; base < block_size; base += 2 * half) { for (std::int64_t offset = 0; offset + 8 <= half; offset += 8) { const __m256 left = _mm256_loadu_ps(values + base + offset); const __m256 right = _mm256_loadu_ps(values + base + half + offset); _mm256_storeu_ps(values + base + offset, _mm256_add_ps(left, right)); _mm256_storeu_ps( values + base + half + offset, _mm256_sub_ps(left, right)); } } } } ORBITQUANT_TARGET_AVX512 void fwht_block_avx512( float *values, std::int64_t block_size) { if (block_size < 16) { fwht_block_portable(values, block_size); return; } // Butterfly widths 1-8 stay inside one 16-float register: swap the paired // lanes with shuffles and blend the +/- results, one pass over memory // instead of four. for (std::int64_t base = 0; base < block_size; base += 16) { __m512 value = _mm512_loadu_ps(values + base); __m512 swapped = _mm512_permute_ps(value, 0xB1); value = _mm512_mask_blend_ps( 0xAAAA, _mm512_add_ps(value, swapped), _mm512_sub_ps(swapped, value)); swapped = _mm512_permute_ps(value, 0x4E); value = _mm512_mask_blend_ps( 0xCCCC, _mm512_add_ps(value, swapped), _mm512_sub_ps(swapped, value)); swapped = _mm512_shuffle_f32x4(value, value, 0xB1); value = _mm512_mask_blend_ps( 0xF0F0, _mm512_add_ps(value, swapped), _mm512_sub_ps(swapped, value)); swapped = _mm512_shuffle_f32x4(value, value, 0x4E); value = _mm512_mask_blend_ps( 0xFF00, _mm512_add_ps(value, swapped), _mm512_sub_ps(swapped, value)); _mm512_storeu_ps(values + base, value); } for (std::int64_t half = 16; half < block_size; half *= 2) { for (std::int64_t base = 0; base < block_size; base += 2 * half) { for (std::int64_t offset = 0; offset + 16 <= half; offset += 16) { const __m512 left = _mm512_loadu_ps(values + base + offset); const __m512 right = _mm512_loadu_ps(values + base + half + offset); _mm512_storeu_ps(values + base + offset, _mm512_add_ps(left, right)); _mm512_storeu_ps( values + base + half + offset, _mm512_sub_ps(left, right)); } } } } #endif inline void fwht_block( float *values, std::int64_t block_size, ActivationIsa isa) { #if defined(__x86_64__) || defined(_M_X64) if (isa == ActivationIsa::Avx512) { #if defined(_MSC_VER) orbitquant::cpu::activation_fwht_msvc_avx2(values, block_size); #else fwht_block_avx512(values, block_size); #endif return; } if (isa == ActivationIsa::Avx2) { #if defined(_MSC_VER) orbitquant::cpu::activation_fwht_msvc_avx2(values, block_size); #else fwht_block_avx2(values, block_size); #endif return; } #endif #if defined(__aarch64__) || defined(_M_ARM64) if (isa == ActivationIsa::Neon) { fwht_block_neon(values, block_size); return; } #endif fwht_block_portable(values, block_size); } template float squared_norm_scalar(void const *data, std::int64_t offset, std::int64_t dim) { float result = 0.0f; for (std::int64_t index = 0; index < dim; ++index) { const float value = load_scalar(data, offset + index); result += value * value; } return result; } #if defined(__aarch64__) || defined(_M_ARM64) template float squared_norm_neon(void const *data, std::int64_t offset, std::int64_t dim) { float32x4_t accumulator = vdupq_n_f32(0.0f); std::int64_t index = 0; for (; index + 4 <= dim; index += 4) { float32x4_t values; if constexpr (std::is_same_v) { values = vld1q_f32(static_cast(data) + offset + index); } else if constexpr (std::is_same_v) { const auto *source = reinterpret_cast( static_cast(data) + offset + index); values = vcvt_f32_f16(vld1_f16(source)); } else { const uint16x4_t raw = vld1_u16( static_cast(data) + offset + index); values = vreinterpretq_f32_u32(vshlq_n_u32(vmovl_u16(raw), 16)); } accumulator = vfmaq_f32(accumulator, values, values); } float result = vaddvq_f32(accumulator); for (; index < dim; ++index) { const float value = load_scalar(data, offset + index); result += value * value; } return result; } #endif #if (defined(__x86_64__) || defined(_M_X64)) && !defined(_MSC_VER) template ORBITQUANT_TARGET_AVX2 float squared_norm_avx2( void const *data, std::int64_t offset, std::int64_t dim) { __m256 accumulator = _mm256_setzero_ps(); std::int64_t index = 0; for (; index + 8 <= dim; index += 8) { __m256 values; if constexpr (std::is_same_v) { values = _mm256_loadu_ps( static_cast(data) + offset + index); } else if constexpr (std::is_same_v) { const auto *source = static_cast(data) + offset + index; values = _mm256_cvtph_ps( _mm_loadu_si128(reinterpret_cast<__m128i const *>(source))); } else { const auto *source = static_cast(data) + offset + index; const __m128i packed = _mm_loadu_si128(reinterpret_cast<__m128i const *>(source)); values = _mm256_castsi256_ps( _mm256_slli_epi32(_mm256_cvtepu16_epi32(packed), 16)); } accumulator = _mm256_fmadd_ps(values, values, accumulator); } const __m128 halves = _mm_add_ps( _mm256_castps256_ps128(accumulator), _mm256_extractf128_ps(accumulator, 1)); const __m128 pairs = _mm_hadd_ps(halves, halves); float result = _mm_cvtss_f32(_mm_hadd_ps(pairs, pairs)); for (; index < dim; ++index) { const float value = load_scalar(data, offset + index); result += value * value; } return result; } template ORBITQUANT_TARGET_AVX512 float squared_norm_avx512( void const *data, std::int64_t offset, std::int64_t dim) { __m512 accumulator = _mm512_setzero_ps(); std::int64_t index = 0; for (; index + 16 <= dim; index += 16) { __m512 values; if constexpr (std::is_same_v) { values = _mm512_loadu_ps( static_cast(data) + offset + index); } else if constexpr (std::is_same_v) { const auto *source = static_cast(data) + offset + index; values = _mm512_cvtph_ps( _mm256_loadu_si256(reinterpret_cast<__m256i const *>(source))); } else { const auto *source = static_cast(data) + offset + index; const __m256i packed = _mm256_loadu_si256(reinterpret_cast<__m256i const *>(source)); values = _mm512_castsi512_ps( _mm512_slli_epi32(_mm512_cvtepu16_epi32(packed), 16)); } accumulator = _mm512_fmadd_ps(values, values, accumulator); } float result = _mm512_reduce_add_ps(accumulator); for (; index < dim; ++index) { const float value = load_scalar(data, offset + index); result += value * value; } return result; } #endif template float squared_norm( void const *data, std::int64_t offset, std::int64_t dim, ActivationIsa isa) { #if defined(__x86_64__) || defined(_M_X64) if (isa == ActivationIsa::Avx512) { #if defined(_MSC_VER) constexpr auto scalar_kind = std::is_same_v ? orbitquant::cpu::ScalarKind::Float32 : std::is_same_v ? orbitquant::cpu::ScalarKind::Float16 : orbitquant::cpu::ScalarKind::BFloat16; return orbitquant::cpu::activation_squared_norm_msvc_avx2( data, scalar_kind, offset, dim); #else return squared_norm_avx512(data, offset, dim); #endif } if (isa == ActivationIsa::Avx2) { #if defined(_MSC_VER) constexpr auto scalar_kind = std::is_same_v ? orbitquant::cpu::ScalarKind::Float32 : std::is_same_v ? orbitquant::cpu::ScalarKind::Float16 : orbitquant::cpu::ScalarKind::BFloat16; return orbitquant::cpu::activation_squared_norm_msvc_avx2( data, scalar_kind, offset, dim); #else return squared_norm_avx2(data, offset, dim); #endif } #endif #if defined(__aarch64__) || defined(_M_ARM64) if (isa == ActivationIsa::Neon) { return squared_norm_neon(data, offset, dim); } #endif return squared_norm_scalar(data, offset, dim); } inline std::int64_t nearest_centroid( float value, float const *boundaries, std::int64_t boundary_count) { std::int64_t low = 0; std::int64_t high = boundary_count; while (low < high) { const std::int64_t middle = low + (high - low) / 2; if (value <= boundaries[middle]) { high = middle; } else { low = middle + 1; } } return low; } template void quantize_lookup_scalar( ActivationArgs const &args, float const *scratch, std::int64_t output_offset, float norm, std::int64_t start = 0) { for (std::int64_t index = start; index < args.dim; ++index) { const float direction = scratch[index] * args.inv_sqrt_block; const std::int64_t centroid_index = nearest_centroid(direction, args.boundaries, args.boundary_count); store_scalar( args.out, output_offset + index, args.centroids[centroid_index] * norm); } } #if (defined(__x86_64__) || defined(_M_X64)) && !defined(_MSC_VER) ORBITQUANT_TARGET_AVX2 inline __m128i float8_to_bfloat8(__m256 values) { const __m256i bits = _mm256_castps_si256(values); const __m256i absolute_bits = _mm256_and_si256(bits, _mm256_set1_epi32(0x7fffffff)); const __m256i nan_mask = _mm256_cmpgt_epi32(absolute_bits, _mm256_set1_epi32(0x7f800000)); const __m256i rounding = _mm256_add_epi32( _mm256_set1_epi32(0x7fff), _mm256_and_si256(_mm256_srli_epi32(bits, 16), _mm256_set1_epi32(1))); const __m256i upper = _mm256_srli_epi32( _mm256_add_epi32(bits, rounding), 16); __m128i packed = _mm_packus_epi32( _mm256_castsi256_si128(upper), _mm256_extracti128_si256(upper, 1)); const __m128i packed_nan_mask = _mm_packs_epi32( _mm256_castsi256_si128(nan_mask), _mm256_extracti128_si256(nan_mask, 1)); packed = _mm_blendv_epi8( packed, _mm_set1_epi16(0x7fc0), packed_nan_mask); return packed; } ORBITQUANT_TARGET_AVX512 inline __m256i float16_to_bfloat16(__m512 values) { const __m512i bits = _mm512_castps_si512(values); const __m512i absolute_bits = _mm512_and_si512(bits, _mm512_set1_epi32(0x7fffffff)); const __mmask16 nan_mask = _mm512_cmp_epu32_mask( absolute_bits, _mm512_set1_epi32(0x7f800000), _MM_CMPINT_GT); const __m512i rounding = _mm512_add_epi32( _mm512_set1_epi32(0x7fff), _mm512_and_si512(_mm512_srli_epi32(bits, 16), _mm512_set1_epi32(1))); const __m512i upper = _mm512_srli_epi32( _mm512_add_epi32(bits, rounding), 16); return _mm256_mask_mov_epi16( _mm512_cvtepi32_epi16(upper), nan_mask, _mm256_set1_epi16(0x7fc0)); } template ORBITQUANT_TARGET_AVX2 void quantize_lookup_avx2( ActivationArgs const &args, float const *scratch, std::int64_t output_offset, float norm) { const __m256 inverse_sqrt_block = _mm256_set1_ps(args.inv_sqrt_block); const __m256 output_norm = _mm256_set1_ps(norm); const __m256i ones = _mm256_set1_epi32(1); std::int64_t index = 0; for (; index + 8 <= args.dim; index += 8) { const __m256 direction = _mm256_mul_ps( _mm256_loadu_ps(scratch + index), inverse_sqrt_block); __m256i centroid_indices = _mm256_setzero_si256(); for (std::int64_t boundary = 0; boundary < args.boundary_count; ++boundary) { const __m256 comparison = _mm256_cmp_ps( direction, _mm256_set1_ps(args.boundaries[boundary]), _CMP_NLE_UQ); centroid_indices = _mm256_add_epi32( centroid_indices, _mm256_and_si256(_mm256_castps_si256(comparison), ones)); } const __m256 output = _mm256_mul_ps( _mm256_i32gather_ps(args.centroids, centroid_indices, 4), output_norm); if constexpr (std::is_same_v) { _mm256_storeu_ps( static_cast(args.out) + output_offset + index, output); } else if constexpr (std::is_same_v) { _mm_storeu_si128( reinterpret_cast<__m128i *>( static_cast(args.out) + output_offset + index), _mm256_cvtps_ph(output, _MM_FROUND_TO_NEAREST_INT)); } else { _mm_storeu_si128( reinterpret_cast<__m128i *>( static_cast(args.out) + output_offset + index), float8_to_bfloat8(output)); } } quantize_lookup_scalar(args, scratch, output_offset, norm, index); } template ORBITQUANT_TARGET_AVX512 void quantize_lookup_avx512( ActivationArgs const &args, float const *scratch, std::int64_t output_offset, float norm) { const __m512 inverse_sqrt_block = _mm512_set1_ps(args.inv_sqrt_block); const __m512 output_norm = _mm512_set1_ps(norm); const __m512i ones = _mm512_set1_epi32(1); std::int64_t index = 0; for (; index + 16 <= args.dim; index += 16) { const __m512 direction = _mm512_mul_ps( _mm512_loadu_ps(scratch + index), inverse_sqrt_block); __m512i centroid_indices = _mm512_setzero_si512(); for (std::int64_t boundary = 0; boundary < args.boundary_count; ++boundary) { const __mmask16 comparison = _mm512_cmp_ps_mask( direction, _mm512_set1_ps(args.boundaries[boundary]), _CMP_NLE_UQ); centroid_indices = _mm512_mask_add_epi32( centroid_indices, comparison, centroid_indices, ones); } const __m512 output = _mm512_mul_ps( _mm512_i32gather_ps(centroid_indices, args.centroids, 4), output_norm); if constexpr (std::is_same_v) { _mm512_storeu_ps( static_cast(args.out) + output_offset + index, output); } else if constexpr (std::is_same_v) { _mm256_storeu_si256( reinterpret_cast<__m256i *>( static_cast(args.out) + output_offset + index), _mm512_cvtps_ph(output, _MM_FROUND_TO_NEAREST_INT)); } else { _mm256_storeu_si256( reinterpret_cast<__m256i *>( static_cast(args.out) + output_offset + index), float16_to_bfloat16(output)); } } quantize_lookup_scalar(args, scratch, output_offset, norm, index); } #endif template void quantize_lookup( ActivationArgs const &args, float const *scratch, std::int64_t output_offset, float norm, ActivationIsa isa) { #if defined(__x86_64__) || defined(_M_X64) if (isa == ActivationIsa::Avx512) { #if defined(_MSC_VER) orbitquant::cpu::activation_quantize_lookup_msvc_avx2( args, scratch, output_offset, norm); #else quantize_lookup_avx512(args, scratch, output_offset, norm); #endif return; } if (isa == ActivationIsa::Avx2) { #if defined(_MSC_VER) orbitquant::cpu::activation_quantize_lookup_msvc_avx2( args, scratch, output_offset, norm); #else quantize_lookup_avx2(args, scratch, output_offset, norm); #endif return; } #endif quantize_lookup_scalar(args, scratch, output_offset, norm); } template void quantize_activation_range( ActivationArgs const &args, index_t const *permutation, std::int64_t row_start, std::int64_t row_end) { thread_local std::vector scratch; if (scratch.size() < static_cast(args.dim)) { scratch.resize(static_cast(args.dim)); } for (std::int64_t row = row_start; row < row_end; ++row) { const std::int64_t input_offset = row * args.dim; const float norm_squared = squared_norm(args.x, input_offset, args.dim, args.isa); const float norm = std::sqrt(norm_squared); const float inverse_norm = 1.0f / (norm + args.eps); for (std::int64_t index = 0; index < args.dim; ++index) { const float value = load_scalar( args.x, input_offset + static_cast(permutation[index])); scratch[index] = value * static_cast(args.signs[index]) * inverse_norm; } for (std::int64_t block = 0; block < args.dim; block += args.block_size) { fwht_block(scratch.data() + block, args.block_size, args.isa); } quantize_lookup( args, scratch.data(), input_offset, norm, args.isa); } } template void quantize_activation_dispatch_indexed( ActivationArgs const &args, index_t const *permutation, std::int64_t row_start, std::int64_t row_end) { switch (args.scalar_kind) { case orbitquant::cpu::ScalarKind::Float32: quantize_activation_range(args, permutation, row_start, row_end); return; case orbitquant::cpu::ScalarKind::Float16: quantize_activation_range(args, permutation, row_start, row_end); return; case orbitquant::cpu::ScalarKind::BFloat16: quantize_activation_range(args, permutation, row_start, row_end); return; } } void quantize_activation_dispatch( ActivationArgs const &args, std::int64_t row_start, std::int64_t row_end) { if (args.permutation_i32 != nullptr) { quantize_activation_dispatch_indexed( args, args.permutation_i32, row_start, row_end); return; } quantize_activation_dispatch_indexed(args, args.permutation, row_start, row_end); } void parallel_quantize_activations(ActivationArgs const &args) { const std::int64_t values = args.rows * args.dim; const int threads = values < 16'384 ? 1 : std::max( 1, std::min( orbitquant::cpu::requested_threads(), args.rows)); if (threads == 1) { quantize_activation_dispatch(args, 0, args.rows); return; } std::vector> ranges; ranges.reserve(threads); const std::int64_t rows_per_thread = (args.rows + threads - 1) / threads; for (int thread = 0; thread < threads; ++thread) { const std::int64_t start = thread * rows_per_thread; const std::int64_t end = std::min(args.rows, start + rows_per_thread); if (start >= end) { break; } ranges.emplace_back(start, end); } orbitquant::cpu::run_ranges( ranges, [&args](std::int64_t start, std::int64_t end) { quantize_activation_dispatch(args, start, end); }); } } // namespace void quantize_activations_cpu( OrbitQuantTensor &out, OrbitQuantTensor const &x, OrbitQuantTensor const &permutation, OrbitQuantTensor const &signs, OrbitQuantTensor const ¢roids, OrbitQuantTensor const &boundaries, double eps, double inv_sqrt_block, int64_t block_size) { using torch::headeronly::DeviceType; using torch::headeronly::ScalarType; STD_TORCH_CHECK(x.device().type() == DeviceType::CPU, "x must be a CPU tensor"); STD_TORCH_CHECK(out.device().type() == DeviceType::CPU, "out must be a CPU tensor"); STD_TORCH_CHECK( permutation.device().type() == DeviceType::CPU, "permutation must be a CPU tensor"); STD_TORCH_CHECK(signs.device().type() == DeviceType::CPU, "signs must be a CPU tensor"); STD_TORCH_CHECK( centroids.device().type() == DeviceType::CPU, "centroids must be a CPU tensor"); STD_TORCH_CHECK( boundaries.device().type() == DeviceType::CPU, "boundaries must be a CPU tensor"); STD_TORCH_CHECK(x.is_contiguous(), "x must be contiguous"); STD_TORCH_CHECK(out.is_contiguous(), "out must be contiguous"); STD_TORCH_CHECK(permutation.is_contiguous(), "permutation must be contiguous"); STD_TORCH_CHECK(signs.is_contiguous(), "signs must be contiguous"); STD_TORCH_CHECK(centroids.is_contiguous(), "centroids must be contiguous"); STD_TORCH_CHECK(boundaries.is_contiguous(), "boundaries must be contiguous"); STD_TORCH_CHECK(out.scalar_type() == x.scalar_type(), "out dtype must match x dtype"); STD_TORCH_CHECK( permutation.scalar_type() == ScalarType::Long || permutation.scalar_type() == ScalarType::Int, "permutation must be int32 or int64"); STD_TORCH_CHECK(signs.scalar_type() == ScalarType::Char, "signs must be int8"); STD_TORCH_CHECK(centroids.scalar_type() == ScalarType::Float, "centroids must be float32"); STD_TORCH_CHECK(boundaries.scalar_type() == ScalarType::Float, "boundaries must be float32"); STD_TORCH_CHECK(x.dim() == 2, "x must be rank 2"); STD_TORCH_CHECK(out.dim() == 2, "out must be rank 2"); STD_TORCH_CHECK(out.size(0) == x.size(0), "out row count must match x"); STD_TORCH_CHECK(out.size(1) == x.size(1), "out dimension must match x"); const int64_t dim = x.size(1); STD_TORCH_CHECK(permutation.numel() == dim, "permutation must match the input dimension"); STD_TORCH_CHECK(signs.numel() == dim, "signs must match the input dimension"); STD_TORCH_CHECK( centroids.numel() == boundaries.numel() + 1, "centroids must contain exactly one more value than boundaries"); STD_TORCH_CHECK(centroids.numel() >= 2, "at least two centroids are required"); STD_TORCH_CHECK(block_size > 0, "block_size must be positive"); STD_TORCH_CHECK( (block_size & (block_size - 1)) == 0, "block_size must be a power of two"); STD_TORCH_CHECK(dim % block_size == 0, "block_size must divide the input dimension"); STD_TORCH_CHECK(eps >= 0.0, "eps must be non-negative"); STD_TORCH_CHECK(inv_sqrt_block > 0.0, "inv_sqrt_block must be positive"); const bool int32_permutation = permutation.scalar_type() == ScalarType::Int; const std::int64_t *permutation_values = int32_permutation ? nullptr : permutation.const_data_ptr(); const std::int32_t *permutation_values_i32 = int32_permutation ? permutation.const_data_ptr() : nullptr; const auto *sign_values = signs.const_data_ptr(); const auto permutation_at = [&](std::int64_t index) -> std::int64_t { return int32_permutation ? static_cast(permutation_values_i32[index]) : permutation_values[index]; }; // The permutation/sign buffers are immutable module constants, so cache a // fingerprint of validated buffers instead of re-scanning them per forward. struct ValidatedEntry { void const *permutation; void const *signs; std::int64_t dim; std::int64_t head; std::int64_t tail; }; static std::mutex validated_mutex; static std::array validated_entries{}; static std::size_t validated_cursor = 0; const ValidatedEntry candidate{ int32_permutation ? static_cast(permutation_values_i32) : static_cast(permutation_values), sign_values, dim, permutation_at(0), permutation_at(dim - 1)}; bool already_validated = false; { std::lock_guard lock(validated_mutex); for (auto const &entry : validated_entries) { if (entry.permutation == candidate.permutation && entry.signs == candidate.signs && entry.dim == candidate.dim && entry.head == candidate.head && entry.tail == candidate.tail) { already_validated = true; break; } } } if (!already_validated) { for (int64_t index = 0; index < dim; ++index) { const std::int64_t source_index = permutation_at(index); STD_TORCH_CHECK( source_index >= 0 && source_index < dim, "permutation contains an out-of-range index"); STD_TORCH_CHECK( sign_values[index] == -1 || sign_values[index] == 1, "signs must contain only -1 and 1"); } std::lock_guard lock(validated_mutex); validated_entries[validated_cursor % validated_entries.size()] = candidate; ++validated_cursor; } if (x.numel() == 0) { return; } const ActivationArgs args{ out.mutable_data_ptr(), x.const_data_ptr(), permutation_values, permutation_values_i32, sign_values, centroids.const_data_ptr(), boundaries.const_data_ptr(), activation_scalar_kind(x), select_activation_isa(), x.size(0), dim, boundaries.numel(), block_size, static_cast(eps), static_cast(inv_sqrt_block), }; parallel_quantize_activations(args); }