Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up and fix numpy_helper and subbyte #6124

Open
wants to merge 21 commits into
base: main
Choose a base branch
from

Conversation

justinchuby
Copy link
Contributor

@justinchuby justinchuby commented May 3, 2024

  1. complex number handling in numpy_helper
    Otherwise the line

    return np.asarray(data, dtype=storage_np_dtype).astype(np_dtype).reshape(dims)

    raises TypeError: float() argument must be a string or a real number, not 'complex', because storage_np_dtype is float but data is complex already.

  2. Vectorize float8 conversion functions and improve readability: Speed up float8e4m3_to_float32 by 10.3x (1000x1000 input, 10 iterations, 34.829s -> 3.11s)

  3. Clean up int4 numpy helpers to make them more useful and performant with np native vectorization. Move all int4 related functions to the subbyte module.

  4. Improve handling of big-endian systems

  5. Remove the dims parameter in numpy helper functions to simplify the implementation.

  6. Improve reference evaluator to_array_extended

@galagam for int4 updates, @AlexandreEichenberger for big-endian handling @xadupre for float8 functions and reference evaluator. Thanks!

Float 8 util speed test

from onnx import numpy_helper
import numpy as np
import pyinstrument
import onnx
floats = np.random.randint(0, 255, [1000, 1000], dtype=np.uint8)

print(onnx.__version__)
profiler = pyinstrument.Profiler()
profiler.start()
for i in range(10):
    print(i)
    numpy_helper.float8e4m3_to_float32(floats)
profiler.stop()
profiler.print()

TODO: Unit tests

Fixes #6126

@justinchuby justinchuby requested a review from a team as a code owner May 3, 2024 02:37
Copy link

github-actions bot commented May 3, 2024

Test Results

     3 files  ±0       3 suites  ±0   2m 15s ⏱️ +8s
 7 486 tests ±0   4 386 ✅  -  70  3 030 💤 ± 0   70 ❌ + 70 
22 441 runs  +5  13 051 ✅  - 135  9 180 💤  - 70  210 ❌ +210 

For more details on these failures, see this check.

Results for commit 64be57c. ± Comparison against base commit 013eb5e.

♻️ This comment has been updated with latest results.

@justinchuby justinchuby changed the title Fix numpy_helper to_array when tensor is complex Fix numpy_helper to_array errors May 3, 2024
@justinchuby justinchuby added this to the 1.17 milestone May 3, 2024
@xadupre
Copy link
Contributor

xadupre commented May 3, 2024

Maybe it is worth adding a unit test.

@justinchuby justinchuby marked this pull request as draft May 3, 2024 14:53
@justinchuby justinchuby changed the title Fix numpy_helper to_array errors Clean up numpy_helper and subbyte May 4, 2024
@justinchuby justinchuby marked this pull request as ready for review May 4, 2024 02:03
@justinchuby justinchuby force-pushed the justinchu/complex-numpy branch 2 times, most recently from 9ba8ff1 to b688548 Compare May 4, 2024 02:07
@justinchuby justinchuby added the better engineering Improve engineering quality of the project label May 4, 2024
third_party/benchmark Outdated Show resolved Hide resolved
@justinchuby justinchuby changed the title Clean up numpy_helper and subbyte Clean up and fix numpy_helper and subbyte May 4, 2024
"""
single_func = lambda x: subbyte.unpack_single_4bitx2(x, signed) # noqa: E731
func = np.frompyfunc(single_func, 1, 2)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

frompyfunc is not performant

onnx/numpy_helper.py Outdated Show resolved Hide resolved
onnx/numpy_helper.py Outdated Show resolved Hide resolved
onnx/numpy_helper.py Fixed Show fixed Hide fixed
onnx/numpy_helper.py Fixed Show fixed Hide fixed
onnx/numpy_helper.py Fixed Show fixed Hide fixed
if tensor_dtype in (TensorProto.COMPLEX64, TensorProto.COMPLEX128):
data = combine_pairs_to_complex(data) # type: ignore[assignment,arg-type]
if tensor_dtype in (onnx.TensorProto.COMPLEX64, onnx.TensorProto.COMPLEX128):
return np.asarray(combine_pairs_to_complex(data)).astype(np_dtype).reshape(dims)

Check failure

Code scanning / lintrunner

MYPY/arg-type Error

Argument 1 to "asarray" has incompatible type "list[complex]"; expected "_SupportsArray[dtype[Never]] | _NestedSequence[_SupportsArray[dtype[Never]]]" To disable, use # type: ignore[arg-type]
if tensor_dtype in (TensorProto.COMPLEX64, TensorProto.COMPLEX128):
data = combine_pairs_to_complex(data) # type: ignore[assignment,arg-type]
if tensor_dtype in (onnx.TensorProto.COMPLEX64, onnx.TensorProto.COMPLEX128):
return np.asarray(combine_pairs_to_complex(data)).astype(np_dtype).reshape(dims)

Check failure

Code scanning / lintrunner

MYPY/arg-type Error

Argument 1 to "combine_pairs_to_complex" has incompatible type "ndarray[Any, dtype[Any]]"; expected "Sequence[int]" To disable, use # type: ignore[arg-type]
clip_high = INT4_MAX if signed else UINT4_MAX
if not isinstance(x, np.ndarray):
x = np.asarray(x)
return np.rint(np.clip(x, INT4_MIN, INT4_MAX)).astype(np.int8)

Check failure

Code scanning / lintrunner

MYPY/no-any-return Error

Returning Any from function declared to return "ndarray[Any, dtype[signedinteger[_8Bit]]] | signedinteger[_8Bit]" To disable, use # type: ignore[no-any-return]
Returns:
An ndarray with a single int4 element.
"""
return np.rint(np.clip(x, UINT4_MIN, UINT4_MAX)).astype(np.uint8)

