Repository: numba/numba

Issue:
Cuda.jit does not seem to respect max_registers keyword
 numba=0.42.0=py37h962f231_0

I have been running into problems with the number of registers in the kernel limiting the number of threads so I have tried using max_registers to clamp the number of registers but it seems to have no effect.


Example of how I'm using it in a decorator:
```
@cuda.jit('void(signature-omitted-for-clarity)' , max_registers=40)   
def foo( code omitted here for clarity):  
```
**expected outcome**
I expect that if I set max_registers=40 then the kernel should not use more than 40 registers.

**actual outcome**
I noticed the register problem because at run time I will get an out-of-resources error if the number of threads exceeds some value less than the SM thread capacity of 1024, and this value decreases as the kernel code gets more complicated, indicating a register count problem. But  Changing the max_registers setting has no effect on the out-of-resources error.  So I dug a little deeper...

**regression**
I inspected the ptax info to see how many registers were in use as follows:

```
with open('/tmp/info.ptx','w') as out:
    print(foo._func.get_info(),file=out)
```

the output seems to tell me that 64 registers were used despite the max_registers keyword used in the decorator.
```
cat /tmp/info.ptx 
ptxas info    : 28 bytes gmem
ptxas info    : Compiling entry function '_ZN6cudapy8__main__35cu_u_driver_global_funcs_col_py$242E5ArrayIfLi2E1A7mutable7alignedE5ArrayIfLi2E1A7mutable7alignedE5ArrayIfLi2E1A7mutable7alignedE5ArrayIfLi2E1A7mutable7alignedE5ArrayIfLi2E1A7mutable7alignedEff5ArrayIfLi2E1A7mutable7alignedE' for 'sm_75'
ptxas info    : Function properties for _ZN6cudapy8__main__35cu_u_driver_global_funcs_col_py$242E5ArrayIfLi2E1A7mutable7alignedE5ArrayIfLi2E1A7mutable7alignedE5ArrayIfLi2E1A7mutable7alignedE5ArrayIfLi2E1A7mutable7alignedE5ArrayIfLi2E1A7mutable7alignedEff5ArrayIfLi2E1A7mutable7alignedE
ptxas         .     0 bytes stack frame, 0 bytes spill stores, 0 bytes spill loads
ptxas info    : Used 64 registers, 792 bytes cmem[0]
info    : 40 bytes gmem
info    : Function properties for '_ZN6cudapy8__main__35cu_u_driver_global_funcs_col_py$242E5ArrayIfLi2E1A7mutable7alignedE5ArrayIfLi2E1A7mutable7alignedE5ArrayIfLi2E1A7mutable7alignedE5ArrayIfLi2E1A7mutable7alignedE5ArrayIfLi2E1A7mutable7aligned
```

**Code to reproduce**
```
import numpy as np
from numba import cuda

@cuda.jit('void(float32[:,:], float32[:,:], float32[:,:],float32[:,:],float32[:,:],float32,float32,float32[:,:])' , max_registers=40)        
def  foo(h, n, u, v, f, dx, dy, out_u):
    sj,si  = cuda.grid(2)  # cuda.x cuda.y skow,fast
    
    # the following dummy code is just here to do anything that is
    # going to create a a kernel using a bunch of registers
    # the code itself is irrelevant I believe.
    
    
    gj = sj
    gi = 31 #out_u.shape[0]-2
    if  (gj < out_u.shape[1]-2) and gj>0 :
        #for gi in range(1, out_u.shape[0]-2):    
        while gi>1:
                 gi=gi-1
                 
              
                 tmp=np.float32(0.0)
                 j = gj
                 i = gi
                 p5 = np.float32(0.5)
                 last_v_n_i = (v[2,j]+v[2,5])*p5
                 v_n = (v[i,j]+v[i,5])*p5  # stradles n[i,j]
                 coriolis_u = (f[99,j]*last_v_n_i+f[i,j]*v_n)*p5 # coriolis force F is n-centered
                 tmp+= coriolis_u
                 out_u[gi,gj] = tmp #funcs(si,sj,h, n, u, v, f, dx, dy)


with open('/tmp/info.ptx','w') as out:
    print(foo._func.get_info(),file=out)
```
To see output look in the file it writes in /tmp/info.ptx



