openai_gemm.py (505 lines of code) (raw):
import re
import time
import ctypes
import appdirs
import os.path
import subprocess
import numpy as np
import pycuda.driver as drv
from operator import mul
from struct import unpack_from
from pycuda.tools import context_dependent_memoize
from scikits.cuda import cublas
def matmul(A, B, C, alpha=1.0, beta=0.0, stream=None, bench=False):
"""
C = alpha * A . B + beta * C
C = alpha * A.T . B + beta * C
C = alpha * A . B.T + beta * C
C = alpha * A.T . B.T + beta * C
bench: return benchmark data for all available tiles + cublas
"""
# this could be relaxed, kernels are capable of mixed precision (with minor tweaks)
# the s/h prefix would then go away and each type would be specified with kernel build option
assert A.dtype.type == B.dtype.type == C.dtype.type
if C.dtype.type is np.float32:
prefix = "s"
elif C.dtype.type is np.float16:
prefix = "h"
else:
raise TypeError("Only floating point dot currently supported.")
# (m,n) = (m,k) . (k,n)
m = A.shape[0]
n = B.shape[1]
k = A.shape[1]
assert m == C.shape[0]
assert n == C.shape[1]
assert k == B.shape[0]
# Extract the operations and contiguous dimension sizes (cda, cdb, cdc).
# Note that these can be the same as from the shape unless the non-contiguous dimension is sliced.
# One dimension must be contiguous (DRAM efficiency demands this).
# Note that the strides here do not include the datatype size as they would in numpy.
# A transpose op (.T) on a GPUTensor reverses the shape and strides then flags the tensor as transposed (is_trans=True) -
# The underlying data is unchanged.
if A.is_trans:
opA = 'T'
cda = A.strides[1]
assert A.strides[0] == 1
else:
opA = 'N'
cda = A.strides[0]
assert A.strides[1] == 1
if B.is_trans:
opB = 'T'
cdb = B.strides[1]
assert B.strides[0] == 1
else:
opB = 'N'
cdb = B.strides[0]
assert B.strides[1] == 1
cdc = C.strides[0]
assert C.strides[1] == 1
op = opA + opB
# get and autotune the kernel selection
kernel, params, dynamic_shared = _get_gemm_kernel(prefix, op, cda, cdb, cdc, m, n, k)
# bind dynamic params
params[2:8] = (stream, C.gpudata, A.gpudata, B.gpudata, alpha, beta)
# call the kernel
kernel.prepared_async_call(*params, shared_size=dynamic_shared)
# unbind dynamic params
params[2:8] = (None,) * 6
# return benchmark data if requested
if bench:
return _get_bench_data()[(prefix, op, cda, cdb, cdc, m, n, k)]
return C
####################################################################################################
# scikits.cuda doesn't expose cublasSgemmEx or cublasHgemm
cublas._libcublas.cublasSgemmEx.restype = int
cublas._libcublas.cublasSgemmEx.argtypes = [
cublas._types.handle,
ctypes.c_int, ctypes.c_int,
ctypes.c_int, ctypes.c_int, ctypes.c_int,
ctypes.c_void_p,
ctypes.c_void_p, ctypes.c_int, ctypes.c_int,
ctypes.c_void_p, ctypes.c_int, ctypes.c_int,
ctypes.c_void_p,
ctypes.c_void_p, ctypes.c_int, ctypes.c_int ]
def cublasSgemmEx(handle, transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc):
status = cublas._libcublas.cublasSgemmEx(handle,
cublas._CUBLAS_OP[transa], cublas._CUBLAS_OP[transb],
m, n, k,
ctypes.byref(ctypes.c_float(alpha)),
int(A), 2, lda,
int(B), 2, ldb,
ctypes.byref(ctypes.c_float(beta)),
int(C), 2, ldc)
cublas.cublasCheckStatus(status)
cublas._libcublas.cublasHgemm.restype = int
cublas._libcublas.cublasHgemm.argtypes = [
cublas._types.handle,
ctypes.c_int, ctypes.c_int,
ctypes.c_int, ctypes.c_int, ctypes.c_int,
ctypes.c_void_p,
ctypes.c_void_p, ctypes.c_int,
ctypes.c_void_p, ctypes.c_int,
ctypes.c_void_p,
ctypes.c_void_p, ctypes.c_int ]
h_dtype = np.dtype(np.float16)
def cublasHgemm(handle, transa, transb, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc):
alpha = unpack_from('H', h_dtype.type(alpha))[0]
beta = unpack_from('H', h_dtype.type(beta))[0]
status = cublas._libcublas.cublasHgemm(handle,
cublas._CUBLAS_OP[transa], cublas._CUBLAS_OP[transb],
m, n, k,
ctypes.byref(ctypes.c_uint16(alpha)),
int(A), lda,
int(B), ldb,
ctypes.byref(ctypes.c_uint16(beta)),
int(C), ldc)
cublas.cublasCheckStatus(status)
cublasXgemm = {
"s" : cublas.cublasSgemm,
"h" : cublasSgemmEx,
"h2" : cublasHgemm,
}
@context_dependent_memoize
def _get_sm_count():
attributes = drv.Context.get_device().get_attributes()
return attributes[drv.device_attribute.MULTIPROCESSOR_COUNT]
@context_dependent_memoize
def _get_compute_capability():
attributes = drv.Context.get_device().get_attributes()
major = attributes[drv.device_attribute.COMPUTE_CAPABILITY_MAJOR]
minor = attributes[drv.device_attribute.COMPUTE_CAPABILITY_MINOR]
return major, minor
@context_dependent_memoize
def _get_events():
return (drv.Event(), drv.Event())
@context_dependent_memoize
def _get_cublas():
return cublas.cublasCreate()
@context_dependent_memoize
def _get_bench_data():
return dict()
def _ceil_div(x, y):
return -(-x // y)
def _closest_divisor(val, div):
divisors = sorted([(abs(i - div), i) for i in range(2, 8) if val % i == 0])
if len(divisors):
return (divisors[0][1], val // divisors[0][1])
else:
return (1, val)
# Tile sizes: m, n, k, vA,vB,vC div, op (dynamic shared options)
k128x128x8 = (128, 128, 8, 4, 4, 1, 2, 0, (0,))
k32x32x32 = ( 32, 32, 32, 4, 4, 1, 4, 0, (0, 2**14))
k32x64x32_NN = ( 32, 64, 32, 8, 4, 4, 4, 1, (0, 2**13))
k32x32x64_NT = ( 32, 32, 64, 8, 8, 4, 4, 1, (0,))
k16x64x64_NN = ( 16, 64, 64, 8, 4, 4, 4, 1, (0,))
k16x64x64_NT = ( 16, 64, 64, 8, 8, 4, 4, 1, (0,))
selections = {
"s" : {
"TN" : (k128x128x8, k32x32x32),
"NN" : (k128x128x8, k32x32x32),
"NT" : (k128x128x8, k32x32x32),
"TT" : (k128x128x8, k32x32x32),
},
"h" : {
"TN" : (k128x128x8, k32x32x32),
"NN" : (k128x128x8, k32x32x32, k32x64x32_NN, k16x64x64_NN),
"NT" : (k128x128x8, k32x32x32, k32x32x64_NT, k16x64x64_NT),
"TT" : (k128x128x8, k32x32x32),
},
}
# Autotune kernel selection
@context_dependent_memoize
def _get_gemm_kernel(prefix, op, cda, cdb, cdc, m, n, k):
if op[0] == 'T':
vec4A = (cda & 3) == 0 and (m & 3) == 0
vec8A = (cda & 7) == 0 and (m & 7) == 0
dimA = (k,cda)
else:
vec4A = (cda & 3) == 0 and (k & 3) == 0
vec8A = (cda & 7) == 0 and (k & 7) == 0
dimA = (m,cda)
if op[1] == 'T':
vec4B = (cdb & 3) == 0 and (k & 3) == 0
vec8B = (cdb & 7) == 0 and (k & 7) == 0
dimB = (n,cdb)
else:
vec4B = (cdb & 3) == 0 and (n & 3) == 0
vec8B = (cdb & 7) == 0 and (n & 7) == 0
dimB = (k,cdb)
vec4C = (cdc & 3) == 0 and (n & 3) == 0
dimC = (m,cdc)
dtype = np.dtype(np.float32 if prefix == 's' else np.float16)
A = drv.mem_alloc(mul(*dimA) * dtype.itemsize)
B = drv.mem_alloc(mul(*dimB) * dtype.itemsize)
C = drv.mem_alloc(mul(*dimC) * dtype.itemsize)
# TODO: use curand
dataA = np.random.uniform(-1.0, 1.0, dimA).astype(dtype)
dataB = np.random.uniform(-1.0, 1.0, dimB).astype(dtype)
drv.memcpy_htod(int(A), dataA)
drv.memcpy_htod(int(B), dataB)
# Using random data gets you more accurate autotune results
# drv.memset_d8(int(A), 0, mul(*dimA) * dtype.itemsize)
# drv.memset_d8(int(B), 0, mul(*dimB) * dtype.itemsize)
timings = []
cache = []
# scale the repeat count to amount of work
repeat = min(max(int(5e11 * 28 / (m*n*k * 2.0 * _get_sm_count()) ), 10), 5000)
warmup = repeat
#print repeat
start, end = _get_events()
flops = m * n * k * 2.0
for tileM, tileN, tileK, vecA, vecB, vecC, div, base_op, dyn_shared in selections[prefix][op]:
vecA = (vecA == 4 and vec4A) or (vecA == 8 and vec8A)
vecB = (vecB == 4 and vec4B) or (vecB == 8 and vec8B)
vecC = vecC == 1 or vec4C
vec = vecA and vecB and vecC
if base_op:
# The op is part of the base kernel name
base = "%sgemm_%dx%dx%d_%s" % (prefix, tileM, tileN, tileK, op)
opts = ( "vec", ) if vec else ()
else:
# The op is an option passed to a more generic kernel
base = "%sgemm_%dx%dx%d" % (prefix, tileM, tileN, tileK)
opts = ( op, "vec" ) if vec else (op,)
kernel = get_kernel(base, opts)
blk_A = _ceil_div(m, tileM)
blk_B = _ceil_div(n, tileN)
# TODO: perhaps autotune all possible small divisors
blk_a, blk_A = _closest_divisor(blk_A, div)
blk_b, blk_B = _closest_divisor(blk_B, div)
if blk_a == 1:
blk_a, blk_A = (blk_A, 1)
for dynamic_shared in dyn_shared:
params = [
(blk_a * blk_b, blk_B, blk_A), (kernel.threads, 1, 1), None,
C, A, B, 1.0, 0.0,
cda, cdb, cdc, m, n, k, blk_a, blk_b ]
#print kernel.name, params, dynamic_shared
# Warmup (once per config)
for r in range(warmup):
kernel.prepared_async_call(*params)
warmup = 0
# Benchmark
start.record()
for r in range(repeat):
kernel.prepared_async_call(*params, shared_size=dynamic_shared)
end.record()
end.synchronize()
msecs = end.time_since(start) / float(repeat)
gflops = flops / (msecs * 1000000.0)
params[3:8] = (None,) * 5
timings.append((msecs, gflops, kernel, params, dynamic_shared))
cache.append((msecs, gflops, kernel.name, dynamic_shared))
major, minor = _get_compute_capability()
if prefix == "h" and major == 6 and minor == 0:
cublas_gemm = cublasXgemm["h2"]
else:
cublas_gemm = cublasXgemm[prefix]
# record a cublas time for reference
cublas_handle = _get_cublas()
start.record()
for r in range(repeat):
# convert row order to col order
cublas_gemm(cublas_handle, op[1], op[0], n, m, k, 1.0, B, cdb, A, cda, 0.0, C, cdc)
end.record()
end.synchronize()
msecs = end.time_since(start) / float(repeat)
gflops = flops / (msecs * 1000000.0)
cache.append( (msecs, gflops, "cuBLAS", 0) )
# cache complete timing data for benchmark comparisons
# this data could be cached to disk for quicker autotuning on future runs
_get_bench_data()[(prefix, op, cda, cdb, cdc, m, n, k)] = cache
# return the fastest kernel
return tuple(sorted(timings)[0][2:5])
# Utility function to test all tiles for the given dimensions and dtype
def matmul_test(ng, dtype, op, m, n, k, ones=False, out=False):
prefix = "s" if dtype is np.float32 else "h"
if op[0] == 'T':
vec4A = (m & 3) == 0
vec8A = (m & 7) == 0
dimA = (k,m)
cda = m
else:
vec4A = (k & 3) == 0
vec8A = (k & 7) == 0
dimA = (m,k)
cda = k
if op[1] == 'T':
vec4B = (k & 3) == 0
vec8B = (k & 7) == 0
dimB = (n,k)
cdb = k
else:
vec4B = (n & 3) == 0
vec8B = (n & 7) == 0
dimB = (k,n)
cdb = n
vec4C = (n & 3) == 0
dimC = (m,n)
cdc = n
A1 = ng.empty(dimA, dtype=dtype)
B1 = ng.empty(dimB, dtype=dtype)
C1 = ng.empty(dimC, dtype=dtype)
C2 = ng.empty(dimC, dtype=dtype)
if ones:
A1[:] = 1.0
B1[:] = 1.0
else:
# fill with uniform randoms from -1 to 1
A1[:] = 2 * (.5 - ng.rand())
B1[:] = 2 * (.5 - ng.rand())
# for reducing outputs
partial1 = ng.empty((C1.shape[0],1), dtype=np.float32)
partial2 = partial1[0:1,0:1]
cublas_handle = _get_cublas()
for tileM, tileN, tileK, vecA, vecB, vecC, div, base_op, dyn_shared in selections[prefix][op]:
vecA = (vecA == 4 and vec4A) or (vecA == 8 and vec8A)
vecB = (vecB == 4 and vec4B) or (vecB == 8 and vec8B)
vecC = vecC == 1 or vec4C
vec = vecA and vecB and vecC
if base_op:
# The op is part of the base kernel name
base = "%sgemm_%dx%dx%d_%s" % (prefix, tileM, tileN, tileK, op)
opts = ( "vec", ) if vec else ()
else:
# The op is an option passed to a more generic kernel
base = "%sgemm_%dx%dx%d" % (prefix, tileM, tileN, tileK)
opts = ( op, "vec" ) if vec else (op,)
kernel = get_kernel(base, opts)
blk_A = _ceil_div(m, tileM)
blk_B = _ceil_div(n, tileN)
blk_a, blk_A = _closest_divisor(blk_A, div)
blk_b, blk_B = _closest_divisor(blk_B, div)
if blk_a == 1:
blk_a, blk_A = (blk_A, 1)
for alpha, beta in ( (1.0,0.0), (0.5,0.5) ):
try:
if ones:
C1[:] = 1.0
else:
C1[:] = 2 * (.5 - ng.rand())
C2[:] = C1
params = [
(blk_a * blk_b, blk_B, blk_A), (kernel.threads, 1, 1), None,
C1.gpudata, A1.gpudata, B1.gpudata, alpha, beta,
cda, cdb, cdc, m, n, k, blk_a, blk_b ]
kernel.prepared_async_call(*params)
# convert row order to col order
cublasXgemm[prefix](cublas_handle, op[1], op[0], n, m, k, alpha, B1.gpudata, cdb, A1.gpudata, cda, beta, C2.gpudata, cdc)
# Check for NaNs
partial1[:] = ng.min(ng.finite(C1), axis=1)
partial2[:] = ng.min(partial1, axis=0)
if partial2.get()[0,0] == 0.0:
print "Error: NaN kernel: %s mnk: (%d,%d,%d) ab: (%f,%f)" % (kernel.name, m,n,k, alpha,beta)
exit()
# Get Max Diff
partial1[:] = ng.max(abs(C2 - C1), axis=1)
partial2[:] = ng.max(partial1, axis=0)
diff = partial2.get()[0,0]
# Get Mean
partial1[:] = ng.sum(abs(C2), axis=1)
partial2[:] = ng.sum(partial1, axis=0)
mean = partial2.get()[0,0] / C2.size
# Scale diff by the mean
pctErr = 100 * diff / mean
#print "Error: %.3f %s" % (pctErr, kernel.name)
maxerr = .005 if dtype is np.float32 else 0.7
if pctErr > maxerr:
print "Error: %.3f%% diff: %.5f mean %.5f kernel: %s mnk: (%d,%d,%d) ab: (%f,%f)" % (pctErr, diff, mean, kernel.name, m,n,k, alpha,beta)
print params
if out:
C1 = C1.get()
C2 = C2.get()
D = abs(C2 - C1)
np.savetxt("out_diff.txt", D, fmt='%3.1f')
np.savetxt("out_correct.txt", C2, fmt='%5.1f')
np.savetxt("out_error", C1, fmt='%5.1f')
exit()
except drv.Error as e:
print "kernel: %s mnk: (%d,%d,%d) ab: (%f,%f)" % (kernel.name, m,n,k, alpha,beta)
print e
exit()
### below code adapted from Nervana Neon: kernel_specs.py
def _get_cache_dir(subdir=None):
cache_dir = appdirs.user_cache_dir("openai-gemm")
if subdir:
subdir = subdir if isinstance(subdir, list) else [subdir]
cache_dir = os.path.join(cache_dir, *subdir)
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
return cache_dir
# helpful for kernel development
debug = 0
base_dir = os.path.dirname(__file__)
maxas_dir = os.path.join(base_dir, "maxas")
sass_dir = os.path.join(base_dir, "sass")
kernels = {
# Generic gemm tiles
"sgemm_128x128x8": {"threads": 256, "sass": "xgemm_128x128x8", "params": "xgemm", "share": "(128*8 + 32)*4 + 4", "args": {"type": "s"} },
"hgemm_128x128x8": {"threads": 256, "sass": "xgemm_128x128x8", "params": "xgemm", "share": "(128*8 + 32)*4 + 4", "args": {"type": "h"} },
"sgemm_32x32x32": {"threads": 128, "sass": "xgemm_32x32x32", "params": "xgemm", "share": "(32*33)*4 + 4", "args": {"type": "s"} },
"hgemm_32x32x32": {"threads": 128, "sass": "xgemm_32x32x32", "params": "xgemm", "share": "(32*33)*4 + 4", "args": {"type": "h"} },
# Custom hgemm tiles designed for small minibatch RNNs
"hgemm_32x64x32_NN": {"threads": 128, "sass": "hgemm_32x64x32_NN", "params": "xgemm", "share": "32*33*2 + 64*32*2 + 4" },
"hgemm_32x32x64_NT": {"threads": 128, "sass": "hgemm_32x32x64_NT", "params": "xgemm", "share": "32*65*4 + 4" },
"hgemm_16x64x64_NN": {"threads": 128, "sass": "hgemm_16x64x64_NN", "params": "xgemm", "share": "(16*64 + 32)*2 + 64*64*2 + 4" },
"hgemm_16x64x64_NT": {"threads": 128, "sass": "hgemm_16x64x64_NT", "params": "xgemm", "share": "(16*64 + 32)*2 + (64*64 + 32)*2 + 4" },
}
_params = {
"xgemm": [
"float* param_C",
"float* param_A",
"float* param_B",
"float param_alpha",
"float param_beta",
"unsigned param_cda",
"unsigned param_cdb",
"unsigned param_cdc",
"unsigned param_m",
"unsigned param_n",
"unsigned param_k",
"unsigned param_blk_a",
"unsigned param_blk_b",
],
}
_space_re = re.compile(r"\s+")
_share_template = r"""
.shared .align 4 .b32 share[{0}];
"""
_kernel_template = r"""
.version {6}
.target {0}
.address_size 64
// args: {5}
.visible .entry {1}(
{2}
)
.reqntid {3}
{{
{4}
ret;
}}
"""
def get_ptx_file(kernel_spec, kernel_name, arch, ptx_ver):
ptx_dir = _get_cache_dir([arch, 'ptx'])
thread_spec = kernel_spec["threads"]
args_spec = str(kernel_spec.get("args",""))
param_spec = _params[kernel_spec["params"]]
kernel_params = []
for p in param_spec:
ptype, pname = _space_re.split(p)
if ptype[-1] == '*':
ptype = '.u64'
elif ptype == 'float':
ptype = '.f32'
else:
ptype = '.u32'
kernel_params.append(" .param %s %s" % (ptype, pname))
kernel_params = ",\n".join(kernel_params)
if "share" in kernel_spec:
share = _share_template.format(eval(kernel_spec["share"]))
else:
share = ""
kernel_text = _kernel_template.format(arch, kernel_name, kernel_params, thread_spec, share, args_spec, ptx_ver)
kernel_ptx = os.path.join(ptx_dir, kernel_name + ".ptx")
current_text = ""
if os.path.exists(kernel_ptx):
f = open(kernel_ptx, "r")
current_text = f.read()
f.close()
# only write out the kernel if text has changed.
if kernel_text != current_text:
f = open(kernel_ptx, "w")
f.write(kernel_text)
f.close()
return kernel_ptx
include_re = re.compile(r'^<INCLUDE\s+file="([^"]+)"\s*/>')
def extract_includes(name, includes=None):
if not includes:
includes = list()
sass_file = os.path.join(sass_dir, name)
includes.append((sass_file, os.path.getmtime(sass_file)))
for line in open(sass_file, "r"):
match = include_re.search(line)
if match:
extract_includes(match.group(1), includes)
return includes
def run_command(cmdlist):
cmd = " ".join(cmdlist)
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate()
if proc.returncode:
raise RuntimeError("Error(%d):\n%s\n%s" % (proc.returncode, cmd, err))
if debug:
print cmd
if out: print out
if err: print err
@context_dependent_memoize
def get_kernel(base_name, options=None):
major, minor = _get_compute_capability()
if major < 5:
raise RuntimeError("sass kernels require Maxwell or greater class hardware")
arch = "sm_%d%d" % (major, minor)
libprefix = "PERL5LIB=%s" % maxas_dir
maxas_i = [libprefix, os.path.join(maxas_dir, "maxas.pl") + " -i -w"]
maxas_p = [libprefix, os.path.join(maxas_dir, "maxas.pl") + " -p"]
kernel_spec = kernels[base_name]
kernel_name = base_name
# static options
if "args" in kernel_spec:
for pair in kernel_spec["args"].items():
maxas_i.append("-D%s %s" % pair)
maxas_p.append("-D%s %s" % pair)
# dynamic options
if options is not None:
for opt in options:
if type(opt) is tuple:
maxas_i.append("-D%s %s" % opt)
maxas_p.append("-D%s %s" % opt)
kernel_name += "_%s%s" % opt
else:
maxas_i.append("-D%s 1" % opt)
maxas_p.append("-D%s 1" % opt)
kernel_name += "_%s" % opt
maxas_i.insert(2, "-k " + kernel_name)
sass_name = kernel_spec["sass"] + ".sass"
cubin_name = kernel_name + ".cubin"
cubin_dir = _get_cache_dir([arch, 'cubin'])
ptx_version = "4.2" if major < 6 else "5.0"
ptx_file = get_ptx_file(kernel_spec, kernel_name, arch, ptx_version)
sass_file = os.path.join(sass_dir, sass_name)
cubin_file = os.path.join(cubin_dir, cubin_name)
if not os.path.exists(sass_file):
raise RuntimeError("Missing: %s for kernel: %s" % (sass_name, kernel_name))
ptx_mtime = os.path.getmtime(ptx_file)
cubin_mtime = os.path.getmtime(cubin_file) if os.path.exists(cubin_file) else 0
build_cubin = False
if ptx_mtime > cubin_mtime:
build_cubin = True
includes = extract_includes(sass_name)
for include, include_mtime in includes:
if include_mtime > cubin_mtime:
build_cubin = True
break
if build_cubin:
# build the cubin and run maxas in the same command
# we don't want the chance of a generated cubin not processed by maxas (in case user hits ^C in between these steps)
run_command([ "ptxas -v -arch", arch, "-o", cubin_file, ptx_file, ";" ] + maxas_i + [sass_file, cubin_file])
cubin_mtime = time.time()
# output preprocessed and disassembled versions in debug mode
if debug:
pre_dir = _get_cache_dir([arch, 'pre'])
dump_dir = _get_cache_dir([arch, 'dump'])
pre_file = os.path.join(pre_dir, kernel_name + "_pre.sass")
dump_file = os.path.join(dump_dir, kernel_name + "_dump.sass")
pre_mtime = os.path.getmtime(pre_file) if os.path.exists(pre_file) else 0
dump_mtime = os.path.getmtime(dump_file) if os.path.exists(dump_file) else 0
for include, include_mtime in includes:
if include_mtime > pre_mtime:
run_command(maxas_p + [sass_file, pre_file])
break
if cubin_mtime > dump_mtime:
run_command(["nvdisasm -c", cubin_file, ">", dump_file])
# generate the function signature for pycuda
params = _params[kernel_spec["params"]]
sig = ""
for p in params:
ptype, pname = _space_re.split(p)
if ptype[-1] == '*':
sig += "Q"
elif ptype == 'float':
sig += "f"
elif ptype == 'unsigned':
sig += "I"
else:
sig += "i"
module = drv.module_from_file(cubin_file)
func = module.get_function(kernel_name)
func.prepare(sig)
func.threads = kernel_spec["threads"]
func.name = kernel_name
func.static_shared = eval(kernel_spec["share"])
return func