@@ -612,34 +612,37 @@ Tensor& dequantize_per_tensor_out(
612612 const int8_t * __restrict__ input_data = input.const_data_ptr <int8_t >();
613613#if defined(__XTENSA__)
614614 // Direct inline PDX SIMD dequantization for per-tensor int8->float32.
615- // 4x-unrolled to improve ILP: the Fusion G3 scheduler can interleave
616- // independent loads, converts, and FP MACs across unrolled iterations,
617- // hiding load-to-use latency and reducing loop overhead.
615+ // The zero-point subtract stays in the int32 domain (PDX_SUB_MX32) and
616+ // the int->float conversion is folded into PDX_MUL_MXF32 (mixed int32 x
617+ // float32 -> float32), so each element costs one fused mul (sym) or one
618+ // int sub + one fused mul (asym) with no separate convert. The int sub
619+ // issues on the integer unit, overlapping the float mul, so the asym path
620+ // stays store-bound. 4x-unrolled to hide load-to-use latency.
618621 if (dequant_simd_aligned (input_data, out_data)) {
619622 const int numel = inp_shape[0 ];
620623 auto vIn = reinterpret_cast <const xb_vecMx8*>(input_data);
621624 auto vOut = reinterpret_cast <xb_vecMxf32*>(out_data);
622625 const xb_vecMxf32 v_scale{
623626 scale_data, scale_data, scale_data, scale_data};
624627 int i = 0 ;
628+ // 4x unrolled main loop: 16 elements per iteration
629+ const int e16 = (numel >> 4 ) << 4 ;
625630 if (zero_point_data != 0 ) {
631+ const xb_vecMx32 v_zp{
632+ zero_point_data,
633+ zero_point_data,
634+ zero_point_data,
635+ zero_point_data};
626636 const float zp_f = static_cast <float >(zero_point_data);
627- const xb_vecMxf32 v_zp{zp_f, zp_f, zp_f, zp_f};
628- // 4x unrolled main loop: 16 elements per iteration
629- const int e16 = (numel >> 4 ) << 4 ;
630637 for (; i < e16 ; i += 16 ) {
631638 xb_vecMx32 vP0 = PDX_LV32_MX8_I (vIn, 0 );
632639 xb_vecMx32 vP1 = PDX_LV32_MX8_I (vIn + 1 , 0 );
633640 xb_vecMx32 vP2 = PDX_LV32_MX8_I (vIn + 2 , 0 );
634641 xb_vecMx32 vP3 = PDX_LV32_MX8_I (vIn + 3 , 0 );
635- xb_vecMxf32 vF0 = (xb_vecMxf32)vP0;
636- xb_vecMxf32 vF1 = (xb_vecMxf32)vP1;
637- xb_vecMxf32 vF2 = (xb_vecMxf32)vP2;
638- xb_vecMxf32 vF3 = (xb_vecMxf32)vP3;
639- vOut[0 ] = PDX_MUL_MXF32 (PDX_SUB_MXF32 (vF0, v_zp), v_scale);
640- vOut[1 ] = PDX_MUL_MXF32 (PDX_SUB_MXF32 (vF1, v_zp), v_scale);
641- vOut[2 ] = PDX_MUL_MXF32 (PDX_SUB_MXF32 (vF2, v_zp), v_scale);
642- vOut[3 ] = PDX_MUL_MXF32 (PDX_SUB_MXF32 (vF3, v_zp), v_scale);
642+ vOut[0 ] = PDX_MUL_MXF32 (PDX_SUB_MX32 (vP0, v_zp), v_scale);
643+ vOut[1 ] = PDX_MUL_MXF32 (PDX_SUB_MX32 (vP1, v_zp), v_scale);
644+ vOut[2 ] = PDX_MUL_MXF32 (PDX_SUB_MX32 (vP2, v_zp), v_scale);
645+ vOut[3 ] = PDX_MUL_MXF32 (PDX_SUB_MX32 (vP3, v_zp), v_scale);
643646 vIn += 4 ;
644647 vOut += 4 ;
645648 }
@@ -649,21 +652,15 @@ Tensor& dequantize_per_tensor_out(
649652 (static_cast <float >(input_data[i]) - zp_f) * scale_data;
650653 }
651654 } else {
652- // 4x unrolled main loop: 16 elements per iteration
653- const int e16 = (numel >> 4 ) << 4 ;
654655 for (; i < e16 ; i += 16 ) {
655656 xb_vecMx32 vP0 = PDX_LV32_MX8_I (vIn, 0 );
656657 xb_vecMx32 vP1 = PDX_LV32_MX8_I (vIn + 1 , 0 );
657658 xb_vecMx32 vP2 = PDX_LV32_MX8_I (vIn + 2 , 0 );
658659 xb_vecMx32 vP3 = PDX_LV32_MX8_I (vIn + 3 , 0 );
659- xb_vecMxf32 vF0 = (xb_vecMxf32)vP0;
660- xb_vecMxf32 vF1 = (xb_vecMxf32)vP1;
661- xb_vecMxf32 vF2 = (xb_vecMxf32)vP2;
662- xb_vecMxf32 vF3 = (xb_vecMxf32)vP3;
663- vOut[0 ] = PDX_MUL_MXF32 (vF0, v_scale);
664- vOut[1 ] = PDX_MUL_MXF32 (vF1, v_scale);
665- vOut[2 ] = PDX_MUL_MXF32 (vF2, v_scale);
666- vOut[3 ] = PDX_MUL_MXF32 (vF3, v_scale);
660+ vOut[0 ] = PDX_MUL_MXF32 (vP0, v_scale);
661+ vOut[1 ] = PDX_MUL_MXF32 (vP1, v_scale);
662+ vOut[2 ] = PDX_MUL_MXF32 (vP2, v_scale);
663+ vOut[3 ] = PDX_MUL_MXF32 (vP3, v_scale);
667664 vIn += 4 ;
668665 vOut += 4 ;
669666 }
@@ -704,34 +701,37 @@ Tensor& dequantize_per_tensor_out(
704701 const uint8_t * __restrict__ input_data = input.const_data_ptr <uint8_t >();
705702#if defined(__XTENSA__)
706703 // Direct inline PDX SIMD dequantization for per-tensor uint8->float32.
707- // 4x-unrolled to improve ILP: the Fusion G3 scheduler can interleave
708- // independent loads, converts, and FP MACs across unrolled iterations,
709- // hiding load-to-use latency and reducing loop overhead.
704+ // The zero-point subtract stays in the int32 domain (PDX_SUB_MX32) and
705+ // the int->float conversion is folded into PDX_MUL_MXF32 (mixed int32 x
706+ // float32 -> float32), so each element costs one fused mul (sym) or one
707+ // int sub + one fused mul (asym) with no separate convert. The int sub
708+ // issues on the integer unit, overlapping the float mul, so the asym path
709+ // stays store-bound. 4x-unrolled to hide load-to-use latency.
710710 if (dequant_simd_aligned (input_data, out_data)) {
711711 const int numel = inp_shape[0 ];
712712 auto vIn = reinterpret_cast <const xb_vecMxu8*>(input_data);
713713 auto vOut = reinterpret_cast <xb_vecMxf32*>(out_data);
714714 const xb_vecMxf32 v_scale{
715715 scale_data, scale_data, scale_data, scale_data};
716716 int i = 0 ;
717+ // 4x unrolled main loop: 16 elements per iteration
718+ const int e16 = (numel >> 4 ) << 4 ;
717719 if (zero_point_data != 0 ) {
720+ const xb_vecMxu32 v_zp{
721+ static_cast <uint32_t >(zero_point_data),
722+ static_cast <uint32_t >(zero_point_data),
723+ static_cast <uint32_t >(zero_point_data),
724+ static_cast <uint32_t >(zero_point_data)};
718725 const float zp_f = static_cast <float >(zero_point_data);
719- const xb_vecMxf32 v_zp{zp_f, zp_f, zp_f, zp_f};
720- // 4x unrolled main loop: 16 elements per iteration
721- const int e16 = (numel >> 4 ) << 4 ;
722726 for (; i < e16 ; i += 16 ) {
723727 xb_vecMxu32 vP0 = PDX_LVU32_MX8_I (vIn, 0 );
724728 xb_vecMxu32 vP1 = PDX_LVU32_MX8_I (vIn + 1 , 0 );
725729 xb_vecMxu32 vP2 = PDX_LVU32_MX8_I (vIn + 2 , 0 );
726730 xb_vecMxu32 vP3 = PDX_LVU32_MX8_I (vIn + 3 , 0 );
727- xb_vecMxf32 vF0 = (xb_vecMxf32)vP0;
728- xb_vecMxf32 vF1 = (xb_vecMxf32)vP1;
729- xb_vecMxf32 vF2 = (xb_vecMxf32)vP2;
730- xb_vecMxf32 vF3 = (xb_vecMxf32)vP3;
731- vOut[0 ] = PDX_MUL_MXF32 (PDX_SUB_MXF32 (vF0, v_zp), v_scale);
732- vOut[1 ] = PDX_MUL_MXF32 (PDX_SUB_MXF32 (vF1, v_zp), v_scale);
733- vOut[2 ] = PDX_MUL_MXF32 (PDX_SUB_MXF32 (vF2, v_zp), v_scale);
734- vOut[3 ] = PDX_MUL_MXF32 (PDX_SUB_MXF32 (vF3, v_zp), v_scale);
731+ vOut[0 ] = PDX_MUL_MXF32 (PDX_SUB_MX32 (vP0, v_zp), v_scale);
732+ vOut[1 ] = PDX_MUL_MXF32 (PDX_SUB_MX32 (vP1, v_zp), v_scale);
733+ vOut[2 ] = PDX_MUL_MXF32 (PDX_SUB_MX32 (vP2, v_zp), v_scale);
734+ vOut[3 ] = PDX_MUL_MXF32 (PDX_SUB_MX32 (vP3, v_zp), v_scale);
735735 vIn += 4 ;
736736 vOut += 4 ;
737737 }
@@ -741,21 +741,15 @@ Tensor& dequantize_per_tensor_out(
741741 (static_cast <float >(input_data[i]) - zp_f) * scale_data;
742742 }
743743 } else {
744- // 4x unrolled main loop: 16 elements per iteration
745- const int e16 = (numel >> 4 ) << 4 ;
746744 for (; i < e16 ; i += 16 ) {
747745 xb_vecMxu32 vP0 = PDX_LVU32_MX8_I (vIn, 0 );
748746 xb_vecMxu32 vP1 = PDX_LVU32_MX8_I (vIn + 1 , 0 );
749747 xb_vecMxu32 vP2 = PDX_LVU32_MX8_I (vIn + 2 , 0 );
750748 xb_vecMxu32 vP3 = PDX_LVU32_MX8_I (vIn + 3 , 0 );
751- xb_vecMxf32 vF0 = (xb_vecMxf32)vP0;
752- xb_vecMxf32 vF1 = (xb_vecMxf32)vP1;
753- xb_vecMxf32 vF2 = (xb_vecMxf32)vP2;
754- xb_vecMxf32 vF3 = (xb_vecMxf32)vP3;
755- vOut[0 ] = PDX_MUL_MXF32 (vF0, v_scale);
756- vOut[1 ] = PDX_MUL_MXF32 (vF1, v_scale);
757- vOut[2 ] = PDX_MUL_MXF32 (vF2, v_scale);
758- vOut[3 ] = PDX_MUL_MXF32 (vF3, v_scale);
749+ vOut[0 ] = PDX_MUL_MXF32 (vP0, v_scale);
750+ vOut[1 ] = PDX_MUL_MXF32 (vP1, v_scale);
751+ vOut[2 ] = PDX_MUL_MXF32 (vP2, v_scale);
752+ vOut[3 ] = PDX_MUL_MXF32 (vP3, v_scale);
759753 vIn += 4 ;
760754 vOut += 4 ;
761755 }
@@ -796,34 +790,37 @@ Tensor& dequantize_per_tensor_out(
796790 const int16_t * __restrict__ input_data = input.const_data_ptr <int16_t >();
797791#if defined(__XTENSA__)
798792 // Direct inline PDX SIMD dequantization for per-tensor int16->float32.
799- // 4x-unrolled to improve ILP: the Fusion G3 scheduler can interleave
800- // independent loads, converts, and FP MACs across unrolled iterations,
801- // hiding load-to-use latency and reducing loop overhead.
793+ // The zero-point subtract stays in the int32 domain (PDX_SUB_MX32) and
794+ // the int->float conversion is folded into PDX_MUL_MXF32 (mixed int32 x
795+ // float32 -> float32), so each element costs one fused mul (sym) or one
796+ // int sub + one fused mul (asym) with no separate convert. The int sub
797+ // issues on the integer unit, overlapping the float mul, so the asym path
798+ // stays store-bound. 4x-unrolled to hide load-to-use latency.
802799 if (dequant_simd_aligned (input_data, out_data)) {
803800 const int numel = inp_shape[0 ];
804801 auto vIn = reinterpret_cast <const xb_vecMx16*>(input_data);
805802 auto vOut = reinterpret_cast <xb_vecMxf32*>(out_data);
806803 const xb_vecMxf32 v_scale{
807804 scale_data, scale_data, scale_data, scale_data};
808805 int i = 0 ;
806+ // 4x unrolled main loop: 16 elements per iteration
807+ const int e16 = (numel >> 4 ) << 4 ;
809808 if (zero_point_data != 0 ) {
809+ const xb_vecMx32 v_zp{
810+ zero_point_data,
811+ zero_point_data,
812+ zero_point_data,
813+ zero_point_data};
810814 const float zp_f = static_cast <float >(zero_point_data);
811- const xb_vecMxf32 v_zp{zp_f, zp_f, zp_f, zp_f};
812- // 4x unrolled main loop: 16 elements per iteration
813- const int e16 = (numel >> 4 ) << 4 ;
814815 for (; i < e16 ; i += 16 ) {
815816 xb_vecMx32 vP0 = PDX_LV32_MX16_I (vIn, 0 );
816817 xb_vecMx32 vP1 = PDX_LV32_MX16_I (vIn + 1 , 0 );
817818 xb_vecMx32 vP2 = PDX_LV32_MX16_I (vIn + 2 , 0 );
818819 xb_vecMx32 vP3 = PDX_LV32_MX16_I (vIn + 3 , 0 );
819- xb_vecMxf32 vF0 = (xb_vecMxf32)vP0;
820- xb_vecMxf32 vF1 = (xb_vecMxf32)vP1;
821- xb_vecMxf32 vF2 = (xb_vecMxf32)vP2;
822- xb_vecMxf32 vF3 = (xb_vecMxf32)vP3;
823- vOut[0 ] = PDX_MUL_MXF32 (PDX_SUB_MXF32 (vF0, v_zp), v_scale);
824- vOut[1 ] = PDX_MUL_MXF32 (PDX_SUB_MXF32 (vF1, v_zp), v_scale);
825- vOut[2 ] = PDX_MUL_MXF32 (PDX_SUB_MXF32 (vF2, v_zp), v_scale);
826- vOut[3 ] = PDX_MUL_MXF32 (PDX_SUB_MXF32 (vF3, v_zp), v_scale);
820+ vOut[0 ] = PDX_MUL_MXF32 (PDX_SUB_MX32 (vP0, v_zp), v_scale);
821+ vOut[1 ] = PDX_MUL_MXF32 (PDX_SUB_MX32 (vP1, v_zp), v_scale);
822+ vOut[2 ] = PDX_MUL_MXF32 (PDX_SUB_MX32 (vP2, v_zp), v_scale);
823+ vOut[3 ] = PDX_MUL_MXF32 (PDX_SUB_MX32 (vP3, v_zp), v_scale);
827824 vIn += 4 ;
828825 vOut += 4 ;
829826 }
@@ -833,21 +830,15 @@ Tensor& dequantize_per_tensor_out(
833830 (static_cast <float >(input_data[i]) - zp_f) * scale_data;
834831 }
835832 } else {
836- // 4x unrolled main loop: 16 elements per iteration
837- const int e16 = (numel >> 4 ) << 4 ;
838833 for (; i < e16 ; i += 16 ) {
839834 xb_vecMx32 vP0 = PDX_LV32_MX16_I (vIn, 0 );
840835 xb_vecMx32 vP1 = PDX_LV32_MX16_I (vIn + 1 , 0 );
841836 xb_vecMx32 vP2 = PDX_LV32_MX16_I (vIn + 2 , 0 );
842837 xb_vecMx32 vP3 = PDX_LV32_MX16_I (vIn + 3 , 0 );
843- xb_vecMxf32 vF0 = (xb_vecMxf32)vP0;
844- xb_vecMxf32 vF1 = (xb_vecMxf32)vP1;
845- xb_vecMxf32 vF2 = (xb_vecMxf32)vP2;
846- xb_vecMxf32 vF3 = (xb_vecMxf32)vP3;
847- vOut[0 ] = PDX_MUL_MXF32 (vF0, v_scale);
848- vOut[1 ] = PDX_MUL_MXF32 (vF1, v_scale);
849- vOut[2 ] = PDX_MUL_MXF32 (vF2, v_scale);
850- vOut[3 ] = PDX_MUL_MXF32 (vF3, v_scale);
838+ vOut[0 ] = PDX_MUL_MXF32 (vP0, v_scale);
839+ vOut[1 ] = PDX_MUL_MXF32 (vP1, v_scale);
840+ vOut[2 ] = PDX_MUL_MXF32 (vP2, v_scale);
841+ vOut[3 ] = PDX_MUL_MXF32 (vP3, v_scale);
851842 vIn += 4 ;
852843 vOut += 4 ;
853844 }
@@ -889,34 +880,37 @@ Tensor& dequantize_per_tensor_out(
889880 input.const_data_ptr <uint16_t >();
890881#if defined(__XTENSA__)
891882 // Direct inline PDX SIMD dequantization for per-tensor uint16->float32.
892- // 4x-unrolled to improve ILP: the Fusion G3 scheduler can interleave
893- // independent loads, converts, and FP MACs across unrolled iterations,
894- // hiding load-to-use latency and reducing loop overhead.
883+ // The zero-point subtract stays in the int32 domain (PDX_SUB_MX32) and
884+ // the int->float conversion is folded into PDX_MUL_MXF32 (mixed int32 x
885+ // float32 -> float32), so each element costs one fused mul (sym) or one
886+ // int sub + one fused mul (asym) with no separate convert. The int sub
887+ // issues on the integer unit, overlapping the float mul, so the asym path
888+ // stays store-bound. 4x-unrolled to hide load-to-use latency.
895889 if (dequant_simd_aligned (input_data, out_data)) {
896890 const int numel = inp_shape[0 ];
897891 auto vIn = reinterpret_cast <const xb_vecMxu16*>(input_data);
898892 auto vOut = reinterpret_cast <xb_vecMxf32*>(out_data);
899893 const xb_vecMxf32 v_scale{
900894 scale_data, scale_data, scale_data, scale_data};
901895 int i = 0 ;
896+ // 4x unrolled main loop: 16 elements per iteration
897+ const int e16 = (numel >> 4 ) << 4 ;
902898 if (zero_point_data != 0 ) {
899+ const xb_vecMxu32 v_zp{
900+ static_cast <uint32_t >(zero_point_data),
901+ static_cast <uint32_t >(zero_point_data),
902+ static_cast <uint32_t >(zero_point_data),
903+ static_cast <uint32_t >(zero_point_data)};
903904 const float zp_f = static_cast <float >(zero_point_data);
904- const xb_vecMxf32 v_zp{zp_f, zp_f, zp_f, zp_f};
905- // 4x unrolled main loop: 16 elements per iteration
906- const int e16 = (numel >> 4 ) << 4 ;
907905 for (; i < e16 ; i += 16 ) {
908906 xb_vecMxu32 vP0 = PDX_LVU32_MX16_I (vIn, 0 );
909907 xb_vecMxu32 vP1 = PDX_LVU32_MX16_I (vIn + 1 , 0 );
910908 xb_vecMxu32 vP2 = PDX_LVU32_MX16_I (vIn + 2 , 0 );
911909 xb_vecMxu32 vP3 = PDX_LVU32_MX16_I (vIn + 3 , 0 );
912- xb_vecMxf32 vF0 = (xb_vecMxf32)vP0;
913- xb_vecMxf32 vF1 = (xb_vecMxf32)vP1;
914- xb_vecMxf32 vF2 = (xb_vecMxf32)vP2;
915- xb_vecMxf32 vF3 = (xb_vecMxf32)vP3;
916- vOut[0 ] = PDX_MUL_MXF32 (PDX_SUB_MXF32 (vF0, v_zp), v_scale);
917- vOut[1 ] = PDX_MUL_MXF32 (PDX_SUB_MXF32 (vF1, v_zp), v_scale);
918- vOut[2 ] = PDX_MUL_MXF32 (PDX_SUB_MXF32 (vF2, v_zp), v_scale);
919- vOut[3 ] = PDX_MUL_MXF32 (PDX_SUB_MXF32 (vF3, v_zp), v_scale);
910+ vOut[0 ] = PDX_MUL_MXF32 (PDX_SUB_MX32 (vP0, v_zp), v_scale);
911+ vOut[1 ] = PDX_MUL_MXF32 (PDX_SUB_MX32 (vP1, v_zp), v_scale);
912+ vOut[2 ] = PDX_MUL_MXF32 (PDX_SUB_MX32 (vP2, v_zp), v_scale);
913+ vOut[3 ] = PDX_MUL_MXF32 (PDX_SUB_MX32 (vP3, v_zp), v_scale);
920914 vIn += 4 ;
921915 vOut += 4 ;
922916 }
@@ -926,21 +920,15 @@ Tensor& dequantize_per_tensor_out(
926920 (static_cast <float >(input_data[i]) - zp_f) * scale_data;
927921 }
928922 } else {
929- // 4x unrolled main loop: 16 elements per iteration
930- const int e16 = (numel >> 4 ) << 4 ;
931923 for (; i < e16 ; i += 16 ) {
932924 xb_vecMxu32 vP0 = PDX_LVU32_MX16_I (vIn, 0 );
933925 xb_vecMxu32 vP1 = PDX_LVU32_MX16_I (vIn + 1 , 0 );
934926 xb_vecMxu32 vP2 = PDX_LVU32_MX16_I (vIn + 2 , 0 );
935927 xb_vecMxu32 vP3 = PDX_LVU32_MX16_I (vIn + 3 , 0 );
936- xb_vecMxf32 vF0 = (xb_vecMxf32)vP0;
937- xb_vecMxf32 vF1 = (xb_vecMxf32)vP1;
938- xb_vecMxf32 vF2 = (xb_vecMxf32)vP2;
939- xb_vecMxf32 vF3 = (xb_vecMxf32)vP3;
940- vOut[0 ] = PDX_MUL_MXF32 (vF0, v_scale);
941- vOut[1 ] = PDX_MUL_MXF32 (vF1, v_scale);
942- vOut[2 ] = PDX_MUL_MXF32 (vF2, v_scale);
943- vOut[3 ] = PDX_MUL_MXF32 (vF3, v_scale);
928+ vOut[0 ] = PDX_MUL_MXF32 (vP0, v_scale);
929+ vOut[1 ] = PDX_MUL_MXF32 (vP1, v_scale);
930+ vOut[2 ] = PDX_MUL_MXF32 (vP2, v_scale);
931+ vOut[3 ] = PDX_MUL_MXF32 (vP3, v_scale);
944932 vIn += 4 ;
945933 vOut += 4 ;
946934 }
0 commit comments