# AOT vs llvmlite.dll

**URL:** <https://numba.discourse.group/t/aot-vs-llvmlite-dll/2834>\
**Category:** Support: How do I do ...?\
**Created:** [December 7, 2024, 8:48am UTC](https://numba.discourse.group/t/aot-vs-llvmlite-dll/2834 "2024-12-07T08:48:12Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![nb113](https://avatars.discourse-cdn.com/v4/letter/n/df788c/32.png) [@nb113](https://numba.discourse.group/u/nb113)\
**Post date:** [December 7, 2024, 8:48am UTC](https://numba.discourse.group/t/aot-vs-llvmlite-dll/2834/1 "2024-12-07T08:48:12Z")

</div>

Hi,  
I have converted a few functions successfully to `@njit`, it took some finessing, but everything works.  
Problem is that when building the `py` app and bundling into a single executable file, the `llvmlite.dll` needed to run `@njit` functions is a massive dependency of `76.x MB` - the app w/o `numba` was only `20 MB`.  
Is there any way to reduce the size of `llvmlite.dll`?

I then looked at `AOT` compilation, but it’s unclear what the future of that is in the numba ecosystem… (?)  
Also, how much slower will that be on the user’s machine given that the machine code is not optimized like `@njit` w/ `llvmlite`…?  
Last, and this was a red flag:  
An `AOT` compiled function was called (by mistake, in testing) w/ an integer of `2` instead of a float `2.0` (the fn sig specifies float) - the `AOT` fn did **not throw an error** , but it now returned an **incorrect result** - this is scary.  
How can this be prevented realistically in larger numba code bases where `AOT` fns call other `AOT` functions w/ dynamic variables?

Here is the code to reproduce this:

> numba 0.60.0  
> Win 10 x64  
> py 3.10

```auto
from numba.pycc import CC
import math

cc = CC('test')

@cc.export('ded', 'float64(float64[:], float64[:])')
def ded(p1, p2):
    return math.sqrt(((p1[0] - p2[0]) **2) + ((p1[1] - p2[1])** 2) + ((p1[2] - p2[2]) ** 2))

cc.compile()

```

run the module fn:

```auto
_r = np.array([0.5632, 0.78963, 0.01238], dtype=np.float64)

print(test.ded(_r, np.array([2, 2, 2], dtype=np.float64))) # <---- returns 2.7349630, correct
print(test.ded(_r, np.array([2.0, 2.0, 2.0]))) # <---- returns 2.7349630, correct
print(test.ded(_r, np.array([2, 2, 2]))) # <---- returns 0.96998094, incorrect
print(test.ded(_r, [2, 2, 2])) # <---- returns None/null/void !?

```

how to prevent that the same numeric value of `2` (as float|int) returns two different results?

Thanks.

---

<div class="post-metadata">

**Author:** ![Oyibo](https://avatars.discourse-cdn.com/v4/letter/o/4af34b/32.png) [@Oyibo](https://numba.discourse.group/u/Oyibo)\
**Post date:** [December 7, 2024, 2:44pm UTC](https://numba.discourse.group/t/aot-vs-llvmlite-dll/2834/2 "2024-12-07T14:44:24Z")

</div>

Hey @nb113 ,

Unfortunately, I have no solution regarding the dependency issue of llvmlite.

The current process of AOT compilation in Numba has some limitations.  
AOT in Numba lacks type checking, so passing incorrect types (e.g., int instead of float) can lead to incorrect results without errors.  
[https://numba.readthedocs.io/en/stable/user/pycc.html#limitations](https://numba.readthedocs.io/en/stable/user/pycc.html#limitations)  
You could preprocess your inputs to ensure correct types or specify all necessary signatures.

---

<div class="post-metadata">

**Author:** ![nb113](https://avatars.discourse-cdn.com/v4/letter/n/df788c/32.png) [@nb113](https://numba.discourse.group/u/nb113)\
**Post date:** [December 7, 2024, 7:51pm UTC](https://numba.discourse.group/t/aot-vs-llvmlite-dll/2834/3 "2024-12-07T19:51:25Z")

</div>

Hi Oyibo,

thanks for the response!

> You could preprocess your inputs to ensure correct types or specify all necessary signatures

could you elaborate on this?  
the AOT functions require a signature, which I did specify in my example, did you mean to add another signature for `int` input?  
but this would then require a different fn name, and that would mean to change the fn calling logic based on that…

re the preprocessing of inputs, how would this realistically work? can you share a short example?  
I have `numba` jitted code that amounts to ca. 1,200 lines of various jitted functions and processes calling other jitted functions. Almost all numerical value are `ndarray floats` and defined as such, but numerical accuracy is of the utmost importance in these calculations, so cannot take a chance or risk of any process or fn compromising numerical results.  
The incorrect results above do NOT occur w/ jitted fns.

Thanks!

---

<div class="post-metadata">

**Author:** ![Oyibo](https://avatars.discourse-cdn.com/v4/letter/o/4af34b/32.png) [@Oyibo](https://numba.discourse.group/u/Oyibo)\
**Post date:** [December 7, 2024, 9:46pm UTC](https://numba.discourse.group/t/aot-vs-llvmlite-dll/2834/4 "2024-12-07T21:46:50Z")

</div>

@nb113 jitted functions can automatically expand their scope at runtime. The njit decorator is capable to handle a wide variety of input types and automatically generates corresponding signatures.  
You can inspect the generated signatures for your examples:

```auto
import math
import numpy as np
from numba import njit

@njit
def ded(p1, p2):
    return math.sqrt(((p1[0] - p2[0]) **2) + ((p1[1] - p2[1])** 2) + ((p1[2] - p2[2]) ** 2))

_r = np.array([0.5632, 0.78963, 0.01238], dtype=np.float64)

print(ded(_r, np.array([2, 2, 2], dtype=np.float64)))
print(ded(_r, np.array([2.0, 2.0, 2.0])))
print(ded(_r, np.array([2, 2, 2])))
print(ded(_r, [2, 2, 2]))
# 2.734963078599051
# 2.734963078599051
# 2.734963078599051
# 2.734963078599051

```

Numba automatically infers the appropriate and necessary function signatures based on the input types.  
You can inspect the generated signatures:

```auto
print(ded.signatures)
# [(Array(float64, 1, 'C', False, aligned=True), Array(float64, 1, 'C', False, aligned=True)),
# (Array(float64, 1, 'C', False, aligned=True), Array(int64, 1, 'C', False, aligned=True)),
# (Array(float64, 1, 'C', False, aligned=True), List(int64, True))]

```

If you want more control over the signatures in AOT or eager compilation (e.g., to support various combinations of `float64`, `int64`, and `List`), you can manually specify them using a list of signature strings (and function names in AOT).  
But defining all possible combinations can quickly become very impractical:

```auto
@njit([
    'f8(f8[:], f8[:])',
    'f8(f8[:], i8[:])',
    'f8(f8[:], List(f8, True))',
    'f8(f8[:], List(i8, True))',
    'f8(i8[:], f8[:])',
    'f8(i8[:], i8[:])',
    'f8(i8[:], List(f8, True))',
    'f8(i8[:], List(i8, True))',
    'f8(List(f8, True), f8[:])',
    'f8(List(f8, True), i8[:])',
    'f8(List(f8, True), List(f8, True))',
    'f8(List(f8, True), List(i8, True))',
    'f8(List(i8, True), f8[:])',
    'f8(List(i8, True), i8[:])',
    'f8(List(i8, True), List(f8, True))',
    'f8(List(i8, True), List(i8, True))',])
def ded(p1, p2):
    p1 = np.asarray(p1, dtype=np.float64)
    p2 = np.asarray(p2, dtype=np.float64)
    return math.sqrt(((p1[0] - p2[0]) **2) + ((p1[1] - p2[1])** 2) + ((p1[2] - p2[2]) ** 2))

print(ded(_r, np.array([2, 2, 2], dtype=np.float64)))
print(ded(_r, np.array([2.0, 2.0, 2.0])))
print(ded(_r, np.array([2, 2, 2])))
print(ded(_r, [2, 2, 2]))
# 2.734963078599051
# 2.734963078599051
# 2.734963078599051
# 2.734963078599051

```

In AOT compilation, you have to specify input and output types in advance, which can be error-prone if you miss and allow unexpected possible type combinations. A more practical approach is to limit the signature combinations to the most common ones.  
Additionally, with the looming deprecation of this AOT process, it may be more beneficial to rely on jitted functions, which are more user friendly (if that’s possible).

Fyi there is an open issue on Github regarding a similar issue:

> <https://github.com/numba/numba/issues/7725>
>
> We have some functions we want to compile AOT using numba which uses arrays as i…ts input. In some cases users of our code may accidentally use an array of float64 instead of int64 for example. In these cases the numba code appears to continue and does not raise an error so that the results will be wrong. Is there something we should be doing to ensure that the user uses the right types?
> 
> See the minimal example below,
> 
> \`\`\`python
> from numba.pycc import CC
> from numba import complex64, float64, int64
> from numba.core.types.npytypes import Array
> 
> 
> cc = CC('my\_module')
> 
> 
> @cc.export('multc', Array(complex64, 1, "A")(
> Array(complex64, 1, "A"), Array(complex64, 1, "A")
> ))
> @cc.export('multf', Array(float64, 1, "A")(
> Array(float64, 1, "A"), Array(float64, 1, "A")
> ))
> @cc.export('multi', Array(int64, 1, "A")(
> Array(int64, 1, "A"), Array(int64, 1, "A")
> ))
> def mult(a, b):
> return a \* b
> 
> 
> if \_\_name\_\_ == "\_\_main\_\_":
> cc.compile()
> \`\`\`
> 
> \`\`\`python
> \>\>\> import numpy as np
> \>\>\> a = np.array(\[1, 2, 3, 4\], dtype=np.int64)
> \>\>\> b = np.array(\[1.1, 2.2, 3.3, 4.4\], dtype=np.float64)
> \>\>\> c = np.array(\[1.1+1.1j, 2.2+2.2j, 3.3+3.3j, 4.4+4.4j\], dtype=np.complex64)
> \>\>\> import my\_module
> \>\>\> my\_module.multi(a, a)
> array(\[1, 4, 9, 16\])
> \>\>\> my\_module.multi(b, b) # should raise a error
> array(\[-8119809984163909468, -4426858289720102748, 6638485994729205924,
> -733906595276296028\])
> \>\>\> my\_module.multi(c, c) # should raise a error
> array(\[ 2586564337444281385, -4745352150906872791, -8446381298434483159,
> 6369616171939879977\])
> \>\>\> my\_module.multf(a, a) # should raise a error
> array(\[0., 0., 0., 0.\])
> \>\>\> my\_module.multf(b, b)
> array(\[1.21, 4.84, 10.89, 19.36\])
> \>\>\> my\_module.multf(c, c) # should raise a error
> array(\[1.97754000e-04, 1.29600062e+01, 5.89824048e+03, 8.49346967e+05\])
> \>\>\> my\_module.multc(a, a) # should raise a error
> array(\[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j\], dtype=complex64)
> \>\>\> my\_module.multc(b, b) # should raise a error
> array(\[-3.562656 -5.99540536e-23j, -4.1006246-6.43215599e-23j,
> inf+1.17643584e+24j, -5.1756244-7.22624988e-23j\],
> dtype=complex64)
> \>\>\> my\_module.multc(c, c)
> array(\[0. +2.42j , 0. +9.68j , 0.+21.779999j, 0.+38.72j \],
> dtype=complex64)
> \`\`\`

---

<div class="post-metadata">

**Author:** ![nb113](https://avatars.discourse-cdn.com/v4/letter/n/df788c/32.png) [@nb113](https://numba.discourse.group/u/nb113)\
**Post date:** [December 8, 2024, 1:24am UTC](https://numba.discourse.group/t/aot-vs-llvmlite-dll/2834/5 "2024-12-08T01:24:34Z")

</div>

@Oyibo  
thanks so much for the detailed information, it makes sense.

> In AOT compilation, you have to specify input and output types in advance, which can be error-prone if you miss and allow unexpected possible type combinations. A more practical approach is to limit the signature combinations to the most common ones.

okay, I just tried this:

```auto
@cc.export('ded', 'float64(float64[:], float64[:])')
@cc.export('ded', 'float64(float64[:], int64[:])')
@cc.export('ded', 'float64(float64[:], int32[:])')
@cc.export('ded', 'float64(float64[:], List(int32))')
@cc.export('ded', 'float64(float64[:], List(int64))')
def ded(p1, p2):
    return math.sqrt(((float(p1[0]) - float(p2[0])) **2) + ((float(p1[1]) - float(p2[1]))** 2) + ((float(p1[2]) - float(p2[2])) ** 2))

```

I though this would now return correct result for both `int` and `float` input in the 2nd array, but it still returns:

```auto
print(test.ded(_r, np.array([2, 2, 2], dtype=np.float64)))
print(test.ded(_r, np.array([2.0, 2.0, 2.0])))
print(test.ded(_r, np.array([2, 2, 2])))
print(test.ded(_r, [2, 2, 2]))
2.734963078599051
2.734963078599051
0.9699809489366273
None/void

```

what am I missing?

---

<div class="post-metadata">

**Author:** ![Oyibo](https://avatars.discourse-cdn.com/v4/letter/o/4af34b/32.png) [@Oyibo](https://numba.discourse.group/u/Oyibo)\
**Post date:** [December 8, 2024, 1:39am UTC](https://numba.discourse.group/t/aot-vs-llvmlite-dll/2834/6 "2024-12-08T01:39:58Z")

</div>

It’s probably easier to work with function wrappers that make sure, your function input types are correct.  
Can you use asarray to make sure the types are in line with your operation?

```auto
def ded(p1, p2):
    p1 = np.asarray(p1, dtype=np.float64)
    p2 = np.asarray(p2, dtype=np.float64)
    return math.sqrt(((p1[0] - p2[0]) **2) + ((p1[1] - p2[1])** 2) + ((p1[2] - p2[2]) ** 2))

```

Don’t forget: “Each exported function can have only one signature.”

---

<div class="post-metadata">

**Author:** ![nb113](https://avatars.discourse-cdn.com/v4/letter/n/df788c/32.png) [@nb113](https://numba.discourse.group/u/nb113)\
**Post date:** [December 8, 2024, 2:31am UTC](https://numba.discourse.group/t/aot-vs-llvmlite-dll/2834/7 "2024-12-08T02:31:46Z")

</div>

ok, the `ded()` fn from last response when AOT compiled did not change anything, but regarding `function wrappers` I assume I’d use a function in Py to convert data as needed and then call AOT binary fn w/ that input data?  
this would work if the AOT binary fns are called individually from Py, but my code that I want to AOT compile runs a large fn that then repeatedly calls other AOT compiled fns - so the wrapper fn approach makes little sense there but I guess I could force convert all input data always to required data type before calling other AOT fns… if this decreases execution speed then AOT compilation is not worthwhile in my case.

---

<div class="post-metadata">

**Author:** ![Oyibo](https://avatars.discourse-cdn.com/v4/letter/o/4af34b/32.png) [@Oyibo](https://numba.discourse.group/u/Oyibo)\
**Post date:** [December 8, 2024, 2:41am UTC](https://numba.discourse.group/t/aot-vs-llvmlite-dll/2834/8 "2024-12-08T02:41:24Z")

</div>

Just to confirm, have you compiled every function signature with a designated function name (one name per signature)?

```auto
@cc.export('multf', 'f8(f8, f8)')
@cc.export('multi', 'i4(i4, i4)')
def mult(a, b):
    return a * b

```

---

<div class="post-metadata">

**Author:** ![nb113](https://avatars.discourse-cdn.com/v4/letter/n/df788c/32.png) [@nb113](https://numba.discourse.group/u/nb113)\
**Post date:** [December 8, 2024, 7:49pm UTC](https://numba.discourse.group/t/aot-vs-llvmlite-dll/2834/9 "2024-12-08T19:49:16Z")

</div>

> Just to confirm, have you compiled every function signature with a designated function name (one name per signature)?

no, I’ve not b/c that is not usable in my code, as I’d have to dynamically use a different fn name to call for the same basic functionality.  
if `int` happens to sneak for whatever reason, I just need the fn to perform same as it does under `@njit`, most importantly return the correct result - but it seems this is not possible with `numba AOT` approach.  
I’ve now tested this particular bit w/ `Cython` - more hassle to set up - but it handles both `int` and `float` nicely and crashes w/ error if strict types are employed - `numba AOT` crashes silently - not good.  
Problem w/ `Cython` is that it is not as fast as `numba` (in my limited testing) and the whole reason we’re going the machine code route is to get fastest possible speed…
