yum/vfft

git clone https://git.yummers.dev/yum/vfft

yumFix VkFFT baseline on metalc8008fe

master
8.7 KiB295 linesraw
1#define VKFFT_BACKEND 0  // vulkan
2
3#include "fftw3.h"
4#include "vkFFT.h"
5
6#include <stdio.h>
7#include <string.h>
8#include <unistd.h>
9
10#include <chrono>
11#include <vector>
12
13using std::chrono::high_resolution_clock;
14using std::chrono::duration_cast;
15using std::chrono::duration;
16using std::chrono::milliseconds;
17
18#define CHECK(expr) do { \
19  auto err = (expr); \
20  if (err != 0) { \
21    fprintf(stderr, "%s failed: %d\n", #expr, (int) err); \
22    exit(1); \
23  } \
24} while (0)
25
26#define CHECK_TRUE(expr) do { \
27  auto ret = (expr); \
28  if (!ret) { \
29    fprintf(stderr, "%s failed: %d\n", #expr, (int) ret); \
30    exit(1); \
31  } \
32} while (0)
33
34void fftw_test() {
35  printf("=== FFTW test ===\n");
36  int N = 4096;
37  auto *in  = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * N);
38  auto *out = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * N);
39  fftwf_plan p = fftwf_plan_dft_1d(N, in, out, FFTW_FORWARD, FFTW_EXHAUSTIVE);
40
41  const int BATCH_SIZE = 32 * 1024;
42  const int WARMUP_S = 2;
43  const int MEASURE_S = 5;
44  auto t0 = high_resolution_clock::now();
45  while (high_resolution_clock::now() < t0 + std::chrono::seconds(WARMUP_S)) {
46    for (int i = 0; i < BATCH_SIZE; i++) {
47      fftwf_execute(p);
48    }
49  }
50  auto t1 = high_resolution_clock::now();
51  int n_iter = 0;
52  while (high_resolution_clock::now() < t0 + std::chrono::seconds(WARMUP_S + MEASURE_S)) {
53    for (int i = 0; i < BATCH_SIZE; i++) {
54      fftwf_execute(p);
55    }
56    n_iter += BATCH_SIZE;
57  }
58  auto t2 = high_resolution_clock::now();
59
60  auto ns_int = duration_cast<std::chrono::nanoseconds>(t2 - t1);
61  printf("%d ns\n", static_cast<int>(ns_int.count() / n_iter));
62
63  fftwf_destroy_plan(p);
64  fftwf_free(in);
65  fftwf_free(out);
66}
67
68void vkfft_test() {
69  printf("=== vkFFT test ===\n");
70
71  VkApplicationInfo ai = {};
72  ai.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
73  ai.apiVersion = VK_API_VERSION_1_1;  // match vkFFT test suite
74
75  VkInstanceCreateInfo ici = {};
76  ici.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
77  ici.pApplicationInfo = &ai;
78#ifdef __APPLE__
79  const char* ici_ext[] = {
80    VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
81  };
82  ici.enabledExtensionCount = 1;
83  ici.ppEnabledExtensionNames = ici_ext;
84  ici.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
85#endif  // __APPLE__
86
87  VkInstance instance;
88  CHECK(vkCreateInstance(&ici, nullptr, &instance));
89
90  uint32_t n_dev = 0;
91  CHECK(vkEnumeratePhysicalDevices(instance, &n_dev, nullptr));
92  CHECK_TRUE(n_dev > 0);
93  std::vector<VkPhysicalDevice> devices(n_dev);
94  CHECK(vkEnumeratePhysicalDevices(instance, &n_dev, devices.data()));
95  VkPhysicalDevice phys_dev = devices[0];
96
97  uint32_t n_fam = 0;
98  vkGetPhysicalDeviceQueueFamilyProperties(phys_dev, &n_fam, nullptr);
99  std::vector<VkQueueFamilyProperties> families(n_fam);
100  vkGetPhysicalDeviceQueueFamilyProperties(phys_dev, &n_fam, families.data());
101
102  // Find a compute queue.
103  uint32_t family;
104  for (family = 0; family < n_fam; family++) {
105    if (families[family].queueFlags & VK_QUEUE_COMPUTE_BIT) {
106      break;
107    }
108  }
109  CHECK_TRUE(family < n_fam);
110  const uint64_t ts_bits = families[family].timestampValidBits;
111  CHECK_TRUE(ts_bits > 0);
112
113  float priority = 1;
114  VkDeviceQueueCreateInfo qci = {};
115  qci.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
116  qci.queueFamilyIndex = family;
117  qci.queueCount = 1;
118  qci.pQueuePriorities = &priority;
119
120  VkDeviceCreateInfo dci ={};
121  dci.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
122  dci.queueCreateInfoCount = 1;
123  dci.pQueueCreateInfos = &qci;
124
125  VkDevice device;
126  CHECK(vkCreateDevice(phys_dev, &dci, /*VkAllocationCallbacks*/ nullptr, &device));
127
128  VkQueue queue;
129  vkGetDeviceQueue(device, family, 0, &queue);
130
131  // Get GPU timestamp query
132  VkQueryPoolCreateInfo qi = {};
133  qi.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
134  qi.queryType = VK_QUERY_TYPE_TIMESTAMP;
135  qi.queryCount = 2;
136
137  VkQueryPool queries;
138  CHECK(vkCreateQueryPool(device, &qi, nullptr, &queries));
139
140  VkPhysicalDeviceProperties phys_dev_props;
141  vkGetPhysicalDeviceProperties(phys_dev, &phys_dev_props);
142
143  VkCommandPoolCreateInfo pci = {};
144  pci.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
145  pci.queueFamilyIndex = family;
146  pci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
147
148  VkCommandPool pool;
149  CHECK(vkCreateCommandPool(device, &pci, nullptr, &pool));
150
151  VkFenceCreateInfo fci = {};
152  fci.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
153
154  VkFence fence;
155  CHECK(vkCreateFence(device, &fci, nullptr, &fence));
156
157  // Length-4096 complex input. All zeros.
158  const uint64_t FFT_SIZE = 4096;
159  const uint64_t BATCH_SIZE = 1024 * 4;
160  std::vector<float> data(2 * FFT_SIZE * BATCH_SIZE, 0);
161  uint64_t buf_sz = data.size() * sizeof(data[0]);
162
163  VkBufferCreateInfo bci = {};
164  bci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
165  bci.size = buf_sz;
166  bci.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
167  bci.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
168
169  VkBuffer buffer;
170  CHECK(vkCreateBuffer(device, &bci, nullptr, &buffer));
171
172  VkMemoryRequirements mem_req;
173  vkGetBufferMemoryRequirements(device, buffer, &mem_req);
174
175  VkPhysicalDeviceMemoryProperties mem_props;
176  vkGetPhysicalDeviceMemoryProperties(phys_dev, &mem_props);
177
178  const VkMemoryPropertyFlags wanted =
179    VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
180    VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
181    VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
182  uint32_t mem_type;
183  for (mem_type = 0; mem_type < mem_props.memoryTypeCount; mem_type++) {
184    if (!(mem_req.memoryTypeBits & (1u << mem_type))) {
185      continue;
186    }
187    if ((mem_props.memoryTypes[mem_type].propertyFlags & wanted) != wanted) {
188      continue;
189    }
190    break;
191  }
192  CHECK_TRUE(mem_type < mem_props.memoryTypeCount);
193
194  VkMemoryAllocateInfo mai = {};
195  mai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
196  mai.allocationSize = mem_req.size;
197  mai.memoryTypeIndex = mem_type;
198
199  VkDeviceMemory memory;
200  CHECK(vkAllocateMemory(device, &mai, nullptr, &memory));
201  CHECK(vkBindBufferMemory(device, buffer, memory, 0));
202
203  void *mapped;
204  CHECK(vkMapMemory(device, memory, 0, buf_sz, 0, &mapped));
205  memcpy(mapped, data.data(), buf_sz);
206
207  // Finally into vkFFT shit now...
208  VkFFTConfiguration config = {};
209  config.FFTdim = 1;
210  config.size[0] = FFT_SIZE;
211  config.numberBatches = BATCH_SIZE;
212  config.device = &device;
213  config.physicalDevice = &phys_dev;
214  config.queue = &queue;
215  config.commandPool = &pool;
216  config.fence = &fence;
217  config.buffer = &buffer;
218  config.bufferSize = &buf_sz;
219
220  VkFFTApplication app = {};
221  CHECK(initializeVkFFT(&app, config));
222
223  VkCommandBufferAllocateInfo cai = {};
224  cai.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
225  cai.commandPool = pool;
226  cai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
227  cai.commandBufferCount = 1;
228
229  VkCommandBuffer cmd;
230  CHECK(vkAllocateCommandBuffers(device, &cai, &cmd));
231
232  VkCommandBufferBeginInfo begin = {};
233  begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
234  CHECK(vkBeginCommandBuffer(cmd, &begin));
235
236  vkCmdResetQueryPool(cmd, queries, 0, 2);
237  vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, queries, 0);
238
239  VkFFTLaunchParams launch = {};
240  launch.commandBuffer = &cmd;
241  CHECK(VkFFTAppend(&app, -1, &launch));
242
243  vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, queries, 1);
244
245  VkMemoryBarrier barrier = {};
246  barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
247  barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
248  barrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
249  vkCmdPipelineBarrier(
250      cmd,
251      VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
252      VK_PIPELINE_STAGE_HOST_BIT,
253      0, 1, &barrier, 0, nullptr, 0, nullptr);
254
255  CHECK(vkEndCommandBuffer(cmd));
256
257  VkSubmitInfo submit = {};
258  submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
259  submit.commandBufferCount = 1;
260  submit.pCommandBuffers = &cmd;
261  CHECK(vkQueueSubmit(queue, 1, &submit, VK_NULL_HANDLE));
262  CHECK(vkQueueWaitIdle(queue));
263
264  uint64_t timestamps[2] = {};
265  CHECK(vkGetQueryPoolResults(
266        device, queries, 0, 2,
267        sizeof(timestamps), timestamps, sizeof(timestamps[0]),
268        VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT));
269
270  uint64_t mask = ts_bits == 64
271    ? UINT64_MAX : (UINT64_C(1) << ts_bits) - 1;
272  uint64_t ticks = (timestamps[1] - timestamps[0]) & mask;
273  double ns = double(ticks) * phys_dev_props.limits.timestampPeriod;
274
275  memcpy(data.data(), mapped, buf_sz);
276
277  // TODO process result
278  printf("Runtime (GPU): %.0f ns\n", ns / BATCH_SIZE);
279
280  deleteVkFFT(&app);
281  vkUnmapMemory(device, memory);
282  vkDestroyBuffer(device, buffer, nullptr);
283  vkFreeMemory(device, memory, nullptr);
284  vkDestroyFence(device, fence, nullptr);
285  vkDestroyCommandPool(device, pool, nullptr);
286  vkDestroyQueryPool(device, queries, nullptr);
287  vkDestroyDevice(device, nullptr);
288  vkDestroyInstance(instance, nullptr);
289}
290
291int main() {
292  //fftw_test();
293  vkfft_test();
294}
295