Discussion:
Thanks for the report. Please could you share the following:
 * Which version of Numba are you using?
 * What the output of the `CUDA` section from running the `numba -s` command is?
 * Some code to reproduce this?

Thanks.
```
 numba -s
System info:
--------------------------------------------------------------------------------
__Time Stamp__
2019-02-22 17:07:55.544606

__Hardware Information__
Machine                                       : x86_64
CPU Name                                      : skylake
Number of accessible CPU cores                : 8
Listed accessible CPUs cores                  : 0-7
CFS restrictions                              : None
CPU Features                                  : 
adx aes avx avx2 bmi bmi2 clflushopt cmov cx16 f16c fma fsgsbase invpcid lzcnt
mmx movbe pclmul popcnt prfchw rdrnd rdseed rtm sahf sgx sse sse2 sse3 sse4.1
sse4.2 ssse3 xsave xsavec xsaveopt xsaves

__OS Information__
Platform                                      : Linux-4.15.0-42-generic-x86_64-with-debian-buster-sid
Release                                       : 4.15.0-42-generic
System Name                                   : Linux
Version                                       : #45-Ubuntu SMP Thu Nov 15 19:32:57 UTC 2018
OS specific info                              : debianbuster/sid
glibc info                                    : glibc 2.10

__Python Information__
Python Compiler                               : GCC 7.3.0
Python Implementation                         : CPython
Python Version                                : 3.7.2
Python Locale                                 : en_US UTF-8

__LLVM information__
LLVM version                                  : 7.0.0

__CUDA Information__
Found 1 CUDA devices
id 0     b'GeForce RTX 2070'                              [SUPPORTED]
                      compute capability: 7.5
                           pci device id: 0
                              pci bus id: 1
Summary:
	1/1 devices are supported
CUDA driver version                           : 10000
CUDA libraries:
Finding cublas
	named  libcublas.so.10.0.130
	trying to open library...	ok
Finding cusparse
	named  libcusparse.so.10.0.130
	trying to open library...	ok
Finding cufft
	named  libcufft.so.10.0.145
	trying to open library...	ok
Finding curand
	named  libcurand.so.10.0.130
	trying to open library...	ok
Finding nvvm
	named  libnvvm.so.3.3.0
	trying to open library...	ok
	finding libdevice for compute_20...	ok
	finding libdevice for compute_30...	ok
	finding libdevice for compute_35...	ok
	finding libdevice for compute_50...	ok

__ROC Information__
ROC available                                 : False
Error initialising ROC due to                 : No ROC toolchains found.
No HSA Agents found, encountered exception when searching:
Error at driver init: 
NUMBA_HSA_DRIVER /opt/rocm/lib/libhsa-runtime64.so is not a valid file path.  Note it must be a filepath of the .so/.dll/.dylib or the driver:

__SVML Information__
SVML state, config.USING_SVML                 : True
SVML library found and loaded                 : True
llvmlite using SVML patched LLVM              : True
SVML operational                              : True

__Threading Layer Information__
TBB Threading layer available                 : True
OpenMP Threading layer available              : True
Workqueue Threading layer available           : True

__Numba Environment Variable Information__
None set.

__Conda Information__
Conda not present/not working.
Error was [Errno 2] No such file or directory: 'conda': 'conda'

--------------------------------------------------------------------------------
```============================================

I'm cleaning up my code as a runable example and will post it shortly
I just added the numba -s

I was also thinking about putting in a feature request for Numba to give a better error message at runtime.  Maybe I'm wrong, but I think it shouldbe detectable when registers*thread_block_size > 65,000 or whatever the registers per SM of the cuda device in use has.  It would be nice to even make that an accessible variable e.g. function.register_count  so the user could dynamically set the number of threads at runtime.  I see there is already a request to simplyfy access to the ptax  text information to access these register counts.

Would that be useful to request?

> On Feb 22, 2019, at 10:07 AM, stuartarchibald <notifications@github.com> wrote:
> 
> Thanks for the report. Please could you share the following:
> 
> Which version of Numba are you using?
> What the output of the CUDA section from running the numba -s command is?
> Some code to reproduce this?
> Thanks.
> 
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub <https://github.com/numba/numba/issues/3795#issuecomment-466470746>, or mute the thread <https://github.com/notifications/unsubscribe-auth/AKII_hAWjbv-qSz44Le2FLS-8zfLNxNJks5vQCPDgaJpZM4bJ_42>.
> 


