Repository navigation
Conversation
|
I only skimmed through the commit message, but I think only extending the return type of the compute region doesn't work because the input of the compute region can take larger size too. For example: The schedule can be: |
|
@loganchien Thanks for the feedback. Yes, I can see your point and agree with it. Upon further thinking into this: Here is an high level overview of an option in order to accommodate higher (than
Notes:
Please, let me know your feedback! |
|
Thank you @sdasgup3 for the update.
Does that imply that for Also what do you think about explicitly defining the conversion behavior between input/output type and accumulator type, e.g. "converted by zero-extending storage value"? |
|
Hi @loganchien (upd: sorry I should have referred my reply to refer to @wecing )
Yes, the spec allows that. Let say The specific schedule The open question, that I was pondering about, is how a block, with a fixed static type signature, like any one of 1-4, can be used for any of these schedules. One solution could be use only (2) and update the specification of schedule execution to always up cast the binary tree nodes to accum type before before applying (2).
May be I am not sure about what you meant by "defining the conversion behavior between input/output type and accumulator type". Can you suggest an example? On a related note how about we explicitly zero-extending the |
|
@wecing wrote:
@sdasgup3 wrote:
This is another idea that circulates in our team. Keep the reduce computation with symmetric semantics but allow users to define |
Just for the completeness of my thoughts, the following is what i meant: With that, spec for reduce can will be pretty much the same (and simple) in terms of type constraints. Also, I note that to accommodate multiple input operands in the stablehlo.reduce, we might need to support
Thanks @loganchien for the example. That sounds like a valid option. A few observations, along with some questions, that I have here:
Let me know if I miss out anything in my observations. |
Not every
Yes. In that case, it can be optional and the input element is copied as-is. The verification constraint should be: if the types are different, there must be a conversion block. If types are the same, it is optional (people can do some preprocessing if they want) but can be skipped for brevity. |
IIUC, for all the input and init_value s contributing to a particular output element, we first use the |
|
Also looking forward to some feedback on
|
It is not about redundancy. It is about not rounding/saturating value too early. For a 4-element qi8 input, we only want to do: We don't want to round off the bits or saturate at qui8 range when we are doing the reduce, so that the inputs get the chance to cancel each other. Allowing them to be cancelled is the motivating reason to do this kind of extension. For example, if the zero-point adjusted input is
That is the third option being circulated in the internal doc. But I don't like it because doing tensor-level type conversion means 4x (run-time) memory usage. All serious compilers/interpreters must do a graph-level rewriting to remove the inefficiency before the they compile or execute the model. |
No. I don't see problem with multiple-operand reductions. Multiple-operand reductions only need multiple-operand |
|
So, if I understand it correctly, to summarize the discussions so far (please let me know if I missed anything): (Let
There were also some discussions between @loganchien and @sdasgup3 where IIUC @sdasgup3 was asking about Here are my thoughts:
What do you think? |
|
Hi @wecing Regarding your proposal:
Re: Just for context, @loganchien is referring to A where the proposal is about using separate quantize and dequantize ops before and after stablehlo.reduce to do the Can you please elaborate on the problem @loganchien is referring to? (sorry that I have to delegate) If we have one single op, as proposed in B, then I believe it can be readily mapped an efficient reduce kernel. For (A), pattern matching could be an additional concern and I am trying to understand how much is that an overhead. |
rewu93
left a comment
There was a problem hiding this comment.
One dumb question. How does the user define the accumulator type A if it is inside the reduce block?
Per @loganchien proposal, the input conversion block with specify the accumulation type. |
|
Hey @sdasgup3 -- I synced with @jingpu offline, and unfortunately neither of us know what @loganchien's concern on your proposal A was. But, as I mentioned above in the summary comment,
My guess is that:
|
|
Let me elaborate my thinking on two different ways to express "accumulator" type in the reduction: The first one (my favorite one for now) is to introduce Its execution looks like: The second one is proposed by @sdasgup3, which simply suggest adding Both options have pros and cons: The first option enables programmers to program at (almost) baremetal (*argument1). If the HW can support reduction computation in narrow type (e.g. in the SIMD instruction set, we typically do widening/compute/narrowing within the kernel to save the memory bandwidth), the programmer can explicitly request for that. The second option can't express the intent. It will rely on the compiler to do pattern matching and optimization to avoid the 4x memory bandwidth increase (*argument2). The compiler (or even interpreter) must pattern match 3 operations and map them into some internal representation (that is not expressible in StableHLO) before their compilation or execution (*argument3). In the second option, there is also a risk that the consecutive Some optimizations or folders may transform the code above into: and the best-effort approach in the backend can't kick in and the compiler must allocate an The second option still have some pros though. It doesn't require significant changes to existing spec (ReduceOp in particular). However, it puts the burden to the backend writer. From my own perspective, since my backend must do semantic graph rewrite anyway, the differences between two options are only about (1) the ease of the pattern matching implementation and (2) keep early transformations away from breaking the best-effort patterns. However, I believe the extra expressiveness might find some use cases in other more general architectures (e.g. CPU/GPU). Regarding the point on extending |
|
@loganchien Thanks for the elaborate explanation and I agree with your point. |
âĶExtensibility`, and `Miscellaneous` categories. (#1647) ### Summary This PRs provides the specification of the ops from the following categories: * **Control-flow ops**: `after_all, case, if, optimization_barrier, while` - `afer_all`: Not applicable for quantization as the ops deals with setting data-flow ordering using tokens. - `case, if`: These ops implicitly captures operands from the parent scope. The result of these operations and the corresponding blocks can be quantized types. - `optimization_barrier`: The `operand` and `result` can both be quantized tensors. - `while`: The `operand` and `result` can both be quantized tensors and of same type. Per the semantics of `while` op, the output of the associated block is used for the next iteration with the `operand` used for the very first iteration. With that, I do not think we can use the `operand` and `result` with different quantization types. * **Distribution**: `all_gather, all_reduce, all_to_all, collective_permute, infeed, outfeed, partition_id, recv, reduce_scatter, replica_id, send` All the ops in this category except for the following ones will support quantized types: - `all_reduce, reduce_scatter`: These involve quantization of reduction operation #1538. I would propose to address these two after the quantization of reduction ops are addressed. - `partition_id, replica_id`: Not applicable * **Extensibility**: `custom_call, get_tuple_element, tuple` All the ops in this category supports all the `ValueType`s including `QuantizedTensorType`. No change is needed in the existing spec. * **Miscellaneous**: `batch_norm_grad, batch_norm_inference, batch_norm_training, cholesky, constant, fft, iota, rng, rng_bit_generator, triangular_solve` - The specification of `batch_norm_inference`, `cholesky`, `triangular_solve` ops follow the same `dequantize-op-quantize` strategy as we followed for the elementwise ops. - `batch_norm_grad, batch_norm_training`: These involve quantization of reduction operation #1538. I would propose to address these two after the quantization of reduction ops are addressed. - `constant, iota`: The output type of these ops can be a quantized type of user-defined quantization parameters. - `fft`: This op supports `element_type(operand) = element_type(result) = complex type`, when the `fft_type` is `FFT` or `IFFT`. For `fft_type = RFFT`, the op supports floating-point `operand` and complex type `result` and for `fft_type = IRFFT`, it supports complex type `operand` and floating-point `result`. IMO, only for `fft_type = RFFT`, the operand can support quantized type and the semantics could be to quantize the operand to floating-point before doing the computation. - `rng`: The `operand` and `result` can be of quantized type only when the `rng_distribution` is `NORMAL` in which case the semantics follows the `dequantize_op_quantize` strategy. The motivation for this choice comes from the fact the op supports floating point type only when `rng_distribution` is `NORMAL` and we propose to treat this op, on floating-point tensors, as an op on quantized tensor, along the lines of dequantize -> float computation -> quantize. - `rng_bit_generator`: To be discussed in "open questions" section below. ### Open questions - `rng_bit_generator`: This op generates a randomized output tensor based on a `rng_algorithm` algorithm (`DEFAULT`, `THREE_FRY`, and `PHILOX`). It is not clear how there algorithm should handle generating quantized output. My proposal here is to skip quantization of this op unless we have a use case for it. Please let me know your opinion on this. Please review and let me know your feedback. **update 06/26/2023** Scoping `rng` out from quantization based on #1647 (comment) **update 07/13/2023** Per #1647 (comment), I proposed not to fuse de-quantization of operand and quantization of result for `fft_type = RFFT` and `fft_type = IRFFT` respectively for fft op. With that a user can explicit de-quantize of operand or quantize of result as needed. In the current proposal, the `batch_norm_inference` op, do not consider the constant operand `epsilon` to be of quantized type. IMO, considering that operand with quantized type unnecessary complicate the expressiveness of the op.
|
Superseded by #1664 - which has summarized the discussion here into an RFC, which is under discussion and will result in spec updates once approved. |
From the discussion emerged in #1538 about the specification of quantized `reduce` op, we zeroed in to a proposal which need some structural changes in the op (adding additional blocks). This proposed RFC is to further explore the proposal in detail. Please let me know your feedback. See also the [OpenXLA Discuss post](https://groups.google.com/a/openxla.org/g/openxla-discuss/c/iwE9is49SS4).
From the discussion emerged in openxla#1538 about the specification of quantized `reduce` op, we zeroed in to a proposal which need some structural changes in the op (adding additional blocks). This proposed RFC is to further explore the proposal in detail. Please let me know your feedback. See also the [OpenXLA Discuss post](https://groups.google.com/a/openxla.org/g/openxla-discuss/c/iwE9is49SS4).
From the discussion emerged in openxla#1538 about the specification of quantized `reduce` op, we zeroed in to a proposal which need some structural changes in the op (adding additional blocks). This proposed RFC is to further explore the proposal in detail. Please let me know your feedback. See also the [OpenXLA Discuss post](https://groups.google.com/a/openxla.org/g/openxla-discuss/c/iwE9is49SS4).
The PR proposes the specification of quantized
ReduceOp,ReduceWindowOp, andSelectAndScatterOp.For ReduceOp (and the other proposed ops using ReduceOp in their specification ), the specification allows different quantized element types for
inputs(orinit_values) and theresults. The reason is to allow the accumulation to happen in higher storage_type. However, the quantized element types forinputsandinit_valuesare the same. This behavior is same as other reduction ops like dot_general.The following shows an example usage of reduce op:
Note that, in the example above, like the non-quantized version of reduce op, the type of blocks arguments are the same. That is because from the POV of the semantic, there are any two elements to be added. The bitwidth of the accumulation is captured via output quantized element type.
Please let me know your review comments.