-
-
Save Hermann-SW/9162a47fd59216aea77df90efc8e7ad2 to your computer and use it in GitHub Desktop.
| /* | |
| f=AVX512_VNNI.DP2A_s32_s16_s16 | |
| g++ -O3 -fopenmp -Wall -Wextra -pedantic $f.cpp -o $f | |
| cpplint --filter=-legal/copyright $f.cpp | |
| cppcheck --enable=all --suppress=missingIncludeSystem $f.cpp --check-config | |
| echo off | sudo tee /sys/devices/system/cpu/smt/control | |
| echo 0 | sudo tee /proc/sys/kernel/perf_event_paranoid | |
| perf stat -a -e fp_ops_retired_by_width.pack_512_uops_retired,cycles,instructions,task-clock ./$f | |
| Outputs for "./f": | |
| Performance: 2.23851 TOPS (Tera-Ops/sec) on AMD 8840HS 8C laptop CPU (at 4.400 GHz) | |
| Performance: 5.12171 TOPS (Tera-Ops/sec) on AMD 7950X 16C CPU (at 5.013 GHz) | |
| Outputs for "./f byte": | |
| Performance: 4.17551 TOPS (Tera-Ops/sec) on AMD 8840HS 8C laptop CPU (at 4.116 GHz) | |
| Performance: 10.2413 TOPS (Tera-Ops/sec) on AMD 7950X 16C CPU (at 5.013 GHz) | |
| */ | |
| #include <omp.h> | |
| #include <inttypes.h> | |
| #include <iostream> | |
| #include <chrono> // NOLINT [build/c++11] | |
| int main(int argc, char**) { | |
| const int iterations = 2000000000; // 2*10^9 | |
| const bool byt = (argc > 1); | |
| std::cout << "Starting hardware-bound benchmark using " | |
| << omp_get_max_threads() << " threads...\n"; | |
| auto start_time = std::chrono::high_resolution_clock::now(); | |
| #pragma omp parallel | |
| { | |
| if (byt) { | |
| for (int i = 0; i < iterations; ++i) { | |
| asm __volatile__ ("vpdpbusd %%zmm0, %%zmm0, %%zmm0" : : : "zmm0"); | |
| asm __volatile__ ("vpdpbusd %%zmm1, %%zmm1, %%zmm1" : : : "zmm1"); | |
| asm __volatile__ ("vpdpbusd %%zmm2, %%zmm2, %%zmm2" : : : "zmm2"); | |
| asm __volatile__ ("vpdpbusd %%zmm3, %%zmm3, %%zmm3" : : : "zmm3"); | |
| asm __volatile__ ("vpdpbusd %%zmm4, %%zmm4, %%zmm4" : : : "zmm4"); | |
| } | |
| } else { | |
| for (int i = 0; i < iterations; ++i) { | |
| asm __volatile__ ("vpdpwssd %%zmm0, %%zmm0, %%zmm0" : : : "zmm0"); | |
| asm __volatile__ ("vpdpwssd %%zmm1, %%zmm1, %%zmm1" : : : "zmm1"); | |
| asm __volatile__ ("vpdpwssd %%zmm2, %%zmm2, %%zmm2" : : : "zmm2"); | |
| asm __volatile__ ("vpdpwssd %%zmm3, %%zmm3, %%zmm3" : : : "zmm3"); | |
| asm __volatile__ ("vpdpwssd %%zmm4, %%zmm4, %%zmm4" : : : "zmm4"); | |
| } | |
| } | |
| } | |
| std::cout << "... [AVX512_VNNI] DP2A(s32,s16,s16) completed\n"; | |
| std::cout << | |
| "https://www.officedaytime.com/simd512e/simdimg/vpdpwssd_3.png?v=22\n"; | |
| std::chrono::duration<double> duration = | |
| std::chrono::high_resolution_clock::now() - start_time; | |
| int64_t mult_s16_per_vpdpwssd = 32 * (byt ? 2 : 1); | |
| int64_t add_s32_per_vpdpwssd = (16 + 16) * (byt ? 2 : 1); | |
| int64_t ops_per_vpdpwssd = mult_s16_per_vpdpwssd + add_s32_per_vpdpwssd; | |
| int64_t ops_per_loop = 5 * ops_per_vpdpwssd * omp_get_max_threads(); | |
| int64_t total_ops = ops_per_loop * iterations; | |
| int64_t giga_cnt = total_ops / ops_per_vpdpwssd; | |
| double tera_ops = total_ops / 1e12; | |
| double performance = tera_ops / duration.count(); | |
| std::cout << "-------------------------------------------\n"; | |
| std::cout << "Execution Time: " << duration.count() << " seconds\n"; | |
| std::cout.imbue(std::locale("")); | |
| std::cout << "Counter: " << giga_cnt << "\n"; | |
| std::cout << "Total Compute: " << tera_ops | |
| << " Tera-Operations (counter * (32+16+16) * " | |
| << (byt ? 2 : 1) << ")\n"; | |
| std::cout << "Performance: " << performance << " TOPS (Tera-Ops/sec)\n"; | |
| std::cout << "-------------------------------------------\n"; | |
| return 0; | |
| } |
Verifying reported 5.12171 TOPS with fp_ops_retired_by_width.pack_512_uops_retired hardware counter.
The vast majority of perf counter fp_ops_retired_by_width.pack_512_uops_retired comes from Counter output:
160000000000 * 64 / (1.99933 * 10^12) = 5.12171577478455282519
hermann@7950x:~$ perf stat -a -e fp_ops_retired_by_width.pack_512_uops_retired,cycles,instructions,task-clock ./$f
Starting hardware-bound benchmark using 16 threads...
... [AVX512_VNNI] DP2A(s32,s16,s16) completed
https://www.officedaytime.com/simd512e/simdimg/vpdpwssd_3.png?v=22
-------------------------------------------
Execution Time: 1.99933 seconds
Counter: 160,000,000,000
Total Compute: 10.24 Tera-Operations (counter * (32+16+16))
Performance: 5.12171 TOPS (Tera-Ops/sec)
-------------------------------------------
Performance counter stats for 'system wide':
160,000,038,102 fp_ops_retired_by_width.pack_512_uops_retired # 4.998 G/sec
160,489,568,548 cycles # 5.013 GHz
224,242,131,827 instructions # 1.40 insn per cycle
32,013.03 msec task-clock # 15.999 CPUs utilized
2.000884035 seconds time elapsed
hermann@7950x:~$
P.S:
perf changed with Zen5, so running with "-a" instead. While AMD 9950X has real AVX512 registers and does not do double pumping as the AMD 7950X, (16bit) TOPS don't double over AMD 7950X:
hermann@9950X:~$ perf stat -a ./$f
Starting hardware-bound benchmark using 16 threads...
... [AVX512_VNNI] DP2A(s32,s16,s16) completed
https://www.officedaytime.com/simd512e/simdimg/vpdpwssd_3.png?v=22
-------------------------------------------
Execution Time: 1.5701 seconds
Counter: 160,000,000,000
Total Compute: 10.24 Tera-Operations (counter * (32+16+16) * 1)
Performance: 6.52188 TOPS (Tera-Ops/sec)
-------------------------------------------
Performance counter stats for 'system wide':
494 context-switches # 19.6 cs/sec cs_per_second
25,140.71 msec cpu-clock # 16.0 CPUs CPUs_utilized
46 cpu-migrations # 1.8 migrations/sec migrations_per_second
193 page-faults # 7.7 faults/sec page_faults_per_second
401,596 branch-misses # 0.0 % branch_miss_rate (49.98%)
32,056,743,834 branches # 1275.1 M/sec branch_frequency (49.98%)
130,630,587,435 cpu-cycles # 5.2 GHz cycles_frequency (66.65%)
224,304,257,222 instructions # 1.7 instructions insn_per_cycle (50.02%)
125,393,838 stalled-cycles-frontend # 0.00 frontend_cycles_idle (50.02%)
1.571122229 seconds time elapsed
hermann@9950X:~$
Main loop in C++ with inline assembly (allowing control over the 512bit register names):
hermann@8840hs:~$ sed -n "/pragma/,/^ }/p" $f.cpp
#pragma omp parallel
{
for (int i = 0; i < iterations; ++i) {
asm __volatile__ ("vpdpwssd %%zmm0, %%zmm0, %%zmm0" : : : "zmm0");
asm __volatile__ ("vpdpwssd %%zmm1, %%zmm1, %%zmm1" : : : "zmm1");
asm __volatile__ ("vpdpwssd %%zmm2, %%zmm2, %%zmm2" : : : "zmm2");
asm __volatile__ ("vpdpwssd %%zmm3, %%zmm3, %%zmm3" : : : "zmm3");
asm __volatile__ ("vpdpwssd %%zmm4, %%zmm4, %%zmm4" : : : "zmm4");
}
}
hermann@8840hs:~$ Same loop with objdump:
hermann@8840hs:~$ objdump -d AVX512_VNNI.DP2A_s32_s16_s16 | sed -n "/vpdpwssd/,/jne/p"
1580: 62 f2 7d 48 52 c0 vpdpwssd %zmm0,%zmm0,%zmm0
1586: 62 f2 75 48 52 c9 vpdpwssd %zmm1,%zmm1,%zmm1
158c: 62 f2 6d 48 52 d2 vpdpwssd %zmm2,%zmm2,%zmm2
1592: 62 f2 65 48 52 db vpdpwssd %zmm3,%zmm3,%zmm3
1598: 62 f2 5d 48 52 e4 vpdpwssd %zmm4,%zmm4,%zmm4
159e: 83 e8 01 sub $0x1,%eax
15a1: 75 dd jne 1580 <main._omp_fn.0+0x10>
hermann@8840hs:~$
With new "byte" option TOPS numbers double over AMD 7950X (multiplying 64 byte pairs vs. 32 word pairs):
hermann@7950x:~$ perf stat -a -e fp_ops_retired_by_width.pack_512_uops_retired,cycles,instructions,task-clock ./$f byte
Starting hardware-bound benchmark using 16 threads...
... [AVX512_VNNI] DP2A(s32,s16,s16) completed
https://www.officedaytime.com/simd512e/simdimg/vpdpwssd_3.png?v=22
-------------------------------------------
Execution Time: 1.99975 seconds
Counter: 160,000,000,000
Total Compute: 20.48 Tera-Operations (counter * (32+16+16) * 2)
Performance: 10.2413 TOPS (Tera-Ops/sec)
-------------------------------------------
Performance counter stats for 'system wide':
160,000,038,892 fp_ops_retired_by_width.pack_512_uops_retired # 4.997 G/sec
160,518,614,310 cycles # 5.013 GHz
224,246,479,090 instructions # 1.40 insn per cycle
32,018.54 msec task-clock # 16.000 CPUs utilized
2.001118241 seconds time elapsed
hermann@7950x:~$
P.S:
perf changed with Zen5, so running with "-a" instead. While AMD 9950X has real AVX512 registers and does not do double pumping as the AMD 7950X, (8bit) TOPS don't double:
hermann@9950X:~$ perf stat -a ./$f byte
Starting hardware-bound benchmark using 16 threads...
... [AVX512_VNNI] DP2A(s32,s16,s16) completed
https://www.officedaytime.com/simd512e/simdimg/vpdpwssd_3.png?v=22
-------------------------------------------
Execution Time: 1.57218 seconds
Counter: 160,000,000,000
Total Compute: 20.48 Tera-Operations (counter * (32+16+16) * 2)
Performance: 13.0265 TOPS (Tera-Ops/sec)
-------------------------------------------
Performance counter stats for 'system wide':
535 context-switches # 21.3 cs/sec cs_per_second
25,174.53 msec cpu-clock # 16.0 CPUs CPUs_utilized
50 cpu-migrations # 2.0 migrations/sec migrations_per_second
193 page-faults # 7.7 faults/sec page_faults_per_second
347,926 branch-misses # 0.0 % branch_miss_rate (49.98%)
31,998,869,170 branches # 1271.1 M/sec branch_frequency (50.03%)
130,566,580,372 cpu-cycles # 5.2 GHz cycles_frequency (66.70%)
224,392,469,904 instructions # 1.7 instructions insn_per_cycle (50.02%)
124,088,901 stalled-cycles-frontend # 0.00 frontend_cycles_idle (49.97%)
1.573245719 seconds time elapsed
hermann@9950X:~$
Verifying reported 2.23851 TOPS with fp_ops_retired_by_width.pack_512_uops_retired hardware counter.
One vpdpwssd does 32 s16×s16 multiplications and 16+16 s32 additions:
https://www.officedaytime.com/simd512e/simdimg/vpdpwssd_3.png?v=22