here's the conda environment
```
conda env export
name: shallowwater
channels:
  - numba
  - defaults
dependencies:
  - backcall=0.1.0=py37_0
  - blas=1.0=mkl
  - bleach=3.1.0=py37_0
  - bzip2=1.0.6=h14c3975_5
  - ca-certificates=2018.12.5=0
  - certifi=2018.11.29=py37_0
  - cftime=1.0.3.4=py37hdd07704_0
  - cudatoolkit=10.0.130=0
  - cudnn=7.3.1=cuda10.0_0
  - cupy=5.1.0=py37hc0ce245_0
  - curl=7.63.0=hbc83047_1000
  - cycler=0.10.0=py37_0
  - dbus=1.13.6=h746ee38_0
  - decorator=4.3.0=py37_0
  - entrypoints=0.3=py37_0
  - expat=2.2.6=he6710b0_0
  - fastrlock=0.4=py37he6710b0_0
  - fontconfig=2.13.0=h9420a91_0
  - freetype=2.9.1=h8a8886c_1
  - glib=2.56.2=hd408876_0
  - gmp=6.1.2=h6c8ec71_1
  - gst-plugins-base=1.14.0=hbbd80ab_1
  - gstreamer=1.14.0=hb453b48_1
  - hdf4=4.2.13=h3ca952b_2
  - hdf5=1.10.4=hb1b8bf9_0
  - icu=58.2=h9c2bf20_1
  - intel-openmp=2019.1=144
  - ipykernel=5.1.0=py37h39e3cac_0
  - ipython=7.2.0=py37h39e3cac_0
  - ipython_genutils=0.2.0=py37_0
  - ipywidgets=7.4.2=py37_0
  - jedi=0.13.2=py37_0
  - jinja2=2.10=py37_0
  - jpeg=9b=h024ee3a_2
  - jsonschema=2.6.0=py37_0
  - jupyter=1.0.0=py37_7
  - jupyter_client=5.2.4=py37_0
  - jupyter_console=6.0.0=py37_0
  - jupyter_core=4.4.0=py37_0
  - kiwisolver=1.0.1=py37hf484d3e_0
  - krb5=1.16.1=h173b8e3_7
  - libcurl=7.63.0=h20c2e04_1000
  - libedit=3.1.20181209=hc058e9b_0
  - libffi=3.2.1=hd88cf55_4
  - libgcc-ng=8.2.0=hdf63c60_1
  - libgfortran-ng=7.3.0=hdf63c60_0
  - libnetcdf=4.6.1=h11d0813_2
  - libpng=1.6.36=hbc83047_0
  - libsodium=1.0.16=h1bed415_0
  - libssh2=1.8.0=h1ba5d50_4
  - libstdcxx-ng=8.2.0=hdf63c60_1
  - libuuid=1.0.3=h1bed415_2
  - libxcb=1.13=h1bed415_1
  - libxml2=2.9.9=he19cac6_0
  - llvmlite=0.27.0=py37hd408876_0
  - markupsafe=1.1.0=py37h7b6447c_0
  - matplotlib=3.0.2=py37h5429711_0
  - mistune=0.8.4=py37h7b6447c_0
  - mkl=2019.1=144
  - mkl_fft=1.0.10=py37ha843d7b_0
  - mkl_random=1.0.2=py37hd81dba3_0
  - nbconvert=5.3.1=py37_0
  - nbformat=4.4.0=py37_0
  - nccl=1.3.5=cuda10.0_0
  - ncurses=6.1=he6710b0_1
  - netcdf4=1.4.2=py37h808af73_0
  - notebook=5.7.4=py37_0
  - numba=0.42.0=py37h962f231_0
  - numpy=1.15.4=py37h7e9f1db_0
  - numpy-base=1.15.4=py37hde5b4d6_0
  - openssl=1.1.1a=h7b6447c_0
  - pandas=0.24.0=py37he6710b0_0
  - pandoc=2.2.3.2=0
  - pandocfilters=1.4.2=py37_1
  - parso=0.3.1=py37_0
  - pcre=8.42=h439df22_0
  - pexpect=4.6.0=py37_0
  - pickleshare=0.7.5=py37_0
  - pip=18.1=py37_0
  - prometheus_client=0.5.0=py37_0
  - prompt_toolkit=2.0.7=py37_0
  - ptyprocess=0.6.0=py37_0
  - pygments=2.3.1=py37_0
  - pyparsing=2.3.1=py37_0
  - pyqt=5.9.2=py37h05f1152_2
  - python=3.7.2=h0371630_0
  - python-dateutil=2.7.5=py37_0
  - pytz=2018.9=py37_0
  - pyzmq=17.1.2=py37h14c3975_0
  - qt=5.9.7=h5867ecd_1
  - qtconsole=4.4.3=py37_0
  - readline=7.0=h7b6447c_5
  - send2trash=1.5.0=py37_0
  - setuptools=40.6.3=py37_0
  - sip=4.19.8=py37hf484d3e_0
  - six=1.12.0=py37_0
  - sqlite=3.26.0=h7b6447c_0
  - terminado=0.8.1=py37_1
  - testpath=0.4.2=py37_0
  - tk=8.6.8=hbc83047_0
  - tornado=5.1.1=py37h7b6447c_0
  - traitlets=4.3.2=py37_0
  - wcwidth=0.1.7=py37_0
  - webencodings=0.5.1=py37_1
  - wheel=0.32.3=py37_0
  - widgetsnbextension=3.4.2=py37_0
  - xz=5.2.4=h14c3975_4
  - zeromq=4.2.5=hf484d3e_1
  - zlib=1.2.11=h7b6447c_3
  - icc_rt=2018.0.2=0
```

