This is an automated email from the ASF dual-hosted git repository.

tlopex pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git


The following commit(s) were added to refs/heads/main by this push:
     new d536d413e6 [Fix][DLight] Handle rank-one GEMV cache loads (#20158)
d536d413e6 is described below

commit d536d413e66cfd018d45f5400301b0d747e87b81
Author: Sam Sui <[email protected]>
AuthorDate: Tue Aug 18 17:55:39 2026 -0500

    [Fix][DLight] Handle rank-one GEMV cache loads (#20158)
    
    DLight's GPU GEMV rule assumes that the final two loops around a local
    cache
    read are both owned by the cache stage. Rank-one vector inputs create
    only one
    cache loop, causing the rule to include the shared placement loop and
    attempt
    to fuse an imperfect loop nest.
    
    This change records the cache loop count before `compute_at` and
    recovers that
    innermost suffix afterward. A single cache loop is used directly, while
    the
    existing final-two-loop fusion is preserved for higher-rank inputs.
    
    A focused regression verifies that rank-one GEMV scheduling completes
    and that
    the local vector load remains vectorized.
    
    Fixes #20118
    
    Testing:
    
    - `python -m pytest tests/python/s_tir/dlight/test_gpu_gemv.py -q` — 14
    passed
    - `python -m pytest tests/python/relax/test_pipeline.py -q` — 24 passed
    - Exact issue reproducer — passed
    - RTX 4070 Ti SUPER (SM89): FP32/FP16 widths 2, 3, 32, 33, and 128 —
    NumPy parity
    - `pre-commit run --all-files` — passed
---
 python/tvm/s_tir/dlight/gpu/gemv.py        | 11 ++++++-----
 tests/python/s_tir/dlight/test_gpu_gemv.py | 28 ++++++++++++++++++++++++++++
 2 files changed, 34 insertions(+), 5 deletions(-)

diff --git a/python/tvm/s_tir/dlight/gpu/gemv.py 
b/python/tvm/s_tir/dlight/gpu/gemv.py
index 1c451b964f..1a71fa697e 100644
--- a/python/tvm/s_tir/dlight/gpu/gemv.py
+++ b/python/tvm/s_tir/dlight/gpu/gemv.py
@@ -164,13 +164,14 @@ class GEMV(GPUScheduleRule):
                 and shared_mem_usage.value <= max_smem
             )
 
-            # vectorize load A
-            # (TODO) this is now actually problematic since the number of 
loops is dependent on the
-            # number of dimensions of A_q
             Aq_local = sch.cache_read(rf, read_buffer_index=1, 
storage_scope="local")
+            num_cache_loops = len(sch.get_loops(block=Aq_local))
             sch.compute_at(Aq_local, r, preserve_unit_loops=True)
-            s_local, r_local = sch.get_loops(block=Aq_local)[-2:]
-            fused_load = sch.fuse(s_local, r_local)
+            cache_loops = sch.get_loops(block=Aq_local)[-num_cache_loops:]
+            if len(cache_loops) == 1:
+                fused_load = cache_loops[0]
+            else:
+                fused_load = sch.fuse(*cache_loops[-2:])
             aq_vec_len = max(1, VEC_LOAD // 
get_bytes(sch.get(Aq_local).reads[0].buffer.dtype))
             fused_load, vec_load = sch.split(
                 fused_load, factors=[None, aq_vec_len], 
preserve_unit_iters=True
diff --git a/tests/python/s_tir/dlight/test_gpu_gemv.py 
b/tests/python/s_tir/dlight/test_gpu_gemv.py
index 8cfcd1bb81..e7454f5505 100644
--- a/tests/python/s_tir/dlight/test_gpu_gemv.py
+++ b/tests/python/s_tir/dlight/test_gpu_gemv.py
@@ -1114,6 +1114,34 @@ def 
test_gemv_cuda_target_without_max_shared_memory_per_block():
     assert mod["main"].attrs["tirx.is_scheduled"] == 1
 
 
+def test_gemv_rank_one_vector_input():
+    @T.prim_func(private=True, s_tir=True)
+    def before(
+        matrix: T.Buffer((2, 2), "float32"),
+        vector: T.Buffer((2,), "float32"),
+        output: T.Buffer((2,), "float32"),
+    ):
+        T.func_attr({"tirx.noalias": True})
+        for i, k in T.grid(2, 2):
+            with T.sblock("gemv"):
+                vi, vk = T.axis.remap("SR", [i, k])
+                T.reads(matrix[vi, vk], vector[vk])
+                T.writes(output[vi])
+                with T.init():
+                    output[vi] = T.float32(0)
+                output[vi] += matrix[vi, vk] * vector[vk]
+
+    mod = tvm.IRModule({"main": before})
+    with Target("nvidia/geforce-rtx-3090-ti"):
+        mod = dl.ApplyDefaultSchedule(dl.gpu.GEMV())(mod)
+
+    assert mod["main"].attrs["tirx.is_scheduled"] == 1
+    sch = tvm.s_tir.Schedule(mod)
+    vector_local = sch.get_sblock("vector_local")
+    vector_load_loop = sch.get(sch.get_loops(vector_local)[-1])
+    assert vector_load_loop.kind == tvm.tirx.ForKind.VECTORIZED
+
+
 def test_gemv_broadcast_epilogue():
     # fmt: off
     @T.prim_func(private=True, s_tir=True)

Reply via email to