1use crate::host::{AccelerationMode, RuntimeProfile};
7
8#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct ExecutionBackend {
11 pub mode: AccelerationMode,
13 pub worker_count: usize,
15 pub preferred_batch_size: usize,
17 pub vector_width_bits: usize,
19}
20
21impl ExecutionBackend {
22 pub fn from_runtime_profile(profile: &RuntimeProfile) -> Self {
24 let (preferred_batch_size, vector_width_bits) = match profile.acceleration_mode {
25 AccelerationMode::Gpu => (4096, 512),
26 AccelerationMode::Avx512 => (2048, 512),
27 AccelerationMode::Avx2 => (1024, 256),
28 AccelerationMode::Neon => (1024, 128),
29 AccelerationMode::Generic => (512, 64),
30 };
31
32 Self {
33 mode: profile.acceleration_mode,
34 worker_count: profile.recommended_workers.max(1),
35 preferred_batch_size,
36 vector_width_bits,
37 }
38 }
39
40 pub const fn name(&self) -> &'static str {
42 self.mode.as_str()
43 }
44
45 pub fn scale_f32_slice(&self, input: &[f32], scale: f32) -> Vec<f32> {
47 if input.is_empty() {
48 return Vec::new();
49 }
50
51 let mut output = vec![0.0f32; input.len()];
52 match self.mode {
53 AccelerationMode::Gpu => {
54 parallel_scale_scalar(input, &mut output, scale, self.worker_count)
55 }
56 AccelerationMode::Avx512 => scale_x86_512(input, &mut output, scale),
57 AccelerationMode::Avx2 => scale_x86_256(input, &mut output, scale),
58 AccelerationMode::Neon => scale_neon(input, &mut output, scale),
59 AccelerationMode::Generic => scale_scalar(input, &mut output, scale),
60 }
61
62 output
63 }
64}
65
66fn scale_scalar(input: &[f32], output: &mut [f32], scale: f32) {
67 for (dst, src) in output.iter_mut().zip(input.iter()) {
68 *dst = *src * scale;
69 }
70}
71
72fn parallel_scale_scalar(input: &[f32], output: &mut [f32], scale: f32, worker_count: usize) {
73 let worker_count = worker_count.max(1);
74 const MIN_PARALLEL_LEN: usize = 65_536;
77 if worker_count <= 1 || input.len() < MIN_PARALLEL_LEN {
78 scale_scalar(input, output, scale);
79 return;
80 }
81
82 let chunk_size = input.len().div_ceil(worker_count).max(1);
83 std::thread::scope(|scope| {
84 for (in_chunk, out_chunk) in input.chunks(chunk_size).zip(output.chunks_mut(chunk_size)) {
85 scope.spawn(move || scale_scalar(in_chunk, out_chunk, scale));
86 }
87 });
88}
89
90#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
91fn scale_x86_256(input: &[f32], output: &mut [f32], scale: f32) {
92 if std::is_x86_feature_detected!("avx2") {
93 unsafe { scale_x86_256_impl(input, output, scale) }
94 } else {
95 scale_scalar(input, output, scale)
96 }
97}
98
99#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
100fn scale_x86_256(input: &[f32], output: &mut [f32], scale: f32) {
101 scale_scalar(input, output, scale)
102}
103
104#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
105fn scale_x86_512(input: &[f32], output: &mut [f32], scale: f32) {
106 scale_x86_256(input, output, scale)
108}
109
110#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
111fn scale_x86_512(input: &[f32], output: &mut [f32], scale: f32) {
112 scale_scalar(input, output, scale)
113}
114
115#[cfg(target_arch = "aarch64")]
116fn scale_neon(input: &[f32], output: &mut [f32], scale: f32) {
117 unsafe { scale_neon_impl(input, output, scale) }
118}
119
120#[cfg(not(target_arch = "aarch64"))]
121fn scale_neon(input: &[f32], output: &mut [f32], scale: f32) {
122 scale_scalar(input, output, scale)
123}
124
125#[cfg(target_arch = "x86_64")]
126#[target_feature(enable = "avx2")]
127unsafe fn scale_x86_256_impl(input: &[f32], output: &mut [f32], scale: f32) {
128 use std::arch::x86_64::{_mm256_loadu_ps, _mm256_mul_ps, _mm256_set1_ps, _mm256_storeu_ps};
129
130 let scale_vec = _mm256_set1_ps(scale);
131 let mut index = 0usize;
132 while index + 32 <= input.len() {
136 let input_vec0 = _mm256_loadu_ps(input.as_ptr().add(index));
137 let input_vec1 = _mm256_loadu_ps(input.as_ptr().add(index + 8));
138 let input_vec2 = _mm256_loadu_ps(input.as_ptr().add(index + 16));
139 let input_vec3 = _mm256_loadu_ps(input.as_ptr().add(index + 24));
140
141 let output_vec0 = _mm256_mul_ps(input_vec0, scale_vec);
142 let output_vec1 = _mm256_mul_ps(input_vec1, scale_vec);
143 let output_vec2 = _mm256_mul_ps(input_vec2, scale_vec);
144 let output_vec3 = _mm256_mul_ps(input_vec3, scale_vec);
145
146 _mm256_storeu_ps(output.as_mut_ptr().add(index), output_vec0);
147 _mm256_storeu_ps(output.as_mut_ptr().add(index + 8), output_vec1);
148 _mm256_storeu_ps(output.as_mut_ptr().add(index + 16), output_vec2);
149 _mm256_storeu_ps(output.as_mut_ptr().add(index + 24), output_vec3);
150
151 index += 32;
152 }
153 while index + 8 <= input.len() {
155 let input_vec = _mm256_loadu_ps(input.as_ptr().add(index));
156 let output_vec = _mm256_mul_ps(input_vec, scale_vec);
157 _mm256_storeu_ps(output.as_mut_ptr().add(index), output_vec);
158 index += 8;
159 }
160 scale_scalar(&input[index..], &mut output[index..], scale);
161}
162
163#[cfg(target_arch = "x86")]
164#[target_feature(enable = "avx2")]
165unsafe fn scale_x86_256_impl(input: &[f32], output: &mut [f32], scale: f32) {
166 use std::arch::x86::{_mm256_loadu_ps, _mm256_mul_ps, _mm256_set1_ps, _mm256_storeu_ps};
167
168 let scale_vec = _mm256_set1_ps(scale);
169 let mut index = 0usize;
170 while index + 32 <= input.len() {
174 let input_vec0 = _mm256_loadu_ps(input.as_ptr().add(index));
175 let input_vec1 = _mm256_loadu_ps(input.as_ptr().add(index + 8));
176 let input_vec2 = _mm256_loadu_ps(input.as_ptr().add(index + 16));
177 let input_vec3 = _mm256_loadu_ps(input.as_ptr().add(index + 24));
178
179 let output_vec0 = _mm256_mul_ps(input_vec0, scale_vec);
180 let output_vec1 = _mm256_mul_ps(input_vec1, scale_vec);
181 let output_vec2 = _mm256_mul_ps(input_vec2, scale_vec);
182 let output_vec3 = _mm256_mul_ps(input_vec3, scale_vec);
183
184 _mm256_storeu_ps(output.as_mut_ptr().add(index), output_vec0);
185 _mm256_storeu_ps(output.as_mut_ptr().add(index + 8), output_vec1);
186 _mm256_storeu_ps(output.as_mut_ptr().add(index + 16), output_vec2);
187 _mm256_storeu_ps(output.as_mut_ptr().add(index + 24), output_vec3);
188
189 index += 32;
190 }
191 while index + 8 <= input.len() {
193 let input_vec = _mm256_loadu_ps(input.as_ptr().add(index));
194 let output_vec = _mm256_mul_ps(input_vec, scale_vec);
195 _mm256_storeu_ps(output.as_mut_ptr().add(index), output_vec);
196 index += 8;
197 }
198 scale_scalar(&input[index..], &mut output[index..], scale);
199}
200
201#[cfg(target_arch = "aarch64")]
202unsafe fn scale_neon_impl(input: &[f32], output: &mut [f32], scale: f32) {
203 use std::arch::aarch64::{vdupq_n_f32, vld1q_f32, vmulq_f32, vst1q_f32};
204
205 let scale_vec = vdupq_n_f32(scale);
206 let mut index = 0usize;
207 while index + 16 <= input.len() {
211 let input_vec0 = vld1q_f32(input.as_ptr().add(index));
212 let input_vec1 = vld1q_f32(input.as_ptr().add(index + 4));
213 let input_vec2 = vld1q_f32(input.as_ptr().add(index + 8));
214 let input_vec3 = vld1q_f32(input.as_ptr().add(index + 12));
215
216 let output_vec0 = vmulq_f32(input_vec0, scale_vec);
217 let output_vec1 = vmulq_f32(input_vec1, scale_vec);
218 let output_vec2 = vmulq_f32(input_vec2, scale_vec);
219 let output_vec3 = vmulq_f32(input_vec3, scale_vec);
220
221 vst1q_f32(output.as_mut_ptr().add(index), output_vec0);
222 vst1q_f32(output.as_mut_ptr().add(index + 4), output_vec1);
223 vst1q_f32(output.as_mut_ptr().add(index + 8), output_vec2);
224 vst1q_f32(output.as_mut_ptr().add(index + 12), output_vec3);
225
226 index += 16;
227 }
228 while index + 4 <= input.len() {
230 let input_vec = vld1q_f32(input.as_ptr().add(index));
231 let output_vec = vmulq_f32(input_vec, scale_vec);
232 vst1q_f32(output.as_mut_ptr().add(index), output_vec);
233 index += 4;
234 }
235 scale_scalar(&input[index..], &mut output[index..], scale);
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use crate::protocol::NodeResources;
242
243 #[test]
244 fn backend_selection_reflects_acceleration_mode() {
245 let profile = RuntimeProfile {
246 node_resources: NodeResources::new("node-a", 24.0, 64.0, "8.9", None),
247 logical_cores: 16,
248 recommended_workers: 8,
249 acceleration_mode: AccelerationMode::Gpu,
250 gpu_backend: crate::host::GpuBackend::Cuda,
251 xdp_supported: true,
252 detection_source: String::from("test"),
253 probe_mode: crate::host::ProbeMode::Fast,
254 };
255
256 let backend = ExecutionBackend::from_runtime_profile(&profile);
257 assert_eq!(backend.name(), "GPU");
258 assert_eq!(backend.vector_width_bits, 512);
259 }
260
261 #[test]
262 fn backend_executes_scaled_transform() {
263 let profile = RuntimeProfile {
264 node_resources: NodeResources::new("node-a", 0.0, 64.0, "cpu", None),
265 logical_cores: 8,
266 recommended_workers: 4,
267 acceleration_mode: AccelerationMode::Avx2,
268 gpu_backend: crate::host::GpuBackend::Cpu,
269 xdp_supported: true,
270 detection_source: String::from("test"),
271 probe_mode: crate::host::ProbeMode::Fast,
272 };
273
274 let backend = ExecutionBackend::from_runtime_profile(&profile);
275 let output = backend.scale_f32_slice(&[1.0, 2.0, 3.5], 2.0);
276 assert_eq!(output, vec![2.0, 4.0, 7.0]);
277 }
278}