> I was also thinking about putting in a feature request for Numba to give a better error message at runtime. Maybe I'm wrong, but I think it shouldbe detectable when registers*thread_block_size > 65,000 or whatever the registers per SM of the cuda device in use has. It would be nice to even make that an accessible variable e.g. function.register_count so the user could dynamically set the number of threads at runtime. I see there is already a request to simplyfy access to the ptax text information to access these register counts. Would that be useful to request?

Yes, please open an issue as a feature request, it seems like a method to at least access this information should be made more prominent. I think this:
```python
cudajittedfunc._func.get().attrs
```
probably does some of what you want?
You are most correct that appears to be the solution.  That also answered the other feature request I saw:
https://github.com/numba/numba/issues/3482

Stuart,  I'm wondering how one is supposed to find documentation on methods like you just pointed me to!  I searched for _func.get in the numba usere and reference docs and did not find it.  I did find the phrase in a search of the code on git itself but not any documentation.

If you have any pointers for me on how to know this sort of thing I'd love to learn so I don't ask any stupid questions.



> On Feb 22, 2019, at 10:37 AM, stuartarchibald <notifications@github.com> wrote:
> 
> Yes, please open an issue as a feature request, it seems like a method to at least access this information should be made more prominent. I think this:
> 
> cudajittedfunc._func.get().attrs
> probably does some of what you want?
> 


> Stuart, I'm wondering how one is supposed to find documentation on methods like you just pointed me to! I searched for _func.get in the numba usere and reference docs and did not find it. I did find the phrase in a search of the code on git itself but not any documentation.

Well, this is an `underscore` method so by convention it is considered internal/private and as a result it is not publicly documented. Seems like you are the first person to ask for this information presented via an API!

> If you have any pointers for me on how to know this sort of thing I'd love to learn so I don't ask any stupid questions.

You really weren't to know, I just happen to know roughly where to look as an engineer familiar with the internals of the compiler. Exploring the docs and pointing out deficiencies/inconsistencies with the code base is really helpful, so thanks!

I just filed a feature request for this.
https://github.com/numba/numba/issues/3798

And a very related wish is for some way to visualize when a line of code requires registers and how many it needs.  A nice place for that would be the .inspect_types numba IR output.  It always seems to be the case that code uses far more registers than one expects. and by that I mean factors of 10 in some cases.    So seeing where they are going and thus being able to beat these down one could get occupancy up.


