gpuarray 0.2.0__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gpuarray/__init__.py +8 -0
- gpuarray/api.py +35 -1
- gpuarray/core.py +468 -14
- gpuarray/shaders.py +507 -19
- {gpuarray-0.2.0.dist-info → gpuarray-0.4.0.dist-info}/METADATA +1 -1
- gpuarray-0.4.0.dist-info/RECORD +9 -0
- gpuarray-0.2.0.dist-info/RECORD +0 -9
- {gpuarray-0.2.0.dist-info → gpuarray-0.4.0.dist-info}/WHEEL +0 -0
- {gpuarray-0.2.0.dist-info → gpuarray-0.4.0.dist-info}/top_level.txt +0 -0
gpuarray/__init__.py
CHANGED
|
@@ -27,6 +27,10 @@ from .api import (
|
|
|
27
27
|
concatenate,
|
|
28
28
|
stack,
|
|
29
29
|
random,
|
|
30
|
+
fft,
|
|
31
|
+
ifft,
|
|
32
|
+
fft_freq,
|
|
33
|
+
fft_magnitude,
|
|
30
34
|
)
|
|
31
35
|
|
|
32
36
|
__version__ = "0.1.0"
|
|
@@ -58,4 +62,8 @@ __all__ = [
|
|
|
58
62
|
"concatenate",
|
|
59
63
|
"stack",
|
|
60
64
|
"random",
|
|
65
|
+
"fft",
|
|
66
|
+
"ifft",
|
|
67
|
+
"fft_freq",
|
|
68
|
+
"fft_magnitude",
|
|
61
69
|
]
|
gpuarray/api.py
CHANGED
|
@@ -6,7 +6,7 @@ from typing import Sequence
|
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
|
|
9
|
-
from .core import GPUArray, _from_numpy, matmul as _matmul
|
|
9
|
+
from .core import GPUArray, _from_numpy, matmul as _matmul, _complex_magnitude
|
|
10
10
|
from . import shaders
|
|
11
11
|
|
|
12
12
|
|
|
@@ -180,3 +180,37 @@ class _Random:
|
|
|
180
180
|
|
|
181
181
|
|
|
182
182
|
random = _Random()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# ----- FFT functions -----
|
|
186
|
+
|
|
187
|
+
def fft(a) -> GPUArray:
|
|
188
|
+
"""1D FFT. Input is real or complex (interleaved). Returns complex GPUArray."""
|
|
189
|
+
if not isinstance(a, GPUArray):
|
|
190
|
+
a = array(a)
|
|
191
|
+
return a.fft()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def ifft(a) -> GPUArray:
|
|
195
|
+
"""1D inverse FFT. Input is complex interleaved. Returns complex GPUArray."""
|
|
196
|
+
if not isinstance(a, GPUArray):
|
|
197
|
+
a = array(a)
|
|
198
|
+
return a.ifft()
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def fft_freq(n: int, d: float = 1.0) -> GPUArray:
|
|
202
|
+
"""Return the DFT sample frequencies (like numpy.fft.fftfreq)."""
|
|
203
|
+
results = np.empty(n, dtype=np.float32)
|
|
204
|
+
N = (n - 1) // 2 + 1
|
|
205
|
+
results[:N] = np.arange(0, N, dtype=np.float32)
|
|
206
|
+
results[N:] = np.arange(-(n // 2), 0, dtype=np.float32)
|
|
207
|
+
results /= (n * d)
|
|
208
|
+
return _from_numpy(results)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def fft_magnitude(a) -> GPUArray:
|
|
212
|
+
"""Compute magnitude spectrum |FFT(x)|. Returns real GPUArray."""
|
|
213
|
+
if not isinstance(a, GPUArray):
|
|
214
|
+
a = array(a)
|
|
215
|
+
fft_result = a.fft()
|
|
216
|
+
return _complex_magnitude(fft_result)
|
gpuarray/core.py
CHANGED
|
@@ -13,6 +13,7 @@ from .device import get_device
|
|
|
13
13
|
from . import shaders
|
|
14
14
|
|
|
15
15
|
WORKGROUP_SIZE = 256
|
|
16
|
+
REDUCTION_ELEMENTS_PER_WORKGROUP = 1024 # 256 threads * 4 elements each
|
|
16
17
|
|
|
17
18
|
# Global pipeline cache: shader_code -> (shader_module, compute_pipeline)
|
|
18
19
|
_pipeline_cache: dict[str, tuple] = {}
|
|
@@ -174,7 +175,10 @@ class GPUArray:
|
|
|
174
175
|
return GPUArray(out_buf, self._shape)
|
|
175
176
|
|
|
176
177
|
def _run_reduction(self, shader_code: str) -> float:
|
|
177
|
-
"""Multi-pass parallel reduction until 1 element remains.
|
|
178
|
+
"""Multi-pass parallel reduction until 1 element remains.
|
|
179
|
+
|
|
180
|
+
Each workgroup of 256 threads handles 1024 elements (4 per thread).
|
|
181
|
+
"""
|
|
178
182
|
device = get_device()
|
|
179
183
|
_, pipeline = _get_pipeline(shader_code)
|
|
180
184
|
|
|
@@ -182,16 +186,16 @@ class GPUArray:
|
|
|
182
186
|
current_size = self.size
|
|
183
187
|
|
|
184
188
|
while current_size > 1:
|
|
185
|
-
n_workgroups = math.ceil(current_size /
|
|
189
|
+
n_workgroups = math.ceil(current_size / REDUCTION_ELEMENTS_PER_WORKGROUP)
|
|
186
190
|
out_buf = device.create_buffer(
|
|
187
|
-
size=n_workgroups * 4,
|
|
191
|
+
size=max(n_workgroups * 4, 4),
|
|
188
192
|
usage=wgpu.BufferUsage.STORAGE | wgpu.BufferUsage.COPY_SRC | wgpu.BufferUsage.COPY_DST,
|
|
189
193
|
)
|
|
190
194
|
bind_group = device.create_bind_group(
|
|
191
195
|
layout=pipeline.get_bind_group_layout(0),
|
|
192
196
|
entries=[
|
|
193
197
|
{"binding": 0, "resource": {"buffer": current_buf, "offset": 0, "size": current_size * 4}},
|
|
194
|
-
{"binding": 1, "resource": {"buffer": out_buf, "offset": 0, "size": n_workgroups * 4}},
|
|
198
|
+
{"binding": 1, "resource": {"buffer": out_buf, "offset": 0, "size": max(n_workgroups * 4, 4)}},
|
|
195
199
|
],
|
|
196
200
|
)
|
|
197
201
|
encoder = device.create_command_encoder()
|
|
@@ -214,6 +218,29 @@ class GPUArray:
|
|
|
214
218
|
current_buf.destroy()
|
|
215
219
|
return result
|
|
216
220
|
|
|
221
|
+
def _run_binary_op_inplace(self, other: "GPUArray", shader_code: str) -> "GPUArray":
|
|
222
|
+
"""Run a binary op that writes the result into self's buffer (no allocation)."""
|
|
223
|
+
assert self.size == other.size, f"Size mismatch: {self.size} vs {other.size}"
|
|
224
|
+
device = get_device()
|
|
225
|
+
_, pipeline = _get_pipeline(shader_code)
|
|
226
|
+
|
|
227
|
+
bind_group = device.create_bind_group(
|
|
228
|
+
layout=pipeline.get_bind_group_layout(0),
|
|
229
|
+
entries=[
|
|
230
|
+
{"binding": 0, "resource": {"buffer": self._buffer, "offset": 0, "size": self.size * 4}},
|
|
231
|
+
{"binding": 1, "resource": {"buffer": other._buffer, "offset": 0, "size": other.size * 4}},
|
|
232
|
+
],
|
|
233
|
+
)
|
|
234
|
+
n_workgroups = math.ceil(self.size / WORKGROUP_SIZE)
|
|
235
|
+
encoder = device.create_command_encoder()
|
|
236
|
+
pass_enc = encoder.begin_compute_pass()
|
|
237
|
+
pass_enc.set_pipeline(pipeline)
|
|
238
|
+
pass_enc.set_bind_group(0, bind_group)
|
|
239
|
+
pass_enc.dispatch_workgroups(n_workgroups)
|
|
240
|
+
pass_enc.end()
|
|
241
|
+
device.queue.submit([encoder.finish()])
|
|
242
|
+
return self
|
|
243
|
+
|
|
217
244
|
# ----- dot product (two-input reduction) -----
|
|
218
245
|
|
|
219
246
|
def _run_dot_reduction(self, other: "GPUArray") -> float:
|
|
@@ -259,11 +286,71 @@ class GPUArray:
|
|
|
259
286
|
current_buf.destroy()
|
|
260
287
|
return result
|
|
261
288
|
|
|
289
|
+
# ----- broadcast helper -----
|
|
290
|
+
|
|
291
|
+
def _run_broadcast_op(self, other: "GPUArray", shader_code: str, out_shape: tuple[int, ...]) -> "GPUArray":
|
|
292
|
+
"""Run a broadcast binary shader with a uniform buffer for column count."""
|
|
293
|
+
device = get_device()
|
|
294
|
+
_, pipeline = _get_pipeline(shader_code)
|
|
295
|
+
out_size = 1
|
|
296
|
+
for d in out_shape:
|
|
297
|
+
out_size *= d
|
|
298
|
+
out_buf = device.create_buffer(
|
|
299
|
+
size=out_size * 4,
|
|
300
|
+
usage=wgpu.BufferUsage.STORAGE | wgpu.BufferUsage.COPY_SRC | wgpu.BufferUsage.COPY_DST,
|
|
301
|
+
)
|
|
302
|
+
cols = out_shape[-1] if len(out_shape) >= 2 else out_shape[0]
|
|
303
|
+
dims_data = struct.pack("IIII", cols, 0, 0, 0)
|
|
304
|
+
dims_buf = device.create_buffer_with_data(
|
|
305
|
+
data=dims_data, usage=wgpu.BufferUsage.UNIFORM,
|
|
306
|
+
)
|
|
307
|
+
bind_group = device.create_bind_group(
|
|
308
|
+
layout=pipeline.get_bind_group_layout(0),
|
|
309
|
+
entries=[
|
|
310
|
+
{"binding": 0, "resource": {"buffer": self._buffer, "offset": 0, "size": self.size * 4}},
|
|
311
|
+
{"binding": 1, "resource": {"buffer": other._buffer, "offset": 0, "size": other.size * 4}},
|
|
312
|
+
{"binding": 2, "resource": {"buffer": out_buf, "offset": 0, "size": out_size * 4}},
|
|
313
|
+
{"binding": 3, "resource": {"buffer": dims_buf, "offset": 0, "size": 16}},
|
|
314
|
+
],
|
|
315
|
+
)
|
|
316
|
+
n_workgroups = math.ceil(out_size / WORKGROUP_SIZE)
|
|
317
|
+
encoder = device.create_command_encoder()
|
|
318
|
+
pass_enc = encoder.begin_compute_pass()
|
|
319
|
+
pass_enc.set_pipeline(pipeline)
|
|
320
|
+
pass_enc.set_bind_group(0, bind_group)
|
|
321
|
+
pass_enc.dispatch_workgroups(n_workgroups)
|
|
322
|
+
pass_enc.end()
|
|
323
|
+
device.queue.submit([encoder.finish()])
|
|
324
|
+
dims_buf.destroy()
|
|
325
|
+
return GPUArray(out_buf, out_shape)
|
|
326
|
+
|
|
327
|
+
def _binary_with_broadcast(self, other: "GPUArray",
|
|
328
|
+
same_shader: str, row_shader: str, col_shader: str) -> "GPUArray":
|
|
329
|
+
"""Dispatch to the right shader based on shape compatibility."""
|
|
330
|
+
if self.shape == other.shape or self.size == other.size:
|
|
331
|
+
return self._run_binary_op(other, same_shader)
|
|
332
|
+
# (M, N) op (N,) or (M, N) op (1, N) — broadcast row
|
|
333
|
+
if self.ndim == 2:
|
|
334
|
+
M, N = self.shape
|
|
335
|
+
if (other.ndim == 1 and other.shape[0] == N) or \
|
|
336
|
+
(other.ndim == 2 and other.shape == (1, N)):
|
|
337
|
+
b = other.flatten() if other.ndim == 2 else other
|
|
338
|
+
return self._run_broadcast_op(b, row_shader, self.shape)
|
|
339
|
+
# (M, N) op (M, 1) — broadcast col
|
|
340
|
+
if other.ndim == 2 and other.shape == (M, 1):
|
|
341
|
+
b_flat = other.flatten()
|
|
342
|
+
return self._run_broadcast_op(b_flat, col_shader, self.shape)
|
|
343
|
+
raise ValueError(f"Incompatible shapes for broadcast: {self.shape} vs {other.shape}")
|
|
344
|
+
|
|
262
345
|
# ----- operator overloads -----
|
|
263
346
|
|
|
264
347
|
def __add__(self, other):
|
|
265
348
|
if isinstance(other, (int, float)):
|
|
266
349
|
return self._run_scalar_op(float(other), shaders.ADD_SCALAR_SHADER)
|
|
350
|
+
if isinstance(other, GPUArray) and self.shape != other.shape:
|
|
351
|
+
return self._binary_with_broadcast(
|
|
352
|
+
other, shaders.ADD_SHADER,
|
|
353
|
+
shaders.BINARY_BROADCAST_ROW_ADD, shaders.BINARY_BROADCAST_COL_ADD)
|
|
267
354
|
return self._run_binary_op(other, shaders.ADD_SHADER)
|
|
268
355
|
|
|
269
356
|
def __radd__(self, other):
|
|
@@ -274,6 +361,10 @@ class GPUArray:
|
|
|
274
361
|
def __sub__(self, other):
|
|
275
362
|
if isinstance(other, (int, float)):
|
|
276
363
|
return self._run_scalar_op(float(-other), shaders.ADD_SCALAR_SHADER)
|
|
364
|
+
if isinstance(other, GPUArray) and self.shape != other.shape:
|
|
365
|
+
return self._binary_with_broadcast(
|
|
366
|
+
other, shaders.SUB_SHADER,
|
|
367
|
+
shaders.BINARY_BROADCAST_ROW_SUB, shaders.BINARY_BROADCAST_COL_SUB)
|
|
277
368
|
return self._run_binary_op(other, shaders.SUB_SHADER)
|
|
278
369
|
|
|
279
370
|
def __rsub__(self, other):
|
|
@@ -285,6 +376,10 @@ class GPUArray:
|
|
|
285
376
|
def __mul__(self, other):
|
|
286
377
|
if isinstance(other, (int, float)):
|
|
287
378
|
return self._run_scalar_op(float(other), shaders.MUL_SCALAR_SHADER)
|
|
379
|
+
if isinstance(other, GPUArray) and self.shape != other.shape:
|
|
380
|
+
return self._binary_with_broadcast(
|
|
381
|
+
other, shaders.MUL_SHADER,
|
|
382
|
+
shaders.BINARY_BROADCAST_ROW_MUL, shaders.BINARY_BROADCAST_COL_MUL)
|
|
288
383
|
return self._run_binary_op(other, shaders.MUL_SHADER)
|
|
289
384
|
|
|
290
385
|
def __rmul__(self, other):
|
|
@@ -295,8 +390,32 @@ class GPUArray:
|
|
|
295
390
|
def __truediv__(self, other):
|
|
296
391
|
if isinstance(other, (int, float)):
|
|
297
392
|
return self._run_scalar_op(1.0 / float(other), shaders.MUL_SCALAR_SHADER)
|
|
393
|
+
if isinstance(other, GPUArray) and self.shape != other.shape:
|
|
394
|
+
return self._binary_with_broadcast(
|
|
395
|
+
other, shaders.DIV_SHADER,
|
|
396
|
+
shaders.BINARY_BROADCAST_ROW_DIV, shaders.BINARY_BROADCAST_COL_DIV)
|
|
298
397
|
return self._run_binary_op(other, shaders.DIV_SHADER)
|
|
299
398
|
|
|
399
|
+
def __iadd__(self, other):
|
|
400
|
+
if isinstance(other, (int, float)):
|
|
401
|
+
return self._run_scalar_op(float(other), shaders.ADD_SCALAR_SHADER)
|
|
402
|
+
return self._run_binary_op_inplace(other, shaders.ADD_INPLACE_SHADER)
|
|
403
|
+
|
|
404
|
+
def __isub__(self, other):
|
|
405
|
+
if isinstance(other, (int, float)):
|
|
406
|
+
return self._run_scalar_op(float(-other), shaders.ADD_SCALAR_SHADER)
|
|
407
|
+
return self._run_binary_op_inplace(other, shaders.SUB_INPLACE_SHADER)
|
|
408
|
+
|
|
409
|
+
def __imul__(self, other):
|
|
410
|
+
if isinstance(other, (int, float)):
|
|
411
|
+
return self._run_scalar_op(float(other), shaders.MUL_SCALAR_SHADER)
|
|
412
|
+
return self._run_binary_op_inplace(other, shaders.MUL_INPLACE_SHADER)
|
|
413
|
+
|
|
414
|
+
def __itruediv__(self, other):
|
|
415
|
+
if isinstance(other, (int, float)):
|
|
416
|
+
return self._run_scalar_op(1.0 / float(other), shaders.MUL_SCALAR_SHADER)
|
|
417
|
+
return self._run_binary_op_inplace(other, shaders.DIV_INPLACE_SHADER)
|
|
418
|
+
|
|
300
419
|
def __neg__(self):
|
|
301
420
|
return self._run_unary_op(shaders.NEG_SHADER)
|
|
302
421
|
|
|
@@ -305,12 +424,108 @@ class GPUArray:
|
|
|
305
424
|
return f"GPUArray({data}, shape={self._shape})"
|
|
306
425
|
|
|
307
426
|
def __getitem__(self, key):
|
|
427
|
+
if isinstance(key, GPUArray):
|
|
428
|
+
return self._getitem_mask(key)
|
|
308
429
|
data = self.to_numpy()
|
|
309
430
|
result = data[key]
|
|
310
431
|
if isinstance(result, np.ndarray):
|
|
311
|
-
return _from_numpy(result)
|
|
432
|
+
return _from_numpy(result.copy())
|
|
312
433
|
return float(result)
|
|
313
434
|
|
|
435
|
+
def _getitem_mask(self, mask: "GPUArray") -> "GPUArray":
|
|
436
|
+
"""Boolean mask indexing via gather shader."""
|
|
437
|
+
mask_np = mask.to_numpy().flatten()
|
|
438
|
+
indices = np.where(mask_np > 0.5)[0].astype(np.uint32)
|
|
439
|
+
if len(indices) == 0:
|
|
440
|
+
return _from_numpy(np.array([], dtype=np.float32))
|
|
441
|
+
device = get_device()
|
|
442
|
+
_, pipeline = _get_pipeline(shaders.GATHER_SHADER)
|
|
443
|
+
idx_buf = device.create_buffer_with_data(
|
|
444
|
+
data=indices.tobytes(),
|
|
445
|
+
usage=wgpu.BufferUsage.STORAGE | wgpu.BufferUsage.COPY_SRC,
|
|
446
|
+
)
|
|
447
|
+
out_buf = device.create_buffer(
|
|
448
|
+
size=len(indices) * 4,
|
|
449
|
+
usage=wgpu.BufferUsage.STORAGE | wgpu.BufferUsage.COPY_SRC | wgpu.BufferUsage.COPY_DST,
|
|
450
|
+
)
|
|
451
|
+
bind_group = device.create_bind_group(
|
|
452
|
+
layout=pipeline.get_bind_group_layout(0),
|
|
453
|
+
entries=[
|
|
454
|
+
{"binding": 0, "resource": {"buffer": self._buffer, "offset": 0, "size": self.size * 4}},
|
|
455
|
+
{"binding": 1, "resource": {"buffer": idx_buf, "offset": 0, "size": len(indices) * 4}},
|
|
456
|
+
{"binding": 2, "resource": {"buffer": out_buf, "offset": 0, "size": len(indices) * 4}},
|
|
457
|
+
],
|
|
458
|
+
)
|
|
459
|
+
n_workgroups = math.ceil(len(indices) / WORKGROUP_SIZE)
|
|
460
|
+
encoder = device.create_command_encoder()
|
|
461
|
+
pass_enc = encoder.begin_compute_pass()
|
|
462
|
+
pass_enc.set_pipeline(pipeline)
|
|
463
|
+
pass_enc.set_bind_group(0, bind_group)
|
|
464
|
+
pass_enc.dispatch_workgroups(n_workgroups)
|
|
465
|
+
pass_enc.end()
|
|
466
|
+
device.queue.submit([encoder.finish()])
|
|
467
|
+
idx_buf.destroy()
|
|
468
|
+
return GPUArray(out_buf, (len(indices),))
|
|
469
|
+
|
|
470
|
+
def __setitem__(self, key, value):
|
|
471
|
+
if isinstance(key, GPUArray):
|
|
472
|
+
self._setitem_mask(key, value)
|
|
473
|
+
return
|
|
474
|
+
device = get_device()
|
|
475
|
+
if isinstance(key, (int, np.integer)):
|
|
476
|
+
idx = int(key)
|
|
477
|
+
if idx < 0:
|
|
478
|
+
idx += self.size
|
|
479
|
+
device.queue.write_buffer(self._buffer, idx * 4, struct.pack("f", float(value)))
|
|
480
|
+
elif isinstance(key, slice):
|
|
481
|
+
if self.ndim == 1:
|
|
482
|
+
indices = range(*key.indices(self._shape[0]))
|
|
483
|
+
if isinstance(value, (int, float)):
|
|
484
|
+
data = struct.pack("f", float(value)) * len(indices)
|
|
485
|
+
elif isinstance(value, GPUArray):
|
|
486
|
+
data = value.to_numpy().astype(np.float32).tobytes()
|
|
487
|
+
else:
|
|
488
|
+
data = np.asarray(value, dtype=np.float32).tobytes()
|
|
489
|
+
start = indices.start if len(indices) > 0 else 0
|
|
490
|
+
device.queue.write_buffer(self._buffer, start * 4, data)
|
|
491
|
+
else:
|
|
492
|
+
indices = range(*key.indices(self._shape[0]))
|
|
493
|
+
cols = self._shape[1]
|
|
494
|
+
if isinstance(value, (int, float)):
|
|
495
|
+
row_data = struct.pack("f", float(value)) * cols
|
|
496
|
+
for r in indices:
|
|
497
|
+
device.queue.write_buffer(self._buffer, r * cols * 4, row_data)
|
|
498
|
+
else:
|
|
499
|
+
val_np = value.to_numpy() if isinstance(value, GPUArray) else np.asarray(value, dtype=np.float32)
|
|
500
|
+
for i, r in enumerate(indices):
|
|
501
|
+
row_bytes = (val_np[i] if val_np.ndim >= 2 else val_np).astype(np.float32).tobytes()
|
|
502
|
+
device.queue.write_buffer(self._buffer, r * cols * 4, row_bytes)
|
|
503
|
+
elif isinstance(key, tuple) and len(key) == 2:
|
|
504
|
+
row, col = key
|
|
505
|
+
if isinstance(row, (int, np.integer)) and isinstance(col, (int, np.integer)):
|
|
506
|
+
cols = self._shape[1]
|
|
507
|
+
offset = (int(row) * cols + int(col)) * 4
|
|
508
|
+
device.queue.write_buffer(self._buffer, offset, struct.pack("f", float(value)))
|
|
509
|
+
else:
|
|
510
|
+
arr = self.to_numpy()
|
|
511
|
+
arr[key] = value if not isinstance(value, GPUArray) else value.to_numpy()
|
|
512
|
+
device.queue.write_buffer(self._buffer, 0, np.ascontiguousarray(arr, dtype=np.float32).tobytes())
|
|
513
|
+
else:
|
|
514
|
+
arr = self.to_numpy()
|
|
515
|
+
arr[key] = value if not isinstance(value, GPUArray) else value.to_numpy()
|
|
516
|
+
device.queue.write_buffer(self._buffer, 0, np.ascontiguousarray(arr, dtype=np.float32).tobytes())
|
|
517
|
+
|
|
518
|
+
def _setitem_mask(self, mask: "GPUArray", value):
|
|
519
|
+
"""Masked assignment: a[mask] = value."""
|
|
520
|
+
data = self.to_numpy()
|
|
521
|
+
mask_np = mask.to_numpy() > 0.5
|
|
522
|
+
if isinstance(value, GPUArray):
|
|
523
|
+
data[mask_np] = value.to_numpy().flatten()[:mask_np.sum()]
|
|
524
|
+
else:
|
|
525
|
+
data[mask_np] = float(value)
|
|
526
|
+
device = get_device()
|
|
527
|
+
device.queue.write_buffer(self._buffer, 0, np.ascontiguousarray(data, dtype=np.float32).tobytes())
|
|
528
|
+
|
|
314
529
|
# ----- reduction methods -----
|
|
315
530
|
|
|
316
531
|
def sum(self) -> float:
|
|
@@ -443,6 +658,224 @@ class GPUArray:
|
|
|
443
658
|
return self._shape[0]
|
|
444
659
|
|
|
445
660
|
|
|
661
|
+
# ----- FFT methods -----
|
|
662
|
+
|
|
663
|
+
def fft(self) -> "GPUArray":
|
|
664
|
+
"""1D FFT. Input is real or complex (interleaved). Returns complex GPUArray (interleaved)."""
|
|
665
|
+
return _fft_impl(self, inverse=False)
|
|
666
|
+
|
|
667
|
+
def ifft(self) -> "GPUArray":
|
|
668
|
+
"""1D inverse FFT. Input is complex interleaved. Returns complex GPUArray."""
|
|
669
|
+
return _fft_impl(self, inverse=True)
|
|
670
|
+
|
|
671
|
+
def fft_magnitude(self) -> "GPUArray":
|
|
672
|
+
"""Compute magnitude spectrum |FFT(x)|. Returns real GPUArray."""
|
|
673
|
+
fft_result = self.fft()
|
|
674
|
+
return _complex_magnitude(fft_result)
|
|
675
|
+
|
|
676
|
+
def fft_phase(self) -> "GPUArray":
|
|
677
|
+
"""Compute phase spectrum angle(FFT(x)). Returns real GPUArray."""
|
|
678
|
+
fft_result = self.fft()
|
|
679
|
+
return _complex_phase(fft_result)
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
def _next_power_of_2(n: int) -> int:
|
|
683
|
+
"""Return the smallest power of 2 >= n."""
|
|
684
|
+
if n <= 1:
|
|
685
|
+
return 1
|
|
686
|
+
p = 1
|
|
687
|
+
while p < n:
|
|
688
|
+
p <<= 1
|
|
689
|
+
return p
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
def _fft_impl(arr: GPUArray, inverse: bool = False) -> GPUArray:
|
|
693
|
+
"""Cooley-Tukey radix-2 FFT/IFFT on GPU."""
|
|
694
|
+
device = get_device()
|
|
695
|
+
|
|
696
|
+
# Determine if input is real or complex (interleaved)
|
|
697
|
+
# Real: 1D array of N floats -> N-point FFT
|
|
698
|
+
# Complex interleaved: 1D array of 2*N floats -> N-point FFT
|
|
699
|
+
# We treat it as real if ndim==1 and size is the number of points
|
|
700
|
+
# For simplicity: if the array was produced by a previous FFT (even size),
|
|
701
|
+
# the caller should know. We'll check: if doing IFFT, input must be complex.
|
|
702
|
+
|
|
703
|
+
flat = arr.to_numpy().flatten()
|
|
704
|
+
|
|
705
|
+
if inverse:
|
|
706
|
+
# Input must be complex interleaved (even number of floats)
|
|
707
|
+
assert len(flat) % 2 == 0, "IFFT input must be complex interleaved (even length)"
|
|
708
|
+
N_complex = len(flat) // 2
|
|
709
|
+
else:
|
|
710
|
+
# Assume real input -> convert to complex
|
|
711
|
+
N_complex = len(flat)
|
|
712
|
+
|
|
713
|
+
# Pad to next power of 2
|
|
714
|
+
N = _next_power_of_2(N_complex)
|
|
715
|
+
log2N = int(math.log2(N))
|
|
716
|
+
|
|
717
|
+
if inverse:
|
|
718
|
+
# Pad complex data
|
|
719
|
+
if N > N_complex:
|
|
720
|
+
padded = np.zeros(2 * N, dtype=np.float32)
|
|
721
|
+
padded[:len(flat)] = flat
|
|
722
|
+
else:
|
|
723
|
+
padded = flat.copy()
|
|
724
|
+
complex_data = padded
|
|
725
|
+
else:
|
|
726
|
+
# Convert real to complex interleaved
|
|
727
|
+
padded_real = np.zeros(N, dtype=np.float32)
|
|
728
|
+
padded_real[:len(flat)] = flat
|
|
729
|
+
complex_data = np.zeros(2 * N, dtype=np.float32)
|
|
730
|
+
complex_data[0::2] = padded_real # real parts
|
|
731
|
+
# imaginary parts are already 0
|
|
732
|
+
|
|
733
|
+
# Upload complex data to GPU
|
|
734
|
+
input_buf = device.create_buffer_with_data(
|
|
735
|
+
data=complex_data.tobytes(),
|
|
736
|
+
usage=wgpu.BufferUsage.STORAGE | wgpu.BufferUsage.COPY_SRC | wgpu.BufferUsage.COPY_DST,
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
# Step 1: Bit-reversal permutation
|
|
740
|
+
output_buf = device.create_buffer(
|
|
741
|
+
size=2 * N * 4,
|
|
742
|
+
usage=wgpu.BufferUsage.STORAGE | wgpu.BufferUsage.COPY_SRC | wgpu.BufferUsage.COPY_DST,
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
_, br_pipeline = _get_pipeline(shaders.FFT_BIT_REVERSE_SHADER)
|
|
746
|
+
params_data = struct.pack("IIII", N, log2N, 0, 0)
|
|
747
|
+
params_buf = device.create_buffer_with_data(
|
|
748
|
+
data=params_data, usage=wgpu.BufferUsage.UNIFORM,
|
|
749
|
+
)
|
|
750
|
+
bind_group = device.create_bind_group(
|
|
751
|
+
layout=br_pipeline.get_bind_group_layout(0),
|
|
752
|
+
entries=[
|
|
753
|
+
{"binding": 0, "resource": {"buffer": input_buf, "offset": 0, "size": 2 * N * 4}},
|
|
754
|
+
{"binding": 1, "resource": {"buffer": output_buf, "offset": 0, "size": 2 * N * 4}},
|
|
755
|
+
{"binding": 2, "resource": {"buffer": params_buf, "offset": 0, "size": 16}},
|
|
756
|
+
],
|
|
757
|
+
)
|
|
758
|
+
n_workgroups = math.ceil(N / WORKGROUP_SIZE)
|
|
759
|
+
encoder = device.create_command_encoder()
|
|
760
|
+
pass_enc = encoder.begin_compute_pass()
|
|
761
|
+
pass_enc.set_pipeline(br_pipeline)
|
|
762
|
+
pass_enc.set_bind_group(0, bind_group)
|
|
763
|
+
pass_enc.dispatch_workgroups(n_workgroups)
|
|
764
|
+
pass_enc.end()
|
|
765
|
+
device.queue.submit([encoder.finish()])
|
|
766
|
+
input_buf.destroy()
|
|
767
|
+
params_buf.destroy()
|
|
768
|
+
|
|
769
|
+
# Step 2: Butterfly stages
|
|
770
|
+
butterfly_shader = shaders.IFFT_BUTTERFLY_SHADER if inverse else shaders.FFT_BUTTERFLY_SHADER
|
|
771
|
+
_, bf_pipeline = _get_pipeline(butterfly_shader)
|
|
772
|
+
|
|
773
|
+
for stage in range(log2N):
|
|
774
|
+
params_data = struct.pack("IIII", N, stage, 0, 0)
|
|
775
|
+
params_buf = device.create_buffer_with_data(
|
|
776
|
+
data=params_data, usage=wgpu.BufferUsage.UNIFORM,
|
|
777
|
+
)
|
|
778
|
+
bind_group = device.create_bind_group(
|
|
779
|
+
layout=bf_pipeline.get_bind_group_layout(0),
|
|
780
|
+
entries=[
|
|
781
|
+
{"binding": 0, "resource": {"buffer": output_buf, "offset": 0, "size": 2 * N * 4}},
|
|
782
|
+
{"binding": 1, "resource": {"buffer": params_buf, "offset": 0, "size": 16}},
|
|
783
|
+
],
|
|
784
|
+
)
|
|
785
|
+
n_workgroups_bf = math.ceil(N // 2 / WORKGROUP_SIZE)
|
|
786
|
+
if n_workgroups_bf == 0:
|
|
787
|
+
n_workgroups_bf = 1
|
|
788
|
+
encoder = device.create_command_encoder()
|
|
789
|
+
pass_enc = encoder.begin_compute_pass()
|
|
790
|
+
pass_enc.set_pipeline(bf_pipeline)
|
|
791
|
+
pass_enc.set_bind_group(0, bind_group)
|
|
792
|
+
pass_enc.dispatch_workgroups(n_workgroups_bf)
|
|
793
|
+
pass_enc.end()
|
|
794
|
+
device.queue.submit([encoder.finish()])
|
|
795
|
+
params_buf.destroy()
|
|
796
|
+
|
|
797
|
+
# Step 3: For IFFT, scale by 1/N
|
|
798
|
+
if inverse:
|
|
799
|
+
_, scale_pipeline = _get_pipeline(shaders.FFT_SCALE_SHADER)
|
|
800
|
+
scale_val = 1.0 / float(N)
|
|
801
|
+
params_data = struct.pack("f", scale_val) + b"\x00" * 12
|
|
802
|
+
params_buf = device.create_buffer_with_data(
|
|
803
|
+
data=params_data, usage=wgpu.BufferUsage.UNIFORM,
|
|
804
|
+
)
|
|
805
|
+
bind_group = device.create_bind_group(
|
|
806
|
+
layout=scale_pipeline.get_bind_group_layout(0),
|
|
807
|
+
entries=[
|
|
808
|
+
{"binding": 0, "resource": {"buffer": output_buf, "offset": 0, "size": 2 * N * 4}},
|
|
809
|
+
{"binding": 1, "resource": {"buffer": params_buf, "offset": 0, "size": 16}},
|
|
810
|
+
],
|
|
811
|
+
)
|
|
812
|
+
n_workgroups_sc = math.ceil(2 * N / WORKGROUP_SIZE)
|
|
813
|
+
encoder = device.create_command_encoder()
|
|
814
|
+
pass_enc = encoder.begin_compute_pass()
|
|
815
|
+
pass_enc.set_pipeline(scale_pipeline)
|
|
816
|
+
pass_enc.set_bind_group(0, bind_group)
|
|
817
|
+
pass_enc.dispatch_workgroups(n_workgroups_sc)
|
|
818
|
+
pass_enc.end()
|
|
819
|
+
device.queue.submit([encoder.finish()])
|
|
820
|
+
params_buf.destroy()
|
|
821
|
+
|
|
822
|
+
return GPUArray(output_buf, (2 * N,))
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
def _complex_magnitude(arr: GPUArray) -> GPUArray:
|
|
826
|
+
"""Compute magnitude of complex interleaved array."""
|
|
827
|
+
device = get_device()
|
|
828
|
+
N = arr.size // 2
|
|
829
|
+
_, pipeline = _get_pipeline(shaders.COMPLEX_MAGNITUDE_SHADER)
|
|
830
|
+
out_buf = device.create_buffer(
|
|
831
|
+
size=N * 4,
|
|
832
|
+
usage=wgpu.BufferUsage.STORAGE | wgpu.BufferUsage.COPY_SRC | wgpu.BufferUsage.COPY_DST,
|
|
833
|
+
)
|
|
834
|
+
bind_group = device.create_bind_group(
|
|
835
|
+
layout=pipeline.get_bind_group_layout(0),
|
|
836
|
+
entries=[
|
|
837
|
+
{"binding": 0, "resource": {"buffer": arr._buffer, "offset": 0, "size": arr.size * 4}},
|
|
838
|
+
{"binding": 1, "resource": {"buffer": out_buf, "offset": 0, "size": N * 4}},
|
|
839
|
+
],
|
|
840
|
+
)
|
|
841
|
+
n_workgroups = math.ceil(N / WORKGROUP_SIZE)
|
|
842
|
+
encoder = device.create_command_encoder()
|
|
843
|
+
pass_enc = encoder.begin_compute_pass()
|
|
844
|
+
pass_enc.set_pipeline(pipeline)
|
|
845
|
+
pass_enc.set_bind_group(0, bind_group)
|
|
846
|
+
pass_enc.dispatch_workgroups(n_workgroups)
|
|
847
|
+
pass_enc.end()
|
|
848
|
+
device.queue.submit([encoder.finish()])
|
|
849
|
+
return GPUArray(out_buf, (N,))
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
def _complex_phase(arr: GPUArray) -> GPUArray:
|
|
853
|
+
"""Compute phase of complex interleaved array."""
|
|
854
|
+
device = get_device()
|
|
855
|
+
N = arr.size // 2
|
|
856
|
+
_, pipeline = _get_pipeline(shaders.COMPLEX_PHASE_SHADER)
|
|
857
|
+
out_buf = device.create_buffer(
|
|
858
|
+
size=N * 4,
|
|
859
|
+
usage=wgpu.BufferUsage.STORAGE | wgpu.BufferUsage.COPY_SRC | wgpu.BufferUsage.COPY_DST,
|
|
860
|
+
)
|
|
861
|
+
bind_group = device.create_bind_group(
|
|
862
|
+
layout=pipeline.get_bind_group_layout(0),
|
|
863
|
+
entries=[
|
|
864
|
+
{"binding": 0, "resource": {"buffer": arr._buffer, "offset": 0, "size": arr.size * 4}},
|
|
865
|
+
{"binding": 1, "resource": {"buffer": out_buf, "offset": 0, "size": N * 4}},
|
|
866
|
+
],
|
|
867
|
+
)
|
|
868
|
+
n_workgroups = math.ceil(N / WORKGROUP_SIZE)
|
|
869
|
+
encoder = device.create_command_encoder()
|
|
870
|
+
pass_enc = encoder.begin_compute_pass()
|
|
871
|
+
pass_enc.set_pipeline(pipeline)
|
|
872
|
+
pass_enc.set_bind_group(0, bind_group)
|
|
873
|
+
pass_enc.dispatch_workgroups(n_workgroups)
|
|
874
|
+
pass_enc.end()
|
|
875
|
+
device.queue.submit([encoder.finish()])
|
|
876
|
+
return GPUArray(out_buf, (N,))
|
|
877
|
+
|
|
878
|
+
|
|
446
879
|
def _from_numpy(data: np.ndarray) -> GPUArray:
|
|
447
880
|
"""Create a GPUArray from a numpy array."""
|
|
448
881
|
device = get_device()
|
|
@@ -455,7 +888,10 @@ def _from_numpy(data: np.ndarray) -> GPUArray:
|
|
|
455
888
|
|
|
456
889
|
|
|
457
890
|
def matmul(a: GPUArray, b: GPUArray) -> GPUArray:
|
|
458
|
-
"""Matrix multiplication C = A @ B.
|
|
891
|
+
"""Matrix multiplication C = A @ B.
|
|
892
|
+
|
|
893
|
+
Uses tiled shared-memory shader for matrices >= 64x64, naive for smaller.
|
|
894
|
+
"""
|
|
459
895
|
if a.ndim != 2 or b.ndim != 2:
|
|
460
896
|
raise ValueError("matmul requires 2D arrays")
|
|
461
897
|
M, K = a.shape
|
|
@@ -464,7 +900,11 @@ def matmul(a: GPUArray, b: GPUArray) -> GPUArray:
|
|
|
464
900
|
raise ValueError(f"Incompatible shapes for matmul: {a.shape} and {b.shape}")
|
|
465
901
|
|
|
466
902
|
device = get_device()
|
|
467
|
-
|
|
903
|
+
|
|
904
|
+
# Choose tiled or naive shader based on matrix size
|
|
905
|
+
use_tiled = M >= 64 and N >= 64 and K >= 64
|
|
906
|
+
shader_code = shaders.MATMUL_TILED if use_tiled else shaders.MATMUL_NAIVE
|
|
907
|
+
_, pipeline = _get_pipeline(shader_code)
|
|
468
908
|
|
|
469
909
|
out_size = M * N
|
|
470
910
|
out_buf = device.create_buffer(
|
|
@@ -489,13 +929,27 @@ def matmul(a: GPUArray, b: GPUArray) -> GPUArray:
|
|
|
489
929
|
],
|
|
490
930
|
)
|
|
491
931
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
932
|
+
if use_tiled:
|
|
933
|
+
# Tiled shader uses @workgroup_size(16, 16); dispatch 2D grid
|
|
934
|
+
TILE_SIZE = 16
|
|
935
|
+
wg_x = math.ceil(N / TILE_SIZE)
|
|
936
|
+
wg_y = math.ceil(M / TILE_SIZE)
|
|
937
|
+
encoder = device.create_command_encoder()
|
|
938
|
+
pass_enc = encoder.begin_compute_pass()
|
|
939
|
+
pass_enc.set_pipeline(pipeline)
|
|
940
|
+
pass_enc.set_bind_group(0, bind_group)
|
|
941
|
+
pass_enc.dispatch_workgroups(wg_x, wg_y)
|
|
942
|
+
pass_enc.end()
|
|
943
|
+
else:
|
|
944
|
+
# Naive shader uses @workgroup_size(256); dispatch 1D
|
|
945
|
+
n_workgroups = math.ceil(out_size / WORKGROUP_SIZE)
|
|
946
|
+
encoder = device.create_command_encoder()
|
|
947
|
+
pass_enc = encoder.begin_compute_pass()
|
|
948
|
+
pass_enc.set_pipeline(pipeline)
|
|
949
|
+
pass_enc.set_bind_group(0, bind_group)
|
|
950
|
+
pass_enc.dispatch_workgroups(n_workgroups)
|
|
951
|
+
pass_enc.end()
|
|
952
|
+
|
|
499
953
|
device.queue.submit([encoder.finish()])
|
|
500
954
|
dims_buf.destroy()
|
|
501
955
|
return GPUArray(out_buf, (M, N))
|
gpuarray/shaders.py
CHANGED
|
@@ -74,6 +74,62 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
|
74
74
|
}
|
|
75
75
|
"""
|
|
76
76
|
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
# In-place elementwise binary operations (result written to a)
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
ADD_INPLACE_SHADER = """
|
|
82
|
+
@group(0) @binding(0) var<storage, read_write> a: array<f32>;
|
|
83
|
+
@group(0) @binding(1) var<storage, read> b: array<f32>;
|
|
84
|
+
|
|
85
|
+
@compute @workgroup_size(256)
|
|
86
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
87
|
+
let i = gid.x;
|
|
88
|
+
if (i < arrayLength(&a)) {
|
|
89
|
+
a[i] = a[i] + b[i];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
SUB_INPLACE_SHADER = """
|
|
95
|
+
@group(0) @binding(0) var<storage, read_write> a: array<f32>;
|
|
96
|
+
@group(0) @binding(1) var<storage, read> b: array<f32>;
|
|
97
|
+
|
|
98
|
+
@compute @workgroup_size(256)
|
|
99
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
100
|
+
let i = gid.x;
|
|
101
|
+
if (i < arrayLength(&a)) {
|
|
102
|
+
a[i] = a[i] - b[i];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
MUL_INPLACE_SHADER = """
|
|
108
|
+
@group(0) @binding(0) var<storage, read_write> a: array<f32>;
|
|
109
|
+
@group(0) @binding(1) var<storage, read> b: array<f32>;
|
|
110
|
+
|
|
111
|
+
@compute @workgroup_size(256)
|
|
112
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
113
|
+
let i = gid.x;
|
|
114
|
+
if (i < arrayLength(&a)) {
|
|
115
|
+
a[i] = a[i] * b[i];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
DIV_INPLACE_SHADER = """
|
|
121
|
+
@group(0) @binding(0) var<storage, read_write> a: array<f32>;
|
|
122
|
+
@group(0) @binding(1) var<storage, read> b: array<f32>;
|
|
123
|
+
|
|
124
|
+
@compute @workgroup_size(256)
|
|
125
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
126
|
+
let i = gid.x;
|
|
127
|
+
if (i < arrayLength(&a)) {
|
|
128
|
+
a[i] = a[i] / b[i];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
"""
|
|
132
|
+
|
|
77
133
|
# ---------------------------------------------------------------------------
|
|
78
134
|
# Elementwise unary operations
|
|
79
135
|
# ---------------------------------------------------------------------------
|
|
@@ -236,17 +292,26 @@ var<workgroup> sdata: array<f32, 256>;
|
|
|
236
292
|
fn main(
|
|
237
293
|
@builtin(local_invocation_id) lid: vec3<u32>,
|
|
238
294
|
@builtin(workgroup_id) wid: vec3<u32>,
|
|
239
|
-
@builtin(global_invocation_id) gid: vec3<u32>,
|
|
240
295
|
) {
|
|
241
296
|
let local_idx = lid.x;
|
|
242
|
-
let global_idx = gid.x;
|
|
243
297
|
let n = arrayLength(&input);
|
|
298
|
+
// Each thread loads and reduces 4 elements (workgroup covers 1024 elements)
|
|
299
|
+
let base = wid.x * 1024u + local_idx;
|
|
244
300
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
sdata[local_idx] = 0.0;
|
|
301
|
+
var acc: f32 = 0.0;
|
|
302
|
+
if (base < n) {
|
|
303
|
+
acc = input[base];
|
|
249
304
|
}
|
|
305
|
+
if (base + 256u < n) {
|
|
306
|
+
acc = acc + input[base + 256u];
|
|
307
|
+
}
|
|
308
|
+
if (base + 512u < n) {
|
|
309
|
+
acc = acc + input[base + 512u];
|
|
310
|
+
}
|
|
311
|
+
if (base + 768u < n) {
|
|
312
|
+
acc = acc + input[base + 768u];
|
|
313
|
+
}
|
|
314
|
+
sdata[local_idx] = acc;
|
|
250
315
|
workgroupBarrier();
|
|
251
316
|
|
|
252
317
|
// Tree reduction
|
|
@@ -276,17 +341,25 @@ var<workgroup> sdata: array<f32, 256>;
|
|
|
276
341
|
fn main(
|
|
277
342
|
@builtin(local_invocation_id) lid: vec3<u32>,
|
|
278
343
|
@builtin(workgroup_id) wid: vec3<u32>,
|
|
279
|
-
@builtin(global_invocation_id) gid: vec3<u32>,
|
|
280
344
|
) {
|
|
281
345
|
let local_idx = lid.x;
|
|
282
|
-
let global_idx = gid.x;
|
|
283
346
|
let n = arrayLength(&input);
|
|
347
|
+
let base = wid.x * 1024u + local_idx;
|
|
284
348
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
349
|
+
var acc: f32 = -3.402823e+38;
|
|
350
|
+
if (base < n) {
|
|
351
|
+
acc = input[base];
|
|
352
|
+
}
|
|
353
|
+
if (base + 256u < n) {
|
|
354
|
+
acc = max(acc, input[base + 256u]);
|
|
355
|
+
}
|
|
356
|
+
if (base + 512u < n) {
|
|
357
|
+
acc = max(acc, input[base + 512u]);
|
|
289
358
|
}
|
|
359
|
+
if (base + 768u < n) {
|
|
360
|
+
acc = max(acc, input[base + 768u]);
|
|
361
|
+
}
|
|
362
|
+
sdata[local_idx] = acc;
|
|
290
363
|
workgroupBarrier();
|
|
291
364
|
|
|
292
365
|
var stride: u32 = 128u;
|
|
@@ -315,17 +388,25 @@ var<workgroup> sdata: array<f32, 256>;
|
|
|
315
388
|
fn main(
|
|
316
389
|
@builtin(local_invocation_id) lid: vec3<u32>,
|
|
317
390
|
@builtin(workgroup_id) wid: vec3<u32>,
|
|
318
|
-
@builtin(global_invocation_id) gid: vec3<u32>,
|
|
319
391
|
) {
|
|
320
392
|
let local_idx = lid.x;
|
|
321
|
-
let global_idx = gid.x;
|
|
322
393
|
let n = arrayLength(&input);
|
|
394
|
+
let base = wid.x * 1024u + local_idx;
|
|
323
395
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
sdata[local_idx] = 3.402823e+38; // FLT_MAX
|
|
396
|
+
var acc: f32 = 3.402823e+38;
|
|
397
|
+
if (base < n) {
|
|
398
|
+
acc = input[base];
|
|
328
399
|
}
|
|
400
|
+
if (base + 256u < n) {
|
|
401
|
+
acc = min(acc, input[base + 256u]);
|
|
402
|
+
}
|
|
403
|
+
if (base + 512u < n) {
|
|
404
|
+
acc = min(acc, input[base + 512u]);
|
|
405
|
+
}
|
|
406
|
+
if (base + 768u < n) {
|
|
407
|
+
acc = min(acc, input[base + 768u]);
|
|
408
|
+
}
|
|
409
|
+
sdata[local_idx] = acc;
|
|
329
410
|
workgroupBarrier();
|
|
330
411
|
|
|
331
412
|
var stride: u32 = 128u;
|
|
@@ -394,7 +475,7 @@ fn main(
|
|
|
394
475
|
# Each thread computes one element of C.
|
|
395
476
|
# ---------------------------------------------------------------------------
|
|
396
477
|
|
|
397
|
-
|
|
478
|
+
MATMUL_NAIVE = """
|
|
398
479
|
struct Dims {
|
|
399
480
|
M: u32,
|
|
400
481
|
K: u32,
|
|
@@ -425,6 +506,74 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
|
425
506
|
}
|
|
426
507
|
"""
|
|
427
508
|
|
|
509
|
+
# Keep backward compat alias
|
|
510
|
+
MATMUL_SHADER = MATMUL_NAIVE
|
|
511
|
+
|
|
512
|
+
# ---------------------------------------------------------------------------
|
|
513
|
+
# Tiled matrix multiplication (16x16 tiles with shared memory)
|
|
514
|
+
# ---------------------------------------------------------------------------
|
|
515
|
+
|
|
516
|
+
MATMUL_TILED = """
|
|
517
|
+
struct Dims {
|
|
518
|
+
M: u32,
|
|
519
|
+
K: u32,
|
|
520
|
+
N: u32,
|
|
521
|
+
_pad: u32,
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
@group(0) @binding(0) var<storage, read> a: array<f32>;
|
|
525
|
+
@group(0) @binding(1) var<storage, read> b: array<f32>;
|
|
526
|
+
@group(0) @binding(2) var<storage, read_write> c: array<f32>;
|
|
527
|
+
@group(0) @binding(3) var<uniform> dims: Dims;
|
|
528
|
+
|
|
529
|
+
const TILE_SIZE = 16u;
|
|
530
|
+
var<workgroup> tile_a: array<f32, 256>;
|
|
531
|
+
var<workgroup> tile_b: array<f32, 256>;
|
|
532
|
+
|
|
533
|
+
@compute @workgroup_size(16, 16)
|
|
534
|
+
fn main(@builtin(local_invocation_id) lid: vec3<u32>,
|
|
535
|
+
@builtin(workgroup_id) wid: vec3<u32>) {
|
|
536
|
+
let row = wid.y * TILE_SIZE + lid.y;
|
|
537
|
+
let col = wid.x * TILE_SIZE + lid.x;
|
|
538
|
+
let local_idx = lid.y * TILE_SIZE + lid.x;
|
|
539
|
+
|
|
540
|
+
let num_tiles = (dims.K + TILE_SIZE - 1u) / TILE_SIZE;
|
|
541
|
+
|
|
542
|
+
var sum: f32 = 0.0;
|
|
543
|
+
|
|
544
|
+
for (var t: u32 = 0u; t < num_tiles; t = t + 1u) {
|
|
545
|
+
// Load tile of A into shared memory
|
|
546
|
+
let a_col = t * TILE_SIZE + lid.x;
|
|
547
|
+
if (row < dims.M && a_col < dims.K) {
|
|
548
|
+
tile_a[local_idx] = a[row * dims.K + a_col];
|
|
549
|
+
} else {
|
|
550
|
+
tile_a[local_idx] = 0.0;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Load tile of B into shared memory
|
|
554
|
+
let b_row = t * TILE_SIZE + lid.y;
|
|
555
|
+
if (b_row < dims.K && col < dims.N) {
|
|
556
|
+
tile_b[local_idx] = b[b_row * dims.N + col];
|
|
557
|
+
} else {
|
|
558
|
+
tile_b[local_idx] = 0.0;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
workgroupBarrier();
|
|
562
|
+
|
|
563
|
+
// Compute partial dot product for this tile
|
|
564
|
+
for (var k: u32 = 0u; k < TILE_SIZE; k = k + 1u) {
|
|
565
|
+
sum = sum + tile_a[lid.y * TILE_SIZE + k] * tile_b[k * TILE_SIZE + lid.x];
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
workgroupBarrier();
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (row < dims.M && col < dims.N) {
|
|
572
|
+
c[row * dims.N + col] = sum;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
"""
|
|
576
|
+
|
|
428
577
|
# ---------------------------------------------------------------------------
|
|
429
578
|
# Clamp (requires uniform buffer with min_val, max_val)
|
|
430
579
|
# ---------------------------------------------------------------------------
|
|
@@ -598,3 +747,342 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
|
598
747
|
}
|
|
599
748
|
}
|
|
600
749
|
"""
|
|
750
|
+
|
|
751
|
+
# ---------------------------------------------------------------------------
|
|
752
|
+
# Gather: result[i] = source[indices[i]]
|
|
753
|
+
# ---------------------------------------------------------------------------
|
|
754
|
+
|
|
755
|
+
GATHER_SHADER = """
|
|
756
|
+
@group(0) @binding(0) var<storage, read> source: array<f32>;
|
|
757
|
+
@group(0) @binding(1) var<storage, read> indices: array<u32>;
|
|
758
|
+
@group(0) @binding(2) var<storage, read_write> result: array<f32>;
|
|
759
|
+
|
|
760
|
+
@compute @workgroup_size(256)
|
|
761
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
762
|
+
let i = gid.x;
|
|
763
|
+
if (i < arrayLength(&indices)) {
|
|
764
|
+
result[i] = source[indices[i]];
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
"""
|
|
768
|
+
|
|
769
|
+
# ---------------------------------------------------------------------------
|
|
770
|
+
# Broadcast binary ops: (M,N) op (N,) — broadcast 1D across rows
|
|
771
|
+
# Uniform: dims.x = N (number of columns)
|
|
772
|
+
# ---------------------------------------------------------------------------
|
|
773
|
+
|
|
774
|
+
BINARY_BROADCAST_ROW_ADD = """
|
|
775
|
+
struct Dims { cols: u32, _p1: u32, _p2: u32, _p3: u32, }
|
|
776
|
+
@group(0) @binding(0) var<storage, read> a: array<f32>;
|
|
777
|
+
@group(0) @binding(1) var<storage, read> b: array<f32>;
|
|
778
|
+
@group(0) @binding(2) var<storage, read_write> result: array<f32>;
|
|
779
|
+
@group(0) @binding(3) var<uniform> dims: Dims;
|
|
780
|
+
@compute @workgroup_size(256)
|
|
781
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
782
|
+
let i = gid.x;
|
|
783
|
+
if (i < arrayLength(&a)) { result[i] = a[i] + b[i % dims.cols]; }
|
|
784
|
+
}
|
|
785
|
+
"""
|
|
786
|
+
|
|
787
|
+
BINARY_BROADCAST_ROW_SUB = """
|
|
788
|
+
struct Dims { cols: u32, _p1: u32, _p2: u32, _p3: u32, }
|
|
789
|
+
@group(0) @binding(0) var<storage, read> a: array<f32>;
|
|
790
|
+
@group(0) @binding(1) var<storage, read> b: array<f32>;
|
|
791
|
+
@group(0) @binding(2) var<storage, read_write> result: array<f32>;
|
|
792
|
+
@group(0) @binding(3) var<uniform> dims: Dims;
|
|
793
|
+
@compute @workgroup_size(256)
|
|
794
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
795
|
+
let i = gid.x;
|
|
796
|
+
if (i < arrayLength(&a)) { result[i] = a[i] - b[i % dims.cols]; }
|
|
797
|
+
}
|
|
798
|
+
"""
|
|
799
|
+
|
|
800
|
+
BINARY_BROADCAST_ROW_MUL = """
|
|
801
|
+
struct Dims { cols: u32, _p1: u32, _p2: u32, _p3: u32, }
|
|
802
|
+
@group(0) @binding(0) var<storage, read> a: array<f32>;
|
|
803
|
+
@group(0) @binding(1) var<storage, read> b: array<f32>;
|
|
804
|
+
@group(0) @binding(2) var<storage, read_write> result: array<f32>;
|
|
805
|
+
@group(0) @binding(3) var<uniform> dims: Dims;
|
|
806
|
+
@compute @workgroup_size(256)
|
|
807
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
808
|
+
let i = gid.x;
|
|
809
|
+
if (i < arrayLength(&a)) { result[i] = a[i] * b[i % dims.cols]; }
|
|
810
|
+
}
|
|
811
|
+
"""
|
|
812
|
+
|
|
813
|
+
BINARY_BROADCAST_ROW_DIV = """
|
|
814
|
+
struct Dims { cols: u32, _p1: u32, _p2: u32, _p3: u32, }
|
|
815
|
+
@group(0) @binding(0) var<storage, read> a: array<f32>;
|
|
816
|
+
@group(0) @binding(1) var<storage, read> b: array<f32>;
|
|
817
|
+
@group(0) @binding(2) var<storage, read_write> result: array<f32>;
|
|
818
|
+
@group(0) @binding(3) var<uniform> dims: Dims;
|
|
819
|
+
@compute @workgroup_size(256)
|
|
820
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
821
|
+
let i = gid.x;
|
|
822
|
+
if (i < arrayLength(&a)) { result[i] = a[i] / b[i % dims.cols]; }
|
|
823
|
+
}
|
|
824
|
+
"""
|
|
825
|
+
|
|
826
|
+
# ---------------------------------------------------------------------------
|
|
827
|
+
# Broadcast binary ops: (M,N) op (M,1) — broadcast column vector
|
|
828
|
+
# Uniform: dims.x = N (number of columns)
|
|
829
|
+
# ---------------------------------------------------------------------------
|
|
830
|
+
|
|
831
|
+
BINARY_BROADCAST_COL_ADD = """
|
|
832
|
+
struct Dims { cols: u32, _p1: u32, _p2: u32, _p3: u32, }
|
|
833
|
+
@group(0) @binding(0) var<storage, read> a: array<f32>;
|
|
834
|
+
@group(0) @binding(1) var<storage, read> b: array<f32>;
|
|
835
|
+
@group(0) @binding(2) var<storage, read_write> result: array<f32>;
|
|
836
|
+
@group(0) @binding(3) var<uniform> dims: Dims;
|
|
837
|
+
@compute @workgroup_size(256)
|
|
838
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
839
|
+
let i = gid.x;
|
|
840
|
+
if (i < arrayLength(&a)) { result[i] = a[i] + b[i / dims.cols]; }
|
|
841
|
+
}
|
|
842
|
+
"""
|
|
843
|
+
|
|
844
|
+
BINARY_BROADCAST_COL_SUB = """
|
|
845
|
+
struct Dims { cols: u32, _p1: u32, _p2: u32, _p3: u32, }
|
|
846
|
+
@group(0) @binding(0) var<storage, read> a: array<f32>;
|
|
847
|
+
@group(0) @binding(1) var<storage, read> b: array<f32>;
|
|
848
|
+
@group(0) @binding(2) var<storage, read_write> result: array<f32>;
|
|
849
|
+
@group(0) @binding(3) var<uniform> dims: Dims;
|
|
850
|
+
@compute @workgroup_size(256)
|
|
851
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
852
|
+
let i = gid.x;
|
|
853
|
+
if (i < arrayLength(&a)) { result[i] = a[i] - b[i / dims.cols]; }
|
|
854
|
+
}
|
|
855
|
+
"""
|
|
856
|
+
|
|
857
|
+
BINARY_BROADCAST_COL_MUL = """
|
|
858
|
+
struct Dims { cols: u32, _p1: u32, _p2: u32, _p3: u32, }
|
|
859
|
+
@group(0) @binding(0) var<storage, read> a: array<f32>;
|
|
860
|
+
@group(0) @binding(1) var<storage, read> b: array<f32>;
|
|
861
|
+
@group(0) @binding(2) var<storage, read_write> result: array<f32>;
|
|
862
|
+
@group(0) @binding(3) var<uniform> dims: Dims;
|
|
863
|
+
@compute @workgroup_size(256)
|
|
864
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
865
|
+
let i = gid.x;
|
|
866
|
+
if (i < arrayLength(&a)) { result[i] = a[i] * b[i / dims.cols]; }
|
|
867
|
+
}
|
|
868
|
+
"""
|
|
869
|
+
|
|
870
|
+
BINARY_BROADCAST_COL_DIV = """
|
|
871
|
+
struct Dims { cols: u32, _p1: u32, _p2: u32, _p3: u32, }
|
|
872
|
+
@group(0) @binding(0) var<storage, read> a: array<f32>;
|
|
873
|
+
@group(0) @binding(1) var<storage, read> b: array<f32>;
|
|
874
|
+
@group(0) @binding(2) var<storage, read_write> result: array<f32>;
|
|
875
|
+
@group(0) @binding(3) var<uniform> dims: Dims;
|
|
876
|
+
@compute @workgroup_size(256)
|
|
877
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
878
|
+
let i = gid.x;
|
|
879
|
+
if (i < arrayLength(&a)) { result[i] = a[i] / b[i / dims.cols]; }
|
|
880
|
+
}
|
|
881
|
+
"""
|
|
882
|
+
|
|
883
|
+
# ---------------------------------------------------------------------------
|
|
884
|
+
# FFT shaders (Cooley-Tukey radix-2)
|
|
885
|
+
# Complex numbers stored as interleaved float32 pairs: [re0, im0, re1, im1, ...]
|
|
886
|
+
# ---------------------------------------------------------------------------
|
|
887
|
+
|
|
888
|
+
REAL_TO_COMPLEX_SHADER = """
|
|
889
|
+
@group(0) @binding(0) var<storage, read> input: array<f32>;
|
|
890
|
+
@group(0) @binding(1) var<storage, read_write> output: array<f32>;
|
|
891
|
+
|
|
892
|
+
@compute @workgroup_size(256)
|
|
893
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
894
|
+
let i = gid.x;
|
|
895
|
+
let n = arrayLength(&input);
|
|
896
|
+
if (i < n) {
|
|
897
|
+
output[i * 2u] = input[i];
|
|
898
|
+
output[i * 2u + 1u] = 0.0;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
"""
|
|
902
|
+
|
|
903
|
+
FFT_BIT_REVERSE_SHADER = """
|
|
904
|
+
struct Params {
|
|
905
|
+
N: u32,
|
|
906
|
+
log2N: u32,
|
|
907
|
+
_pad1: u32,
|
|
908
|
+
_pad2: u32,
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
@group(0) @binding(0) var<storage, read> input: array<f32>;
|
|
912
|
+
@group(0) @binding(1) var<storage, read_write> output: array<f32>;
|
|
913
|
+
@group(0) @binding(2) var<uniform> params: Params;
|
|
914
|
+
|
|
915
|
+
fn bit_reverse(x: u32, bits: u32) -> u32 {
|
|
916
|
+
var v = x;
|
|
917
|
+
var r: u32 = 0u;
|
|
918
|
+
for (var i: u32 = 0u; i < bits; i = i + 1u) {
|
|
919
|
+
r = (r << 1u) | (v & 1u);
|
|
920
|
+
v = v >> 1u;
|
|
921
|
+
}
|
|
922
|
+
return r;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
@compute @workgroup_size(256)
|
|
926
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
927
|
+
let i = gid.x;
|
|
928
|
+
if (i >= params.N) {
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
let j = bit_reverse(i, params.log2N);
|
|
932
|
+
output[j * 2u] = input[i * 2u];
|
|
933
|
+
output[j * 2u + 1u] = input[i * 2u + 1u];
|
|
934
|
+
}
|
|
935
|
+
"""
|
|
936
|
+
|
|
937
|
+
FFT_BUTTERFLY_SHADER = """
|
|
938
|
+
struct Params {
|
|
939
|
+
N: u32,
|
|
940
|
+
stage: u32,
|
|
941
|
+
_pad1: u32,
|
|
942
|
+
_pad2: u32,
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
@group(0) @binding(0) var<storage, read_write> data: array<f32>;
|
|
946
|
+
@group(0) @binding(1) var<uniform> params: Params;
|
|
947
|
+
|
|
948
|
+
@compute @workgroup_size(256)
|
|
949
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
950
|
+
let i = gid.x;
|
|
951
|
+
let m = 1u << (params.stage + 1u);
|
|
952
|
+
let half = m >> 1u;
|
|
953
|
+
|
|
954
|
+
// Total number of butterflies is N/2
|
|
955
|
+
if (i >= params.N / 2u) {
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Which butterfly group and position within group
|
|
960
|
+
let group = i / half;
|
|
961
|
+
let pos = i % half;
|
|
962
|
+
let j = group * m + pos;
|
|
963
|
+
let k = j + half;
|
|
964
|
+
|
|
965
|
+
let pi = 3.14159265358979323846;
|
|
966
|
+
let angle = -2.0 * pi * f32(pos) / f32(m);
|
|
967
|
+
let tw_re = cos(angle);
|
|
968
|
+
let tw_im = sin(angle);
|
|
969
|
+
|
|
970
|
+
// Load X[k]
|
|
971
|
+
let xk_re = data[k * 2u];
|
|
972
|
+
let xk_im = data[k * 2u + 1u];
|
|
973
|
+
|
|
974
|
+
// Twiddle multiply: W * X[k]
|
|
975
|
+
let t_re = tw_re * xk_re - tw_im * xk_im;
|
|
976
|
+
let t_im = tw_re * xk_im + tw_im * xk_re;
|
|
977
|
+
|
|
978
|
+
// Load X[j]
|
|
979
|
+
let xj_re = data[j * 2u];
|
|
980
|
+
let xj_im = data[j * 2u + 1u];
|
|
981
|
+
|
|
982
|
+
// Butterfly
|
|
983
|
+
data[j * 2u] = xj_re + t_re;
|
|
984
|
+
data[j * 2u + 1u] = xj_im + t_im;
|
|
985
|
+
data[k * 2u] = xj_re - t_re;
|
|
986
|
+
data[k * 2u + 1u] = xj_im - t_im;
|
|
987
|
+
}
|
|
988
|
+
"""
|
|
989
|
+
|
|
990
|
+
IFFT_BUTTERFLY_SHADER = """
|
|
991
|
+
struct Params {
|
|
992
|
+
N: u32,
|
|
993
|
+
stage: u32,
|
|
994
|
+
_pad1: u32,
|
|
995
|
+
_pad2: u32,
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
@group(0) @binding(0) var<storage, read_write> data: array<f32>;
|
|
999
|
+
@group(0) @binding(1) var<uniform> params: Params;
|
|
1000
|
+
|
|
1001
|
+
@compute @workgroup_size(256)
|
|
1002
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
1003
|
+
let i = gid.x;
|
|
1004
|
+
let m = 1u << (params.stage + 1u);
|
|
1005
|
+
let half = m >> 1u;
|
|
1006
|
+
|
|
1007
|
+
if (i >= params.N / 2u) {
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
let group = i / half;
|
|
1012
|
+
let pos = i % half;
|
|
1013
|
+
let j = group * m + pos;
|
|
1014
|
+
let k = j + half;
|
|
1015
|
+
|
|
1016
|
+
let pi = 3.14159265358979323846;
|
|
1017
|
+
// Conjugate twiddle for IFFT: positive angle
|
|
1018
|
+
let angle = 2.0 * pi * f32(pos) / f32(m);
|
|
1019
|
+
let tw_re = cos(angle);
|
|
1020
|
+
let tw_im = sin(angle);
|
|
1021
|
+
|
|
1022
|
+
let xk_re = data[k * 2u];
|
|
1023
|
+
let xk_im = data[k * 2u + 1u];
|
|
1024
|
+
|
|
1025
|
+
let t_re = tw_re * xk_re - tw_im * xk_im;
|
|
1026
|
+
let t_im = tw_re * xk_im + tw_im * xk_re;
|
|
1027
|
+
|
|
1028
|
+
let xj_re = data[j * 2u];
|
|
1029
|
+
let xj_im = data[j * 2u + 1u];
|
|
1030
|
+
|
|
1031
|
+
data[j * 2u] = xj_re + t_re;
|
|
1032
|
+
data[j * 2u + 1u] = xj_im + t_im;
|
|
1033
|
+
data[k * 2u] = xj_re - t_re;
|
|
1034
|
+
data[k * 2u + 1u] = xj_im - t_im;
|
|
1035
|
+
}
|
|
1036
|
+
"""
|
|
1037
|
+
|
|
1038
|
+
FFT_SCALE_SHADER = """
|
|
1039
|
+
struct Params {
|
|
1040
|
+
scalar: f32,
|
|
1041
|
+
_pad1: u32,
|
|
1042
|
+
_pad2: u32,
|
|
1043
|
+
_pad3: u32,
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
@group(0) @binding(0) var<storage, read_write> data: array<f32>;
|
|
1047
|
+
@group(0) @binding(1) var<uniform> params: Params;
|
|
1048
|
+
|
|
1049
|
+
@compute @workgroup_size(256)
|
|
1050
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
1051
|
+
let i = gid.x;
|
|
1052
|
+
if (i < arrayLength(&data)) {
|
|
1053
|
+
data[i] = data[i] * params.scalar;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
"""
|
|
1057
|
+
|
|
1058
|
+
COMPLEX_MAGNITUDE_SHADER = """
|
|
1059
|
+
@group(0) @binding(0) var<storage, read> input: array<f32>;
|
|
1060
|
+
@group(0) @binding(1) var<storage, read_write> output: array<f32>;
|
|
1061
|
+
|
|
1062
|
+
@compute @workgroup_size(256)
|
|
1063
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
1064
|
+
let i = gid.x;
|
|
1065
|
+
let n = arrayLength(&output);
|
|
1066
|
+
if (i < n) {
|
|
1067
|
+
let re = input[i * 2u];
|
|
1068
|
+
let im = input[i * 2u + 1u];
|
|
1069
|
+
output[i] = sqrt(re * re + im * im);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
"""
|
|
1073
|
+
|
|
1074
|
+
COMPLEX_PHASE_SHADER = """
|
|
1075
|
+
@group(0) @binding(0) var<storage, read> input: array<f32>;
|
|
1076
|
+
@group(0) @binding(1) var<storage, read_write> output: array<f32>;
|
|
1077
|
+
|
|
1078
|
+
@compute @workgroup_size(256)
|
|
1079
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
1080
|
+
let i = gid.x;
|
|
1081
|
+
let n = arrayLength(&output);
|
|
1082
|
+
if (i < n) {
|
|
1083
|
+
let re = input[i * 2u];
|
|
1084
|
+
let im = input[i * 2u + 1u];
|
|
1085
|
+
output[i] = atan2(im, re);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
"""
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
gpuarray/__init__.py,sha256=YmH2uwL9iez8AUGDE2OCY6sVNIrC49FNsUP5PFxrNmk,864
|
|
2
|
+
gpuarray/api.py,sha256=wiZnz4rs631nGiR_wW01Rr1BNgT0RL8fD2rMRQ7SSpg,5883
|
|
3
|
+
gpuarray/core.py,sha256=D2Q5Lwe8p2PmQjaUqMYEWd2qDSQkI1xt5IfTZ3MyIFY,39048
|
|
4
|
+
gpuarray/device.py,sha256=q80VOrKhy1u5i4AMLUKhWVBwFqQjoJ4tNBm234Wpf-A,452
|
|
5
|
+
gpuarray/shaders.py,sha256=0-71ErmWkBCHqPoROsORiVRgl48c4BsoqAjSLOn2QKg,30566
|
|
6
|
+
gpuarray-0.4.0.dist-info/METADATA,sha256=GRL-qds44XKfiHArZsRivg-WEVWondmQtOEXA0tgofI,3132
|
|
7
|
+
gpuarray-0.4.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
8
|
+
gpuarray-0.4.0.dist-info/top_level.txt,sha256=iruYli4LmPBqhaZxTVvzT1bT0VeN6dbC0GE29lT1CbI,9
|
|
9
|
+
gpuarray-0.4.0.dist-info/RECORD,,
|
gpuarray-0.2.0.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
gpuarray/__init__.py,sha256=MB9k9oXPvA6wRCCZii72HtpRxjXPtkiT2RaJsiVY-wY,752
|
|
2
|
-
gpuarray/api.py,sha256=PlYqtvY7jEV3xIneGdRoqXJjVOP3w2fnHyZgVP6fqis,4874
|
|
3
|
-
gpuarray/core.py,sha256=bvBlQ9YSTRUcYawpnHpWnD-zJFdeMV16mv_rNFxYBGQ,18934
|
|
4
|
-
gpuarray/device.py,sha256=q80VOrKhy1u5i4AMLUKhWVBwFqQjoJ4tNBm234Wpf-A,452
|
|
5
|
-
gpuarray/shaders.py,sha256=LhS3vEZc08Mx_OPnsv06bT9gRPThuHIxBuWO0V2C_SI,16301
|
|
6
|
-
gpuarray-0.2.0.dist-info/METADATA,sha256=8ZL33ZQ7eVBnm4GuvjLxNVCiVl39u2maLCpGXbuI24w,3132
|
|
7
|
-
gpuarray-0.2.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
8
|
-
gpuarray-0.2.0.dist-info/top_level.txt,sha256=iruYli4LmPBqhaZxTVvzT1bT0VeN6dbC0GE29lT1CbI,9
|
|
9
|
-
gpuarray-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|