whatwg / webidl

Web IDL Standard
https://webidl.spec.whatwg.org/
Other
387 stars 160 forks source link

Intent to use BigInt/numeric union in WebNN #1388

Open fdwr opened 5 months ago

fdwr commented 5 months ago

What is the issue with the Web IDL Standard?

Opening an issue per the wording here: https://webidl.spec.whatwg.org/#limit-bigint-numeric-unions

It would not be appropriate to accept such a union, only to then convert values of the numeric type to a bigint for further processing, as this runs the risk of introducing precision errors. Please file an issue before using this feature.

We have 3 use cases in WebNN where we need to be able to accept full precision from the API and retain it passed all the way down to the bottom, because float64 cannot express the full precision of int64 (and conversely, an int64 could not express the full precision of a float64).

Evidently it's murky territory, as Bin Miao hit a bug in the Chromium implementation "We met the problem of using the keyword or in IDL to create a union type containing double and bigint. In the generated file 'v8_union_bigint_double.cc', it was only judged that the incoming data was of JS number type and there is no judgment that it is BigInt, which causes an error to be reported when BigInt data is passed in when calling this API using JS code.", and Joshua notes that "Use of bigint is extremely limited in Chromium at the moment, so I'm not surprised if the binding code generation can't handle a union with it yet ... this may just be the first spec".

FYI @inexorabletash, @annevk, @honry, @miaobin

annevk commented 4 months ago

Reading those three issues it seems like upgrading to double or long long would get you most of the way? With JS you should have ~int53 or some such, which is way more than what you are clamping at currently. It doesn't give you precision beyond that, but it's not immediately clear from the issues whether that's needed.

fdwr commented 4 months ago

Howdy Anne. We're looking at a union of (double and BigInt), where double (float64) is large enough to handle any floating point constant (float16, float32, float64), and BigInt is large enough to handle any integer constant (int8, uint8, int32, uint32, int64, uint64). Are you suggesting a union of (double or long long)?

Using only double gets us most of the way, yes, but it's the endpoints that concern me. We want JS to be able to accurately map the full range of int64/uint64 tensors (e.g. numpy.uint64, tensorflow.uint64, ONNX.TensorProto.DataType.UINT64, BNNSDataTypeUInt64, MPSDataType.uInt64, mx.uint64, ov::element::Type_t::u64...), and yeah, most values past 2^52 likely don't need exact precision, but for cases like sentinel values and masks (e.g. 0xFFFFFFFFFFFFFFFF), we'd have a challenge representing them via a double. You would think that passing a really large double like 1.8446744073709552e+19 might map cleanly to 0xFFFFFFFFFFFFFFFF, but you get varying results depending on the FPU logic. Even with C++ on my x64 machine, if you attempt to static_cast a double like 1.8446744073709552e+19 to uint64_t, you get back a surprising answer of 0x8000000000000000 instead of 0xFFFFFFFFFFFFFFFF, and I don't know what happens on ARM/M1.

For long long (https://webidl.spec.whatwg.org/#idl-long-long), is BigInt the way to express int64/uint64 via JS? Thanks.

annevk commented 4 months ago

No, neither double nor long long would give you int64 precision. It seems really strange to overload a double with an integer though. Seems like it should be (long long or BigInt) then.

fdwr commented 4 months ago

Seems like it should be (long long or BigInt) then.

The WebNN user calls a function to apply an operation on a multidimensional tensor (e.g. fill a constant, fill a sequence, pad with a constant value) or to create a multidimensional tensor, where those tensors can be of various data types (including float64/int64/uint64/...), and the caller passes a scalar parameter to that function call which should be able to represent any of the target tensor's potential values. Using BigInt or long long would just introduce the opposite problem, as integer types cannot express float64 point precision (3.4 would be truncated to 3).

The current definition @miaobin has prototyped for a constant sequence fill is:

MLOperand constant(MLOperandDescriptor desc, (double or bigint) start, (double or bigint) step);
annevk commented 4 months ago

Sorry for being slow, that seems reasonable.

fdwr commented 4 months ago

FYI @inexorabletash, Anne says it seems reasonable :).