> On Feb 22, 2019, at 11:00 AM, stuartarchibald <notifications@github.com> wrote:
> 
> Stuart, I'm wondering how one is supposed to find documentation on methods like you just pointed me to! I searched for _func.get in the numba usere and reference docs and did not find it. I did find the phrase in a search of the code on git itself but not any documentation.
> 
> Well, this is an underscore method so by convention it is considered internal/private and as a result it is not publicly documented. Seems like you are the first person to ask for this information presented via an API!
> 
> If you have any pointers for me on how to know this sort of thing I'd love to learn so I don't ask any stupid questions.
> 
> You really weren't to know, I just happen to know roughly where to look as an engineer familiar with the internals of the compiler. Exploring the docs and pointing out deficiencies/inconsistencies with the code base is really helpful, so thanks!
> 
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub <https://github.com/numba/numba/issues/3795#issuecomment-466488195>, or mute the thread <https://github.com/notifications/unsubscribe-auth/AKII_pK-Qtz-w18heNtLL8fqD-5roR-lks5vQDAygaJpZM4bJ_42>.
> 



Resolving patch:
diff --git a/numba/cuda/compiler.py b/numba/cuda/compiler.py
--- a/numba/cuda/compiler.py
+++ b/numba/cuda/compiler.py
@@ -593,6 +593,13 @@ def device(self):
         """
         return get_current_device()
 
+    @property
+    def regs_per_thread(self):
+        '''
+        The number of registers used by each thread for this kernel.
+        '''
+        return self._func.get().attrs.regs
+
     def inspect_llvm(self):
         '''
         Returns the LLVM IR for this kernel.
@@ -955,6 +962,26 @@ def _func(self, signature=None, compute_capability=None):
         else:
             return {sig: defn._func for sig, defn in self.definitions.items()}
 
+    def get_regs_per_thread(self, signature=None):
+        '''
+        Returns the number of registers used by each thread in this kernel for
+        the device in the current context.
+
+        :param signature: The signature of the compiled kernel to get register
+                          usage for. This may be omitted for a specialized
+                          kernel.
+        :return: The number of registers used by the compiled variant of the
+                 kernel for the given signature and current device.
+        '''
+        cc = get_current_device().compute_capability
+        if signature is not None:
+            return self.definitions[(cc, signature.args)].regs_per_thread
+        if self.specialized:
+            return self.definition.regs_per_thread
+        else:
+            return {sig: defn.regs_per_thread
+                    for sig, defn in self.definitions.items()}
+
     def compile(self, sig):
         '''
         Compile and bind to the current context a version of this kernel
diff --git a/numba/cuda/decorators.py b/numba/cuda/decorators.py
--- a/numba/cuda/decorators.py
+++ b/numba/cuda/decorators.py
@@ -46,8 +46,10 @@ def jit(func_or_sig=None, device=False, inline=False, link=[], debug=None,
        disables precise division and square root. This parameter has no effect
        on device function, whose fastmath setting depends on the kernel function
        from which they are called.
-    :param max_registers: Limit the kernel to using at most this number of
-       registers per thread. Useful for increasing occupancy.
+    :param max_registers: Request that the kernel is limited to using at most
+       this number of registers per thread. The limit may not be respected if
+       the ABI requires a greater number of registers than that requested.
+       Useful for increasing occupancy.
     :param opt: Whether to compile from LLVM IR to PTX with optimization
                 enabled. When ``True``, ``-opt=3`` is passed to NVVM. When
                 ``False``, ``-opt=0`` is passed to NVVM. Defaults to ``True``.


Test patch:
diff --git a/numba/cuda/tests/cudadrv/test_linker.py b/numba/cuda/tests/cudadrv/test_linker.py
--- a/numba/cuda/tests/cudadrv/test_linker.py
+++ b/numba/cuda/tests/cudadrv/test_linker.py
@@ -101,25 +101,25 @@ def test_set_registers_no_max(self):
         something greater than the maximum."""
         compiled = cuda.jit(func_with_lots_of_registers)
         compiled = compiled.specialize(np.empty(32), *range(6))
-        self.assertGreater(compiled._func.get().attrs.regs, 57)
+        self.assertGreater(compiled.get_regs_per_thread(), 57)
 
     @require_context
     def test_set_registers_57(self):
         compiled = cuda.jit(max_registers=57)(func_with_lots_of_registers)
         compiled = compiled.specialize(np.empty(32), *range(6))
-        self.assertLessEqual(compiled._func.get().attrs.regs, 57)
+        self.assertLessEqual(compiled.get_regs_per_thread(), 57)
 
     @require_context
     def test_set_registers_38(self):
         compiled = cuda.jit(max_registers=38)(func_with_lots_of_registers)
         compiled = compiled.specialize(np.empty(32), *range(6))
