Skip to main content

tract_linalg/x86_64/
act.rs

1// AVX-512 (zmm, 16-wide) element-wise activation kernels with no FMA
2// predecessor on x86: hardswish and leaky_relu. They mirror the aarch64 NEON
3// kernels (arm64simd_hardswish_f32_8n / arm64simd_leaky_relu_f32_8n) but use
4// 512-bit zmm registers, processing 64 f32 lanes per iteration. Validated
5// against the generic scalar reference via the *_frame_tests! macros.
6
7// hardswish(x) = x * relu6(x + 3) / 6
8//              = x * max(0, min(6, x + 3)) * (1/6)
9routine_ew_rust!(x86_64;
10    f32,
11    x86_64_avx512_hardswish_f32_64n,
12    64,
13    16,
14    #[inline(never)]
15    fn run(buf: &mut [f32], _: ()) {
16        debug_assert!(buf.len() % Self::nr() == 0);
17        debug_assert!(buf.as_ptr() as usize % Self::alignment_bytes() == 0);
18        if buf.is_empty() {
19            return;
20        }
21        unsafe { x86_64_avx512_hardswish_f32_64n_run(buf) }
22    },
23    func(Hardswish),
24    isa(X86_64Avx512f)
25);
26
27#[cfg(target_arch = "x86_64")]
28#[target_feature(enable = "avx512f")]
29unsafe fn x86_64_avx512_hardswish_f32_64n_run(buf: &mut [f32]) {
30    unsafe {
31        let len = buf.len();
32        let ptr = buf.as_ptr();
33        std::arch::asm!("
34            vbroadcastss zmm0, xmm0          // 3.0
35            vbroadcastss zmm1, xmm1          // 6.0
36            vbroadcastss zmm2, xmm2          // 1/6
37            vpxord       zmm3, zmm3, zmm3    // 0.0
38            2:
39                vmovaps zmm4, [{ptr}]
40                vmovaps zmm5, [{ptr} + 64]
41                vmovaps zmm6, [{ptr} + 128]
42                vmovaps zmm7, [{ptr} + 192]
43
44                vaddps  zmm8,  zmm4, zmm0
45                vaddps  zmm9,  zmm5, zmm0
46                vaddps  zmm10, zmm6, zmm0
47                vaddps  zmm11, zmm7, zmm0
48
49                vminps  zmm8,  zmm8,  zmm1
50                vminps  zmm9,  zmm9,  zmm1
51                vminps  zmm10, zmm10, zmm1
52                vminps  zmm11, zmm11, zmm1
53
54                vmaxps  zmm8,  zmm8,  zmm3
55                vmaxps  zmm9,  zmm9,  zmm3
56                vmaxps  zmm10, zmm10, zmm3
57                vmaxps  zmm11, zmm11, zmm3
58
59                vmulps  zmm8,  zmm8,  zmm4
60                vmulps  zmm9,  zmm9,  zmm5
61                vmulps  zmm10, zmm10, zmm6
62                vmulps  zmm11, zmm11, zmm7
63
64                vmulps  zmm8,  zmm8,  zmm2
65                vmulps  zmm9,  zmm9,  zmm2
66                vmulps  zmm10, zmm10, zmm2
67                vmulps  zmm11, zmm11, zmm2
68
69                vmovaps [{ptr}],       zmm8
70                vmovaps [{ptr} + 64],  zmm9
71                vmovaps [{ptr} + 128], zmm10
72                vmovaps [{ptr} + 192], zmm11
73
74                add {ptr}, 256
75                sub {len}, 64
76                jnz 2b
77            ",
78        len = inout(reg) len => _,
79        ptr = inout(reg) ptr => _,
80        inout("xmm0") 3.0f32 => _,
81        inout("xmm1") 6.0f32 => _,
82        inout("xmm2") 1.0f32 / 6.0f32 => _,
83        out("zmm3") _,
84        out("zmm4") _, out("zmm5") _, out("zmm6") _, out("zmm7") _,
85        out("zmm8") _, out("zmm9") _, out("zmm10") _, out("zmm11") _,
86        );
87    }
88}
89
90// leaky_relu(x) = x > 0 ? x : alpha * x
91routine_ew_rust!(x86_64;
92    f32,
93    x86_64_avx512_leaky_relu_f32_64n,
94    64,
95    16,
96    #[inline(never)]
97    fn run(buf: &mut [f32], alpha: f32) {
98        debug_assert!(buf.len() % Self::nr() == 0);
99        debug_assert!(buf.as_ptr() as usize % Self::alignment_bytes() == 0);
100        if buf.is_empty() {
101            return;
102        }
103        unsafe { x86_64_avx512_leaky_relu_f32_64n_run(buf, alpha) }
104    },
105    func(LeakyRelu),
106    param,
107    isa(X86_64Avx512f)
108);
109
110#[cfg(target_arch = "x86_64")]
111#[target_feature(enable = "avx512f")]
112unsafe fn x86_64_avx512_leaky_relu_f32_64n_run(buf: &mut [f32], alpha: f32) {
113    unsafe {
114        let len = buf.len();
115        let ptr = buf.as_ptr();
116        std::arch::asm!("
117            vbroadcastss zmm0, xmm0          // alpha
118            vpxord       zmm1, zmm1, zmm1    // 0.0
119            2:
120                vmovaps zmm4, [{ptr}]
121                vmovaps zmm5, [{ptr} + 64]
122                vmovaps zmm6, [{ptr} + 128]
123                vmovaps zmm7, [{ptr} + 192]
124
125                // alpha * x in zmm8..11
126                vmulps  zmm8,  zmm4, zmm0
127                vmulps  zmm9,  zmm5, zmm0
128                vmulps  zmm10, zmm6, zmm0
129                vmulps  zmm11, zmm7, zmm0
130
131                // mask = x > 0
132                vcmpps  k1, zmm4, zmm1, 14
133                vcmpps  k2, zmm5, zmm1, 14
134                vcmpps  k3, zmm6, zmm1, 14
135                vcmpps  k4, zmm7, zmm1, 14
136
137                // where x > 0, overwrite alpha*x with x
138                vmovaps zmm8{{k1}},  zmm4
139                vmovaps zmm9{{k2}},  zmm5
140                vmovaps zmm10{{k3}}, zmm6
141                vmovaps zmm11{{k4}}, zmm7
142
143                vmovaps [{ptr}],       zmm8
144                vmovaps [{ptr} + 64],  zmm9
145                vmovaps [{ptr} + 128], zmm10
146                vmovaps [{ptr} + 192], zmm11
147
148                add {ptr}, 256
149                sub {len}, 64
150                jnz 2b
151            ",
152        len = inout(reg) len => _,
153        ptr = inout(reg) ptr => _,
154        inout("xmm0") alpha => _,
155        out("zmm1") _,
156        out("zmm4") _, out("zmm5") _, out("zmm6") _, out("zmm7") _,
157        out("zmm8") _, out("zmm9") _, out("zmm10") _, out("zmm11") _,
158        out("k1") _, out("k2") _, out("k3") _, out("k4") _,
159        );
160    }
161}
162
163// Tanh-form GELU (pow=3) matching tract's GeluApproximate:
164//   gelu(x) = 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))
165// Composed at the kernel level (mirrors arm64): save the original x, compute
166// the tanh argument in place, run the AVX-512 tanh kernel, then finish with the
167// 0.5 * x * (1 + tanh) combine.
168routine_ew_rust!(x86_64;
169    f32,
170    x86_64_avx512_gelu_f32_16n,
171    16,
172    16,
173    #[inline(never)]
174    fn run(buf: &mut [f32], _: ()) {
175        debug_assert!(buf.len() % Self::nr() == 0);
176        debug_assert!(buf.as_ptr() as usize % Self::alignment_bytes() == 0);
177        const SQRT_2_OVER_PI: f32 = 0.7978845608028654;
178        const COEF: f32 = 0.044715;
179        const CHUNK: usize = 256;
180        let mut scratch = [0f32; CHUNK];
181        let mut start = 0;
182        while start < buf.len() {
183            let end = (start + CHUNK).min(buf.len());
184            let chunk = &mut buf[start..end];
185            let n = chunk.len();
186            for i in 0..n {
187                let x = chunk[i];
188                scratch[i] = x;
189                chunk[i] = SQRT_2_OVER_PI * (x + COEF * x * x * x);
190            }
191            super::avx512_tanh_f32::run(chunk, ());
192            for i in 0..n {
193                chunk[i] = 0.5 * scratch[i] * (1.0 + chunk[i]);
194            }
195            start = end;
196        }
197    },
198    func(Gelu),
199    isa(X86_64Avx512f)
200);