siyiweigeHEW opened a new issue, #20146:
URL: https://github.com/apache/tvm/issues/20146

   
   ### Expected behavior
   
   ONNX `Mean` computes an element-wise mean of its variadic inputs (with 
Numpy-style
   multidirectional broadcasting). With a **single** input it must return that 
input
   unchanged (the mean of one tensor is the tensor itself); with two inputs it 
must
   return `(x1 + x2) / 2`. Constant (initializer) inputs are valid and common.
   
   onnxruntime returns the `(2, 3)` input unchanged for the single-input case 
and
   `(2, 3)` for the two-input case.
   
   ### Actual behavior
   
   `tvm.relax.frontend.onnx.from_onnx` mishandles `Mean` nodes whose inputs are 
all
   constants (model initializers):
   
   - **Single constant input** `(2, 3)`: returns a **0-d scalar** `3.5` (the 
global mean of
     `1..6`) instead of the `(2, 3)` input unchanged.
   - **Two constant inputs**: raises
     `TypeError: only integer scalar arrays can be converted to a scalar index`.
   
   ### Root cause
   
   `MultiInputBase._impl_v1`'s constant-fold path at
   `python/tvm/relax/frontend/onnx/onnx_frontend.py:2456-2459`:
   
   ```python
   if all([isinstance(inp, relax.Constant) for inp in inputs]):
       np_inputs = [inp.data.numpy() for inp in inputs]
       output = cls.numpy_op(*np_inputs)  # pylint: disable=not-callable
       return relax.const(output, output.dtype)
   ```
   
   For `Mean`, `numpy_op = _np.mean`, so this calls `np.mean(*np_inputs)`. 
`np.mean`
   interprets the 2nd and later positional arguments as the `axis` parameter, 
not as
   additional data tensors:
   
   - **single input**: `np.mean(x)` reduces the whole tensor → 0-d scalar;
   - **multiple inputs**: `np.mean(a, b, ...)` passes array(s) into `axis` → 
TypeError.
   
   The same constant-fold path is shared by `Sum` / `Min` / `Max`
   (`np.sum` / `np.min` / `np.max` have the identical `axis`-as-2nd-arg 
signature),
   so all of them are affected the same way.
   
   ### Environment
   
   - OS: Linux
   - TVM: v0.24.dev0 (commit `262c6d2e0`; also present on `main` at 
`7b2ef6ad5f`)
   - Python: 3.11
   - onnx: 1.20.1
   - onnxruntime: 1.24.1
   
   ### Steps to reproduce
   
   ```python
   """Repro: ONNX Mean with constant (initializer) inputs — TVM returns a 0-d 
scalar for
   single input and raises TypeError for multiple inputs; onnxruntime handles 
both."""
   import numpy as np
   import onnx, onnxruntime
   from onnx import helper, TensorProto
   import tvm
   from tvm import relax
   from tvm.relax.frontend.onnx import from_onnx
   
   
   def build_mean(consts):
       inits = [
           helper.make_tensor(
               f"c{i}", TensorProto.FLOAT, list(c.shape), 
c.astype(np.float32).flatten().tolist()
           )
           for i, c in enumerate(consts)
       ]
       Y = helper.make_tensor_value_info("Y", TensorProto.FLOAT, None)
       node = helper.make_node("Mean", [f"c{i}" for i in range(len(consts))], 
["Y"])
       graph = helper.make_graph([node], "mean", [], [Y], inits)
       model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 
13)])
       model.ir_version = 8
       return model
   
   
   # ---- Case 1: single constant input (2,3). ONNX Mean must return the input 
unchanged. ----
   const = (np.arange(6) + 1).reshape(2, 3).astype(np.float32)
   m1 = build_mean([const])
   ref1 = onnxruntime.InferenceSession(m1.SerializeToString()).run(None, {})[0]
   print("onnxruntime ->", ref1.shape, ref1.flatten().tolist())
   
   mod1 = from_onnx(m1)
   ex1 = relax.build(mod1, target="llvm")
   vm1 = relax.VirtualMachine(ex1, tvm.cpu())
   out1 = vm1["main"]().numpy()
   print("TVM        ->", out1.shape, out1.flatten().tolist() if out1.ndim else 
[float(out1)])
   
   # ---- Case 2: two constant inputs (2,3). ONNX Mean returns (x1+x2)/2. ----
   m2 = build_mean([const, const + 10])
   ref2 = onnxruntime.InferenceSession(m2.SerializeToString()).run(None, {})[0]
   print("\n[two inputs] onnxruntime ->", ref2.shape, ref2.flatten().tolist())
   
   try:
       mod2 = from_onnx(m2)
       ex2 = relax.build(mod2, target="llvm")
       vm2 = relax.VirtualMachine(ex2, tvm.cpu())
       out2 = vm2["main"]().numpy()
       print("[two inputs] TVM        ->", out2.shape, out2.flatten().tolist())
   except Exception as e:
       print("[two inputs] TVM        ->", type(e).__name__, str(e)[:80])
   ```
   
   Actual output:
   
   ```
   onnxruntime -> (2, 3) [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
   TVM        -> () [3.5]
   
   [two inputs] onnxruntime -> (2, 3) [6.0, 7.0, 8.0, 9.0, 10.0, 11.0]
   [two inputs] TVM        -> TypeError only integer scalar arrays can be 
converted to a scalar index
   ```
   
   ### Additional context
   
   Fix suggestion: the constant-fold path must reduce element-wise across the 
input
   arrays (with broadcasting) instead of calling `np.mean(*np_inputs)`, e.g. 
for `Mean`:
   
   ```python
   output = functools.reduce(np.add, np_inputs) / len(np_inputs)
   ```
   
   or simply route the constant case through the same `broadcast_to` + `stack` +
   `relax.op.mean(stacked, axis=0)` path used for non-constant inputs.
   
   ### Triage
   
   * needs-triage
   * bug
   * relax
   * frontend/onnx
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to