-        self.assertLessEqual(compiled._func.get().attrs.regs, 38)
+        self.assertLessEqual(compiled.get_regs_per_thread(), 38)
 
     @require_context
     def test_set_registers_eager(self):
         sig = void(float64[::1], int64, int64, int64, int64, int64, int64)
         compiled = cuda.jit(sig, max_registers=38)(func_with_lots_of_registers)
-        self.assertLessEqual(compiled._func.get().attrs.regs, 38)
+        self.assertLessEqual(compiled.get_regs_per_thread(), 38)
 
 
 if __name__ == '__main__':
diff --git a/numba/cuda/tests/cudapy/test_dispatcher.py b/numba/cuda/tests/cudapy/test_dispatcher.py
--- a/numba/cuda/tests/cudapy/test_dispatcher.py
+++ b/numba/cuda/tests/cudapy/test_dispatcher.py
@@ -1,5 +1,7 @@
-from numba import cuda, float32, int32
+from numba import cuda, float32, float64, int32, int64, void
 from numba.cuda.testing import skip_on_cudasim, unittest, CUDATestCase
+import numpy as np
+import math
 
 
 @skip_on_cudasim('Dispatcher objects not used in the simulator')
@@ -93,6 +95,68 @@ def f(x, y):
         self.assertEqual(len(f.specializations), 2)
         self.assertIs(f_f32c_f32c, f_f32c_f32c_2)
 
+    def test_get_regs_per_thread_unspecialized(self):
+        # A kernel where the register usage per thread is likely to differ
+        # between different specializations
+        @cuda.jit
+        def pi_sin_array(x, n):
+            i = cuda.grid(1)
+            if i < n:
+                x[i] = 3.14 * math.sin(x[i])
+
+        # Call the kernel with different arguments to create two different
+        # definitions within the Dispatcher object
+        N = 10
+        arr_f32 = np.zeros(N, dtype=np.float32)
+        arr_f64 = np.zeros(N, dtype=np.float64)
+
+        pi_sin_array[1, N](arr_f32, N)
+        pi_sin_array[1, N](arr_f64, N)
+
+        # Check we get a positive integer for the two different variations
+        sig_f32 = void(float32[::1], int64)
+        sig_f64 = void(float64[::1], int64)
+        regs_per_thread_f32 = pi_sin_array.get_regs_per_thread(sig_f32)
+        regs_per_thread_f64 = pi_sin_array.get_regs_per_thread(sig_f64)
+
+        self.assertIsInstance(regs_per_thread_f32, int)
+        self.assertIsInstance(regs_per_thread_f64, int)
+
+        self.assertGreater(regs_per_thread_f32, 0)
+        self.assertGreater(regs_per_thread_f64, 0)
+
+        # Check that getting the registers per thread for all signatures
+        # provides the same values as getting the registers per thread for
+        # individual signatures. Note that the returned dict is indexed by
+        # (cc, argtypes) pairs (in keeping with definitions, ptx, LLVM IR,
+        # etc.)
+        regs_per_thread_all = pi_sin_array.get_regs_per_thread()
+        cc = cuda.current_context().device.compute_capability
+        self.assertEqual(regs_per_thread_all[cc, sig_f32.args],
+                         regs_per_thread_f32)
+        self.assertEqual(regs_per_thread_all[cc, sig_f64.args],
+                         regs_per_thread_f64)
+
+        if regs_per_thread_f32 == regs_per_thread_f64:
+            # If the register usage is the same for both variants, there may be
+            # a bug, but this may also be an artifact of the compiler / driver
+            # / device combination, so produce an informational message only.
+            print('f32 and f64 variant thread usages are equal.')
+            print('This may warrant some investigation. Devices:')
+            cuda.detect()
+
+    def test_get_regs_per_thread_specialized(self):
+        @cuda.jit(void(float32[::1], int64))
+        def pi_sin_array(x, n):
+            i = cuda.grid(1)
+            if i < n:
+                x[i] = 3.14 * math.sin(x[i])
+
+        # Check we get a positive integer for the specialized variation
+        regs_per_thread = pi_sin_array.get_regs_per_thread()
+        self.assertIsInstance(regs_per_thread, int)
+        self.assertGreater(regs_per_thread, 0)
+
 
 if __name__ == '__main__':
     unittest.main()