Check failure

Code scanning / lintrunner

MYPY/no-any-return Error

Returning Any from function declared to return "ndarray[Any, dtype[unsignedinteger[_8Bit]]] | unsignedinteger[_8Bit]" To disable, use # type: ignore[no-any-return]
else:
i8_low = cast_uint4(val_low)
i8_high = cast_uint4(val_high)
i8_high <<= 4

Check failure

Code scanning / lintrunner

MYPY/assignment Error

Incompatible types in assignment (expression has type "ndarray[Any, dtype[signedinteger[Any]]] | signedinteger[Any]", variable has type "ndarray[Any, dtype[unsignedinteger[_8Bit]]] | unsignedinteger[_8Bit]") To disable, use # type: ignore[assignment]
x_low = x & np.uint8(0x0F)
x_high = (x >> 4).astype(np.uint8)
if signed:
x_low = _int4_to_int8(x_low)

Check failure

Code scanning / lintrunner

MYPY/assignment Error

Incompatible types in assignment (expression has type "ndarray[Any, dtype[signedinteger[_8Bit]]]", variable has type "ndarray[Any, dtype[unsignedinteger[Any]]]") To disable, use # type: ignore[assignment]
x_high = (x >> 4).astype(np.uint8)
if signed:
x_low = _int4_to_int8(x_low)
x_high = _int4_to_int8(x_high)

Check failure

Code scanning / lintrunner

MYPY/assignment Error

Incompatible types in assignment (expression has type "ndarray[Any, dtype[signedinteger[_8Bit]]]", variable has type "ndarray[Any, dtype[unsignedinteger[_8Bit]]]") To disable, use # type: ignore[assignment]
Comment on lines +103 to +121
# if mantissa > 0:
# exponent = 127 - exponent_bias
# if mantissa & 0b100 == 0:
# mantissa &= 0b011
# mantissa <<= 1
# exponent -= 1
# if mantissa & 0b100 == 0:
# mantissa &= 0b011
# mantissa <<= 1
# exponent -= 1
# result |= (mantissa & 0b011) << 21
# result |= exponent << 23

Check notice

Code scanning / CodeQL

Commented-out code Note

This comment appears to contain commented-out code.
@justinchuby justinchuby marked this pull request as draft May 4, 2024 04:29
onnx/numpy_helper.py Fixed Show fixed Hide fixed
onnx/numpy_helper.py Fixed Show fixed Hide fixed
@justinchuby justinchuby marked this pull request as ready for review May 4, 2024 05:25
return f


