<div dir="ltr"><div dir="ltr"><br></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Tue, Jan 26, 2021 at 2:01 AM Sebastian Berg <<a href="mailto:sebastian@sipsolutions.net">sebastian@sipsolutions.net</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Hi all,<br>
<br>
does anyone have a thought about how user DTypes (i.e. DTypes not<br>
currently part of NumPy) should interact with the "value based<br>
promotion" logic we currently have?<br>
For now I can just do anything, and we will find out later. And I will<br>
have to do something for now, basically with the hope that it all turns<br>
out all-right.<br>
<br>
But there are multiple options for both what to offer to user DTypes<br>
and where we want to move (I am using `bfloat16` as a potential DType<br>
here).<br>
<br>
1. The "weak" dtype option (this is what JAX does), where:<br>
<br>
np.array([1], dtype=bfloat16) + 4.<br>
<br>
returns a bfloat16, because 4. is "lower" than all floating<br>
point types.<br>
In this scheme the user defined `bfloat16` knows that the input<br>
is a Python float, but it does not know its value (if an<br>
overflow occurs during conversion, it could warn or error but<br>
not upcast). For example `np.array([1], dtype=uint4) + 2**5`<br>
will try `uint4(2**5)` assuming it works.<br>
NumPy is different `2.**300` would ensure the result is a `float64`.<br>
<br>
If a DType does not make use of this, it would get the behaviour<br>
of option 2.<br>
<br>
2. The "default" DType option: np.array([1], dtype=bfloat16) + 4. is<br>
always the same as `bfloat16 + float64 -> float64`.<br>
<br>
3. Use whatever NumPy considers the "smallest appropriate dtype".<br>
This will not always work correctly for unsigned integers, and for<br>
floats this would be float16, which doesn't help with bfloat16.<br>
<br>
4. Try to expose the actual value. (I do not want to do this, but it<br>
is probably a plausible extension with most other options, since<br>
the other options can be the "default".)<br>
<br>
<br>
Within these options, there is one more difficulty. NumPy currently<br>
applies the same logic for:<br>
<br>
np.array([1], dtype=bfloat16) + np.array(4., dtype=np.float64)<br>
<br>
which in my opinion is wrong (the second array is typed). We do have<br>
the same issue with deciding what to do in the future for NumPy itself.<br>
Right now I feel that new (user) DTypes should live in the future<br>
(whatever that future is).<br></blockquote><div><br></div><div>I agree. And I have a preference for option 1. Option 2 is too greedy in upcasting, the value-based casting is problematic in multiple ways (e.g., hard for Numba because output dtype cannot be predicted from input dtypes), and option 4 is hard to understand a rationale for (maybe so the user dtype itself can implement option 3?). <br></div><div> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
I have said previously, that we could distinguish this for universal<br>
functions. But calls like `np.asarray(4.)` are common, and they would<br>
lose the information that `4.` was originally a Python float.<br></blockquote><div><br></div><div>Hopefully the future will have way fewer asarray calls in it. Rejecting scalar input to functions would be nice. This is what most other array/tensor libraries do.<br></div><div> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
<br>
So, recently, I was considering that a better option may be to limit<br>
this to math Python operators: +, -, /, **, ...<br></blockquote><div><br></div><div>+1</div><div><br></div><div>This discussion may be relevant: <a href="https://github.com/data-apis/array-api/issues/14">https://github.com/data-apis/array-api/issues/14</a>.</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
Those are the places where it may make a difference to write:<br>
<br>
arr + 4. vs. arr + bfloat16(4.)<br>
int8_arr + 1 vs. int8_arr + np.int8(1)<br>
arr += 4. (in-place may be the most significant use-case)<br>
<br>
while:<br>
<br>
np.add(int8_arr, 1) vs. np.add(int8_arr, np.int8(1))<br>
<br>
is maybe less significant. On the other hand, it would add a subtle<br>
difference between operators vs. direct ufunc calls...<br>
<br>
<br>
In general, it may not matter: We can choose option 1 (which the<br>
bfloat16 does not have to use), and modify it if we ever change the<br>
logic in NumPy itself. Basically, I will probably pick option 1 for<br>
now and press on, and we can reconsider later. And hope that it does<br>
not make things even more complicated than it is now.<br>
<br>
Or maybe better just limit it completely to always use the default for<br>
user DTypes?<br></blockquote><div><br></div><div>I'm not sure I understand why you like option 1 but want to give user-defined dtypes the choice of opting out of it. Upcasting will rarely make sense for user-defined dtypes anyway. <br></div><div> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
<br>
But I would be interested if the "limit to Python operators" is<br>
something we should aim for here. This does make a small difference,<br>
because user DTypes could "live" in the future if we have an idea of<br>
how that future may look like.<br></blockquote><div><br></div><div>A future with:</div><div>- no array scalars</div><div>- 0-D arrays have the same casting rules as >=1-D arrays</div><div>- no value-based casting</div><div>would be quite nice. For "same kind" casting like <a href="https://data-apis.github.io/array-api/latest/API_specification/type_promotion.html">https://data-apis.github.io/array-api/latest/API_specification/type_promotion.html</a>. Mixed-kind casting isn't specified there, because it's too different between libraries. The JAX design (<a href="https://jax.readthedocs.io/en/latest/type_promotion.html">https://jax.readthedocs.io/en/latest/type_promotion.html</a>) seems sensible there.</div><div><br></div><div>Cheers,<br></div><div>Ralf</div><div><br></div></div></div>