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

   ### Expected behavior
   
   Fusing a 2x2 MaxPool with its elementwise Add consumer should retain an 
efficient schedule for the small reduction. In this case, one thread can 
compute an output using four input values and then apply the Add.
   
   ### Actual behavior
   
   Enabling `FuseOps` and `FuseTIR` increases end-to-end VM latency from 45.68 
us to 251.70 us on an RTX A6000. Keeping the upstream Add/Concat operations 
fused while separating Pool from Add takes 13.00 us.
   
   The graph was extracted from the official pass-test corpus for 
AnnotateTIROpPattern/FuseOps and adapted to the TIRx API. Its input is float32 
`[1,16,64,64]`. Nine Add branches are concatenated in two levels, producing 
`[1,144,64,64]`, followed by a 2x2 MaxPool with stride 2 and two Add 
operations. Both Add outputs are returned, each with shape `[1,144,32,32]`.
   
   | Configuration | VM latency, us |
   |---|---:|
   | OFF: skip FuseOps and FuseTIR | 45.68 |
   | PARTIAL: fuse the prefix, keep Pool and Add separate | 13.00 |
   | ON: default fusion and scheduling | 251.70 |
   | SERIAL: same fused graph, replace only the fused pool function's schedule 
| 10.30 |
   
   These are medians of five per-trial median latencies. The five ON/OFF ratios 
were `5.54737, 5.50958, 5.56865, 5.52919, 5.51838`. All five trials passed the 
10% slowdown threshold with CV below 5%; the largest CV across all four paths 
was 0.73%. All paths matched an independent NumPy reference with zero maximum 
absolute error on three input draws per trial.
   
   The saved scheduling trace shows that `GeneralReduction` accepts 
`fused_pool2d_add2`. The resulting launch configuration differs substantially:
   
   | Pool schedule | Blocks | Threads per block | Output mapping |
   |---|---:|---:|---|
   | OFF / PARTIAL | 144 | 1024 | One output per thread, serial four-element 
reduction |
   | ON | 147456 | 256 | One output per block; only four threads load reduction 
inputs |
   | SERIAL | 576 | 256 | One output per thread, serial reduction followed by 
Add |
   
   ON also uses shared memory for the reduction and its consumer. Replacing 
only this fused function with the SERIAL schedule reduces VM latency by 95.9%, 
without changing the other functions or outputs. The actual cubin reports 16 
registers and no stack/local memory for both ON and SERIAL; shared memory drops 
from 132 bytes to zero.
   
   This points to the default scheduling choice for a small reduction with an 
elementwise consumer. A schedule that parallelizes across outputs and performs 
the small reduction within each thread avoids the slowdown. The SERIAL 
intervention changes several scheduling decisions together, so it does not 
isolate their individual costs. The timings above are whole-graph VM timings.
   
   ### Environment
   
   - TVM: `0.26.dev0+source.8f328e8`
   - Commit: `8f328e802cfe5e41fcc8f5c17e7582b1c28bfce4`
   - GPU: NVIDIA RTX A6000, `sm_86`
   - NVIDIA driver: `550.120`
   - CUDA compiler: `12.4.131`
   - LLVM: 15; host target `llvm -mcpu=generic`
   - OS: Linux x86_64, kernel `6.8.0-52-generic`, glibc 2.35
   - Python: 3.11.16; NumPy: 2.4.6
   - `USE_CUDA=ON`, `USE_LLVM=ON`, `USE_CUBLAS=OFF`, `USE_CUDNN=OFF`, 
`USE_CUTLASS=OFF`
   
   ### Steps to reproduce
   
   Save the following as `repro_pool_fusion.py` and run it in a TVM CUDA 
environment at the commit above:
   
   ```bash
   for trial in 0 1 2 3 4; do
       python repro_pool_fusion.py "$trial"
   done
   ```
   
   The script preserves the original TIRx graph and compares the four paths 
above. PARTIAL places a dataflow boundary immediately after Pool. SERIAL 
replaces only the fused pool function before default scheduling.
   
   Inputs are sampled uniformly from `[-0.5, 0.5]` with seeds 0, 1 and 2 for 