_float8e4m3_to_float32 = np.vectorize(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed use of vectorize because it is a for loop and is not performant

result[normal_mask] |= exponents[normal_mask] << 23
result = result.view(np.float32)
if is_scalar:
return result[0]

Check failure

Code scanning / lintrunner

MYPY/no-any-return Error

Returning Any from function declared to return "ndarray[Any, dtype[floating[_32Bit]]]" To disable, use # type: ignore[no-any-return]
result = result.view(np.float32)
if is_scalar:
return result[0]
return result

Check failure

Code scanning / lintrunner

MYPY/return-value Error

Incompatible return value type (got "ndarray[Any, dtype[unsignedinteger[_32Bit]]]", expected "ndarray[Any, dtype[floating[_32Bit]]]") To disable, use # type: ignore[return-value]
result[normal_mask] |= exponents[normal_mask] << 23
result = result.view(np.float32)
if is_scalar:
return result[0]

Check failure

Code scanning / lintrunner

MYPY/no-any-return Error

Returning Any from function declared to return "ndarray[Any, dtype[floating[_32Bit]]]" To disable, use # type: ignore[no-any-return]
result = result.view(np.float32)
if is_scalar:
return result[0]
return result

Check failure

Code scanning / lintrunner

MYPY/return-value Error

Incompatible return value type (got "ndarray[Any, dtype[unsignedinteger[_32Bit]]]", expected "ndarray[Any, dtype[floating[_32Bit]]]") To disable, use # type: ignore[return-value]
Comment on lines +229 to +238
# if exponent == 0:
# # Subnormal number
# if mantissa > 0:
# exponent = 127 - exponent_bias
# if mantissa & 0b10 == 0:
# mantissa &= 0b01
# mantissa <<= 1
# exponent -= 1
# result |= (mantissa & 0b01) << 22
# result |= exponent << 23

Check notice

Code scanning / CodeQL

Commented-out code Note

This comment appears to contain commented-out code.
justinchuby and others added 3 commits May 4, 2024 15:37
Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
justinchuby and others added 14 commits May 4, 2024 15:37
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
y[i] = d
return y.reshape(shape)
data = np.array(tensor.int32_data, dtype=np.uint8)
data = data.view(dtype_mapping[elem_type])

Check failure

Code scanning / lintrunner

MYPY/arg-type Error

Argument 1 to "view" of "ndarray" has incompatible type "dtype[void]"; expected "dtype[unsignedinteger[_16Bit]] | type[unsignedinteger[_16Bit]] | _SupportsDType[dtype[unsignedinteger[_16Bit]]]" To disable, use # type: ignore[arg-type]
y[i] = d
return y.reshape(shape)
data = np.array(tensor.int32_data, dtype=np.uint8)
data = data.view(dtype_mapping[elem_type])

Check failure

Code scanning / lintrunner

MYPY/index Error

Invalid index type "int" for "dict[DataType, dtype[void]]"; expected type "DataType" To disable, use # type: ignore[index]
for i, d in enumerate(data):
y[i] = d
dtype_mapping = {TensorProto.INT4: int4, TensorProto.UINT4: uint4}
dtype = dtype_mapping[elem_type]

Check failure

Code scanning / lintrunner

MYPY/index Error

Invalid index type "int" for "dict[DataType, dtype[void]]"; expected type "DataType" To disable, use # type: ignore[index]
y[i] = d
dtype_mapping = {TensorProto.INT4: int4, TensorProto.UINT4: uint4}
dtype = dtype_mapping[elem_type]
return subbyte.unpack_int4(data, dims=tensor.dims, signed=signed).view(dtype)

Check failure

Code scanning / lintrunner

MYPY/arg-type Error

Argument 1 to "unpack_int4" has incompatible type "ndarray[Any, dtype[unsignedinteger[_16Bit]]]"; expected "ndarray[Any, dtype[unsignedinteger[_8Bit]]]" To disable, use # type: ignore[arg-type]
@justinchuby justinchuby requested a review from a team as a code owner May 4, 2024 16:01
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
@justinchuby justinchuby marked this pull request as draft May 4, 2024 16:22
@justinchuby justinchuby marked this pull request as ready for review May 4, 2024 16:26
return result


def _small_endian_dtype(dtype) -> np.dtype:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def _small_endian_dtype(dtype) -> np.dtype:
def _little_endian_dtype(dtype) -> np.dtype:

Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
@justinchuby
Copy link
Contributor Author

For float8 usage, we may be better of using https://github.com/jax-ml/ml_dtypes?

return shift(data.astype(np.int32)).reshape(dims).view(np.float32) # type: ignore[no-any-return]


def _float8e4m3_to_float32_scalar(ival: int, fn: bool, uz: bool) -> np.float32:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep the code of the old function in the documentation. The logic is easier to read so that the new code can be more easily understood.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
better engineering Improve engineering quality of the project
Projects
Status: In progress
Development

Successfully merging this pull request may close these issues.

Fix and test numpy_helper to_array
2 participants