correctness; seed 0 is used for timing. Inputs are copied to the GPU before 
measurement. Timing uses `invoke_stateful`, 75 warmup calls followed by two 
seconds of timed warmup, and 40 interleaved blocks of five repeats 
(`number=10`, `min_repeat_ms=50`). Compilation and transfers are outside the 
timer. Each process saves the scheduling IR and all 200 samples per path.
   
   ```python
   import ast
   import copy
   import hashlib
   import itertools
   import json
   from pathlib import Path
   import statistics
   import sys
   import time
   
   import numpy as np
   import tvm
   from tvm import relax
   from tvm.relax.backend.cuda import pipeline
   from tvm.script import ir as I, relax as R, tirx as T
   
   SOURCE = r'''
   @I.ir_module
   class Module:
   
       @T.prim_func(private=True, s_tir=True)
       def add(x: T.Buffer((T.int64(1), T.int64(16), T.int64(64), T.int64(64)), 
'float32'), B: T.Buffer((), 'float32'), T_add: T.Buffer((T.int64(1), 
T.int64(16), T.int64(64), T.int64(64)), 'float32')):
           T.func_attr({'tirx.noalias': True})
           for ax0, ax1, ax2, ax3 in T.grid(T.int64(1), T.int64(16), 
T.int64(64), T.int64(64)):
               with T.sblock('T_add'):
                   v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap('SSSS', [ax0, ax1, 
ax2, ax3])
                   T.reads(x[v_ax0, v_ax1, v_ax2, v_ax3], B[()])
                   T.writes(T_add[v_ax0, v_ax1, v_ax2, v_ax3])
                   T_add[v_ax0, v_ax1, v_ax2, v_ax3] = x[v_ax0, v_ax1, v_ax2, 
v_ax3] + B[()]
   
       @T.prim_func(private=True, s_tir=True)
       def add1(lv3: T.Buffer((T.int64(1), T.int64(48), T.int64(64), 
T.int64(64)), 'float32'), B: T.Buffer((), 'float32'), T_add: 
T.Buffer((T.int64(1), T.int64(48), T.int64(64), T.int64(64)), 'float32')):
           T.func_attr({'tirx.noalias': True})
           for ax0, ax1, ax2, ax3 in T.grid(T.int64(1), T.int64(48), 
T.int64(64), T.int64(64)):
               with T.sblock('T_add'):
                   v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap('SSSS', [ax0, ax1, 
ax2, ax3])
                   T.reads(lv3[v_ax0, v_ax1, v_ax2, v_ax3], B[()])
                   T.writes(T_add[v_ax0, v_ax1, v_ax2, v_ax3])
                   T_add[v_ax0, v_ax1, v_ax2, v_ax3] = lv3[v_ax0, v_ax1, v_ax2, 
v_ax3] + B[()]
   
       @T.prim_func(private=True, s_tir=True)
       def add2(lv16: T.Buffer((T.int64(1), T.int64(144), T.int64(32), 
T.int64(32)), 'float32'), B: T.Buffer((), 'float32'), T_add: 
T.Buffer((T.int64(1), T.int64(144), T.int64(32), T.int64(32)), 'float32')):
           T.func_attr({'tirx.noalias': True})
           for ax0, ax1, ax2, ax3 in T.grid(T.int64(1), T.int64(144), 
T.int64(32), T.int64(32)):
               with T.sblock('T_add'):
                   v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap('SSSS', [ax0, ax1, 
ax2, ax3])
                   T.reads(lv16[v_ax0, v_ax1, v_ax2, v_ax3], B[()])
                   T.writes(T_add[v_ax0, v_ax1, v_ax2, v_ax3])
                   T_add[v_ax0, v_ax1, v_ax2, v_ax3] = lv16[v_ax0, v_ax1, 
v_ax2, v_ax3] + B[()]
   
       @T.prim_func(private=True, s_tir=True)
       def concatenate(lv: T.Buffer((T.int64(1), T.int64(16), T.int64(64), 
T.int64(64)), 'float32'), lv1: T.Buffer((T.int64(1), T.int64(16), T.int64(64), 
T.int64(64)), 'float32'), lv2: T.Buffer((T.int64(1), T.int64(16), T.int64(64), 
T.int64(64)), 'float32'), T_concat: T.Buffer((T.int64(1), T.int64(48), 
T.int64(64), T.int64(64)), 'float32')):
           T.func_attr({'tirx.noalias': True})
           for ax0, ax1, ax2, ax3 in T.grid(T.int64(1), T.int64(48), 
T.int64(64), T.int64(64)):
               with T.sblock('T_concat'):
                   v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap('SSSS', [ax0, ax1, 
ax2, ax3])
                   T.reads(lv2[v_ax0, v_ax1 - T.int64(32), v_ax2, v_ax3], 
lv1[v_ax0, v_ax1 - T.int64(16), v_ax2, v_ax3], lv[v_ax0, v_ax1, v_ax2, v_ax3])
                   T.writes(T_concat[v_ax0, v_ax1, v_ax2, v_ax3])
                   T_concat[v_ax0, v_ax1, v_ax2, v_ax3] = 
T.if_then_else(T.int64(32) <= v_ax1, lv2[v_ax0, v_ax1 - T.int64(32), v_ax2, 
v_ax3], T.if_then_else(T.int64(16) <= v_ax1, lv1[v_ax0, v_ax1 - T.int64(16), 
v_ax2, v_ax3], lv[v_ax0, v_ax1, v_ax2, v_ax3]))
   
       @T.prim_func(private=True, s_tir=True)
       def concatenate1(lv4: T.Buffer((T.int64(1), T.int64(48), T.int64(64), 
T.int64(64)), 'float32'), lv9: T.Buffer((T.int64(1), T.int64(48), T.int64(64), 
T.int64(64)), 'float32'), lv14: T.Buffer((T.int64(1), T.int64(48), T.int64(64), 
T.int64(64)), 'float32'), T_concat: T.Buffer((T.int64(1), T.int64(144), 
T.int64(64), T.int64(64)), 'float32')):
           T.func_attr({'tirx.noalias': True})
           for ax0, ax1, ax2, ax3 in T.grid(T.int64(1), T.int64(144), 
T.int64(64), T.int64(64)):
               with T.sblock('T_concat'):
                   v_ax0, v_ax1, v_ax2, v_ax3 = T.axis.remap('SSSS', [ax0, ax1, 
ax2, ax3])
                   T.reads(lv14[v_ax0, v_ax1 - T.int64(96), v_ax2, v_ax3], 
lv9[v_ax0, v_ax1 - T.int64(48), v_ax2, v_ax3], lv4[v_ax0, v_ax1, v_ax2, v_ax3])
                   T.writes(T_concat[v_ax0, v_ax1, v_ax2, v_ax3])
                   T_concat[v_ax0, v_ax1, v_ax2, v_ax3] = 
T.if_then_else(T.int64(96) <= v_ax1, lv14[v_ax0, v_ax1 - T.int64(96), v_ax2, 
v_ax3], T.if_then_else(T.int64(48) <= v_ax1, lv9[v_ax0, v_ax1 - T.int64(48), 
v_ax2, v_ax3], lv4[v_ax0, v_ax1, v_ax2, v_ax3]))
   
       @T.prim_func(private=True, s_tir=True)
       def pool2d(lv15: T.Buffer((T.int64(1), T.int64(144), T.int64(64), 
T.int64(64)), 'float32'), pool_max: T.Buffer((T.int64(1), T.int64(144), 
T.int64(32), T.int64(32)), 'float32')):
           T.func_attr({'tirx.noalias': True})
           for ax0, ax1, ax2, ax3, rv0, rv1 in T.grid(T.int64(1), T.int64(144), 
T.int64(32), T.int64(32), T.int64(2), T.int64(2)):
               with T.sblock('pool_max'):
                   v_ax0, v_ax1, v_ax2, v_ax3, v_rv0, v_rv1 = 
T.axis.remap('SSSSRR', [ax0, ax1, ax2, ax3, rv0, rv1])
                   T.reads(lv15[v_ax0, v_ax1, v_ax2 * T.int64(2) + v_rv0, v_ax3 
* T.int64(2) + v_rv1])
                   T.writes(pool_max[v_ax0, v_ax1, v_ax2, v_ax3])
                   T.sblock_attr({'schedule_rule': 'meta_schedule.pool_max'})
                   with T.init():
                       pool_max[v_ax0, v_ax1, v_ax2, v_ax3] = 
T.float32(-3.4028234663852886e+38)
                   pool_max[v_ax0, v_ax1, v_ax2, v_ax3] = T.max(pool_max[v_ax0, 
v_ax1, v_ax2, v_ax3], lv15[v_ax0, v_ax1, v_ax2 * T.int64(2) + v_rv0, v_ax3 * 
T.int64(2) + v_rv1])
   
       @R.function
       def main(x: R.Tensor((1, 16, 64, 64), dtype='float32')) -> 
R.Tuple(R.Tensor((1, 144, 32, 32), dtype='float32'), R.Tensor((1, 144, 32, 32), 
dtype='float32')):
           cls = Module
           with R.dataflow():
               lv = R.call_tir(cls.add, (x, R.const(1, 'float32')), 
out_ty=R.Tensor((1, 16, 64, 64), dtype='float32'))
               lv1 = R.call_tir(cls.add, (x, R.const(1, 'float32')), 
out_ty=R.Tensor((1, 16, 64, 64), dtype='float32'))
               lv2 = R.call_tir(cls.add, (x, R.const(1, 'float32')), 
out_ty=R.Tensor((1, 16, 64, 64), dtype='float32'))
               lv3 = R.call_tir(cls.concatenate, (lv, lv1, lv2), 
out_ty=R.Tensor((1, 48, 64, 64), dtype='float32'))
               lv4 = R.call_tir(cls.add1, (lv3, R.const(1, 'float32')), 
out_ty=R.Tensor((1, 48, 64, 64), dtype='float32'))
               lv5 = R.call_tir(cls.add, (x, R.const(1, 'float32')), 
out_ty=R.Tensor((1, 16, 64, 64), dtype='float32'))
               lv6 = R.call_tir(cls.add, (x, R.const(1, 'float32')), 
out_ty=R.Tensor((1, 16, 64, 64), dtype='float32'))
               lv7 = R.call_tir(cls.add, (x, R.const(1, 'float32')), 
out_ty=R.Tensor((1, 16, 64, 64), dtype='float32'))
               lv8 = R.call_tir(cls.concatenate, (lv5, lv6, lv7), 
out_ty=R.Tensor((1, 48, 64, 64), dtype='float32'))
               lv9 = R.call_tir(cls.add1, (lv8, R.const(1, 'float32')), 
out_ty=R.Tensor((1, 48, 64, 64), dtype='float32'))
               lv10 = R.call_tir(cls.add, (x, R.const(1, 'float32')), 
out_ty=R.Tensor((1, 16, 64, 64), dtype='float32'))
               lv11 = R.call_tir(cls.add, (x, R.const(1, 'float32')), 
out_ty=R.Tensor((1, 16, 64, 64), dtype='float32'))
               lv12 = R.call_tir(cls.add, (x, R.const(1, 'float32')), 
out_ty=R.Tensor((1, 16, 64, 64), dtype='float32'))
               lv13 = R.call_tir(cls.concatenate, (lv10, lv11, lv12), 
out_ty=R.Tensor((1, 48, 64, 64), dtype='float32'))
               lv14 = R.call_tir(cls.add1, (lv13, R.const(1, 'float32')), 
out_ty=R.Tensor((1, 48, 64, 64), dtype='float32'))
               lv15 = R.call_tir(cls.concatenate1, (lv4, lv9, lv14), 
out_ty=R.Tensor((1, 144, 64, 64), dtype='float32'))
               lv16 = R.call_tir(cls.pool2d, (lv15,), out_ty=R.Tensor((1, 144, 
32, 32), dtype='float32'))
               lv17 = R.call_tir(cls.add2, (lv16, R.const(1, 'float32')), 
out_ty=R.Tensor((1, 144, 32, 32), dtype='float32'))
               lv18 = R.call_tir(cls.add2, (lv17, R.const(1, 'float32')), 
out_ty=R.Tensor((1, 144, 32, 32), dtype='float32'))
               gv: R.Tuple(R.Tensor((1, 144, 32, 32), dtype='float32'), 
R.Tensor((1, 144, 32, 32), dtype='float32')) = (lv17, lv18)
               R.output(gv)
           return gv
   '''
   
   def shape(c):
       return (1, c["channels"], c["height"], c["width"])
   
   def pool_shape(c):
       c0 = c["channels"] * (c["branches"]**2 if c["prelude"] else 1)
       return 
(1,c0,(c["height"]-c["window"])//c["stride"]+1,(c["width"]-c["window"])//c["stride"]+1)
   
   def inputs(c, seed):
       return 
np.random.default_rng(seed).uniform(-.5,.5,shape(c)).astype("float32")
   
   def reference(c, x):
       y = x
       one = np.float32(1)
       if c["prelude"]:
           groups = []
           for _ in range(c["branches"]):
               leaves = [(x+one).astype("float32") for _ in 
range(c["branches"])]
               
groups.append((np.concatenate(leaves,axis=1)+one).astype("float32"))
           y = np.concatenate(groups,axis=1)
       _,channels,oh,ow = pool_shape(c)
       pooled = np.full((1,channels,oh,ow), np.finfo(np.float32).min, 
dtype="float32")
       k,s = c["window"],c["stride"]
       for rh in range(k):
           for rw in range(k):
               pooled = np.maximum(pooled,y[:,:,rh:rh+oh*s:s,rw:rw+ow*s:s])
       outputs = []
       for _ in range(c["tail"]):
           pooled = (pooled+one).astype("float32")
           outputs.append(pooled)
       return (outputs[0], outputs[-1]) if c["shared_output"] else outputs[-1]
   
   def tir_shape(dims):
       return "(" + ", ".join(f"T.int64({v})" for v in dims) + ("," if 
len(dims)==1 else "") + ")"
   
   def partial_source(text):
       """Split dataflow immediately after pool; leave main inputs/outputs 
intact."""
       tree = ast.parse(text)
       cls = next(n for n in tree.body if isinstance(n,ast.ClassDef))
       main = next(n for n in cls.body if isinstance(n,ast.FunctionDef) and 
n.name=="main")
       blocks = [n for n in main.body if isinstance(n,ast.With)]
       if len(blocks)!=1:
           raise ValueError("Expected one dataflow block")
       block = blocks[0]
       hits = [i for i,n in enumerate(block.body) if 
isinstance(n,(ast.Assign,ast.AnnAssign))
               and isinstance(n.value,ast.Call) and 
ast.unparse(n.value.func)=="R.call_tir"
               and ast.unparse(n.value.args[0])=="cls.pool2d"]
       if len(hits)!=1:
           raise ValueError("Expected unique pool call")
       index = hits[0]; stmt = block.body[index]
       var = stmt.targets[0] if isinstance(stmt,ast.Assign) else stmt.target
       first = copy.deepcopy(block); second = copy.deepcopy(block)
       first.body = 
block.body[:index+1]+[ast.parse(f"R.output({var.id})").body[0]]
       second.body = block.body[index+1:]
       pos = main.body.index(block); main.body[pos:pos+1] = [first,second]
       return ast.unparse(ast.fix_missing_locations(tree))+"\n"
   
   def serial_source(c, biases):
       """A diagnostic schedule: one thread computes one small pool and 
epilogue."""
       _,channels,oh,ow = pool_shape(c)
       h,w,k,s = c["height"],c["width"],c["window"],c["stride"]
       total = channels*oh*ow
       args = ", ".join(f"B{i}: T.Buffer((), 'float32')" for i in range(biases))
       additions = "\n".join(f"                    acc[0] = acc[0] + B{i}[()]" 
for i in range(biases))
       return f'''@I.ir_module
   class Replacement:
       @T.prim_func(private=True, s_tir=True)
       def replacement(A: T.Buffer({tir_shape((1,channels,h,w))}, "float32"), 
{args}, O: T.Buffer({tir_shape((1,channels,oh,ow))}, "float32")):
           T.func_attr({{"tirx.noalias": True, "tirx.is_scheduled": True}})
           for bx in T.thread_binding(T.int64({(total+255)//256}), 
thread="blockIdx.x"):
               for tx in T.thread_binding(T.int64(256), thread="threadIdx.x"):
                   with T.sblock("serial_pool_epilogue"):
                       v = T.axis.spatial(T.int64({total}), bx*T.int64(256)+tx)
                       T.where(bx*T.int64(256)+tx < T.int64({total}))
                       acc = T.sblock_alloc_buffer((1,), "float32", 
scope="local")
                       acc[0] = T.float32(-3.4028234663852886e+38)
                       for rh,rw in T.grid(T.int64({k}),T.int64({k})):
                           acc[0] = T.max(acc[0], 
A[0,v//T.int64({oh*ow}),(v//T.int64({ow})%T.int64({oh}))*T.int64({s})+rh,(v%T.int64({ow}))*T.int64({s})+rw])
   {additions}
                       
O[0,v//T.int64({oh*ow}),v//T.int64({ow})%T.int64({oh}),v%T.int64({ow})] = acc[0]
   '''
   
   def parameter_buffer_types(fn, buffer_type):
       """TIRx parameters carry BufferType in Var.ty, not a PrimFunc 
buffer_map."""
       types = []
       for index, param in enumerate(fn.params):
           ty = getattr(param, "ty", None)
           if not isinstance(ty, buffer_type):
               raise ValueError(f"Pool parameter {index} is not a BufferType; 
refusing intervention")
           types.append(ty)
       return types
   
   def intervention(c, out):
       def apply(mod):
           import tvm
           from tvm import tirx
           from tvm.tirx.buffer import BufferType
           from tvm.script import ir as I, tirx as T
           found = [(gv,fn) for gv,fn in mod.functions.items() if 
isinstance(fn,tirx.PrimFunc)
                    and "pool2d" in gv.name_hint and "add2" in gv.name_hint]
           if len(found)!=1:
               raise ValueError("Expected exactly one fused pool epilogue for 
intervention")
           gv,fn = found[0]
           params = list(fn.params)
           buffer_types = parameter_buffer_types(fn, BufferType)
           dims = [tuple(int(x) for x in ty.shape) for ty in buffer_types]
           dtypes = [str(tvm.DataType(ty.dtype)) for ty in buffer_types]
           expected_in = (1,pool_shape(c)[1],c["height"],c["width"])
           if len(params)<3 or dims[0]!=expected_in or dims[-1]!=pool_shape(c) 
or not all(d==() for d in dims[1:-1]):
               raise ValueError("Unrecognized fused pool signature; refusing 
intervention")
           if any(dtype!="float32" for dtype in dtypes):
               raise ValueError("Only float32 intervention validated")
           text = serial_source(c,len(params)-2)
           (out/"serial_kernel.py").write_text(text,encoding="utf-8")
           replacement = tvm.script.from_source(text, 
extra_vars=dict(I=I,T=T),s_tir=True)["replacement"]
           replacement_types = parameter_buffer_types(replacement, BufferType)
           if ([tuple(int(x) for x in ty.shape) for ty in replacement_types] != 
dims
                   or [str(tvm.DataType(ty.dtype)) for ty in replacement_types] 
!= dtypes):
               raise ValueError("Replacement parameter shapes/dtypes differ 
from original")
           record = dict(replaced_function=gv.name_hint, biases=len(params)-2,
               parameter_api="tirx.Var.ty / BufferType", parameter_shapes=dims, 
parameter_dtypes=dtypes,
               policy="diagnostic serial reduction per output; not an upstream 
compiler fix",
               before_sha256=hashlib.sha256(fn.script().encode()).hexdigest(),
               
unchanged_functions={key.name_hint:hashlib.sha256(value.script().encode()).hexdigest()
                                    for key,value in mod.functions.items() if 
key!=gv})
           mod.update_func(gv,replacement)
           record["other_functions_unchanged"] = 
all(hashlib.sha256(mod[key].script().encode()).hexdigest()==value
                                                    for key,value in 
record["unchanged_functions"].items())
           write_json(out/"intervention.json",record)
           return mod
       return apply
   
   CASE = dict(channels=16, height=64, width=64, window=2, stride=2,
               branches=3, prelude=True, tail=2, shared_output=True)
   VARIANTS = ("off", "partial", "on", "serial")
   
   def write_json(path, data):
       path.write_text(json.dumps(data, indent=2), encoding="utf-8")
   
   def build_variant(key, target, directory):
       directory.mkdir(parents=True, exist_ok=True)
       text = partial_source(SOURCE) if key == "partial" else SOURCE
       mod = tvm.script.from_source(text, extra_vars=dict(I=I, R=R, T=T), 
s_tir=True)
       with target, tvm.transform.PassContext(opt_level=3):
           steps = pipeline.library_dispatch_passes(target) + 
pipeline.legalize_passes(target)
           for index, transform in enumerate(steps):
               name = str(transform.info.name).split(".")[-1]
               if key == "off" and name in ("FuseOps", "FuseTIR"):
                   continue
               if name == "ApplyDefaultSchedule" and key == "serial":
                   mod = intervention(CASE, directory)(mod)
               mod = transform(mod)
               if name in ("FuseOps", "FuseTIR", "ApplyDefaultSchedule"):
                   (directory / 
f"{index:02d}_{name}.py").write_text(mod.script())
           for transform in pipeline.dataflow_lower_passes(target) + 
pipeline.finalize_passes(target):
               mod = transform(mod)
           return relax.build(mod, target=target, relax_pipeline=None, 
tir_pipeline="default")
   
   def main():
       trial = int(sys.argv[1]) if len(sys.argv) > 1 else 0
       out = Path(f"pool_repro_trial_{trial}")
       out.mkdir(parents=True, exist_ok=True)
       dev = tvm.cuda(0)
       assert dev.exist
       arch = "sm_" + str(dev.compute_version).replace(".", "")
       target = tvm.target.Target({"kind": "cuda", "arch": arch}, host="llvm 
-mcpu=generic")
       print(tvm.__version__, target, flush=True)
       order = VARIANTS if trial % 2 == 0 else tuple(reversed(VARIANTS))
       vms = {key: relax.VirtualMachine(build_variant(key, target, out/key), 
dev) for key in order}
       for seed in range(3):
           x = inputs(CASE, seed)
           expected = reference(CASE, x)
           for key, vm in vms.items():
               vm.set_input("main", tvm.runtime.tensor(x, device=dev))
               vm.invoke_stateful("main")
               dev.sync()
               actual = vm.get_outputs("main")
               assert len(actual) == len(expected)
               for got, want in zip(actual, expected):
                   got = got.numpy()
                   assert got.dtype == want.dtype and got.shape == want.shape
                   np.testing.assert_allclose(got, want, rtol=2e-4, atol=2e-5)
       print("All four paths passed the independent NumPy reference.", 
flush=True)
       for vm in vms.values():
           vm.set_input("main", tvm.runtime.tensor(inputs(CASE, 0), device=dev))
       timers = {k: vm.time_evaluator("invoke_stateful", dev, number=10,
                                     repeat=5, min_repeat_ms=50) for k, vm in 
vms.items()}
       permutations = list(itertools.permutations(VARIANTS))
       offset = trial * 40
       for key in permutations[offset % len(permutations)]:
           for _ in range(75):
               vms[key].invoke_stateful("main")
           dev.sync()
           deadline = time.monotonic() + 2
           while time.monotonic() < deadline:
               timers[key]("main")
       samples = {key: [] for key in VARIANTS}
       for block in range(40):
           for key in permutations[(offset + block) % len(permutations)]:
               dev.sync()
               samples[key].extend(float(x) for x in 
timers[key]("main").results)
       results = {k: dict(median_us=statistics.median(xs)*1e6,
                          cv=statistics.stdev(xs)/statistics.mean(xs), 
samples_s=xs)
                  for k, xs in samples.items()}
       for key in VARIANTS:
           print(key, "median_us=", results[key]["median_us"], "CV=", 
results[key]["cv"])
       for a, b in (("on", "off"), ("on", "partial"), ("serial", "on")):
           print(a+"/"+b, results[a]["median_us"]/results[b]["median_us"])
       write_json(out/"timings.json", results)
   
   if __name__ == "__main__":
       main()
   ```
   
   ### Triage
   
   - needs-triage
   - backend:cuda
   


-- 
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