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

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


The following commit(s) were added to refs/heads/unity by this push:
     new aec773d690 [Unity][VM] Add Attention KV cache builtin (#14478)
aec773d690 is described below

commit aec773d6908924288a722b04078faf6de55e9ac0
Author: Tianqi Chen <[email protected]>
AuthorDate: Tue Apr 4 11:44:57 2023 -0400

    [Unity][VM] Add Attention KV cache builtin (#14478)
    
    This PR provides a simple implementation of inplace attention
    kv cache for relax runtime. The main goal here is to help
    us enable auto-regressive decoding quickly in relax.
    
    This is likely not the only way to support attention kv-cache.
    We keep the implementation private for now and will
    continue to evolve the relevant code.
---
 src/runtime/relax_vm/attention_kv_cache.cc | 162 +++++++++++++++++++++++++++++
 tests/python/relax/test_pipeline.py        |  58 ++++++++++-
 tests/python/relax/test_runtime_builtin.py |  17 +++
 web/emcc/wasm_runtime.cc                   |   1 +
 4 files changed, 237 insertions(+), 1 deletion(-)

diff --git a/src/runtime/relax_vm/attention_kv_cache.cc 
b/src/runtime/relax_vm/attention_kv_cache.cc
new file mode 100644
index 0000000000..f94b233975
--- /dev/null
+++ b/src/runtime/relax_vm/attention_kv_cache.cc
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*!
+ * \file src/runtime/relax_vm/attention_kv_cache.cc
+ * \brief A simple implementation of inplace attention kv cache for runtime.
+ *
+ * This file provides a simple implementation of inplace attention
+ * kv cache for relax runtime. The main goal here is to help us enable
+ * auto-regressive decoding quickly in relax.
+ *
+ * This is not the only way to support attention kv-cache.
+ * Our support of attention kv-cache can subject to future
+ * changes as we build more LM verticals.
+ *
+ * We will keep the impact minimum by puting it as a private
+ * runtime builtin provide as in this file.
+ *
+ * We can evolve this implementation as we build more LM verticals.
+ */
+
+#include <tvm/runtime/container/shape_tuple.h>
+#include <tvm/runtime/device_api.h>
+#include <tvm/runtime/logging.h>
+#include <tvm/runtime/memory.h>
+#include <tvm/runtime/ndarray.h>
+#include <tvm/runtime/relax_vm/vm.h>
+
+namespace tvm {
+namespace runtime {
+namespace relax_vm {
+
+//-------------------------------------------
+// We keep the implementation private as
+// they may subject to future changes.
+//
+// Users can interact with it through the
+// runtime API function calls
+//-------------------------------------------
+/*!
+ * \brief An object representing an attention kv cache.
+ */
+class AttentionKVCacheObj : public Object {
+ public:
+  /*!
+   * \brief Underlying support data.
+   */
+  NDArray data;
+
+  /*!
+   * \brief number of slots already filled.
+   */
+  int64_t fill_count{0};
+
+  /*!
+   * \brief View all current cached values as one array.
+   * \param shape The cached values.
+   */
+  NDArray View(const ShapeTuple& shape) {
+    CHECK_EQ(shape[0], fill_count) << "Requested shape do not match the filled 
count";
+    for (int i = 1; i < this->data->ndim; ++i) {
+      CHECK_EQ(shape[i], data->shape[i]) << "Dimension " << i << " mismatch";
+    }
+    return data.CreateView(shape, data->dtype);
+  }
+
+  /*!
+   * \brief Append value to the cache.
+   * \param value The value to be appended.
+   */
+  void Append(NDArray value) {
+    CHECK(data.DataType() == value.DataType()) << "dtype mismatch";
+    // reallocate cache
+    int64_t reserved_slots = data->shape[0];
+    while (fill_count + value->shape[0] > reserved_slots) {
+      reserved_slots *= 2;
+    }
+    if (reserved_slots != data->shape[0]) {
+      std::vector<int64_t> new_shape(data->shape, data->shape + data->ndim);
+      new_shape[0] = reserved_slots;
+      NDArray new_data = NDArray::Empty(new_shape, data->dtype, data->device);
+      new_data.CreateView(data.Shape(), data->dtype).CopyFrom(data);
+      this->data = new_data;
+    }
+    // copy into the fill count position.
+    ICHECK_LE(fill_count + value->shape[0], data->shape[0]);
+    ICHECK(data.IsContiguous());
+
+    int64_t num_filled_elements = fill_count;
+    for (int i = 1; i < data->ndim; ++i) {
+      CHECK_EQ(value->shape[i], data->shape[i]) << "Dimension " << i << " 
mismatch";
+      num_filled_elements *= data->shape[i];
+    }
+    // create a view of copy dest to copy the value into it.
+    DLTensor copy_dst = *(data.operator->());
+    copy_dst.byte_offset = num_filled_elements * ((data->dtype.bits * 
data->dtype.lanes + 7) / 8);
+    copy_dst.shape = value->shape;
+    NDArray::CopyFromTo(value.operator->(), &copy_dst);
+    this->fill_count += value->shape[0];
+  }
+
+  static constexpr const uint32_t _type_index = TypeIndex::kDynamic;
+  static constexpr const char* _type_key = "relax.vm.AttentionKVCache";
+  TVM_DECLARE_FINAL_OBJECT_INFO(AttentionKVCacheObj, Object);
+};
+
+/*! \brief reference to closure. */
+class AttentionKVCache : public ObjectRef {
+ public:
+  /*!
+   * \brief Create the attention kv cache.
+   * \param init_reserve The initial reserved.
+   */
+  static AttentionKVCache Create(NDArray init_data) {
+    auto n = make_object<AttentionKVCacheObj>();
+    n->data = std::move(init_data);
+    n->fill_count = 0;
+    return AttentionKVCache(n);
+  }
+
+  TVM_DEFINE_MUTABLE_OBJECT_REF_METHODS(AttentionKVCache, ObjectRef, 
AttentionKVCacheObj);
+};
+
+TVM_REGISTER_OBJECT_TYPE(AttentionKVCacheObj);
+
+//-------------------------------------------------
+//  Register runtime functions
+//-------------------------------------------------
+TVM_REGISTER_GLOBAL("vm.builtin.attention_kv_cache_create")
+    .set_body_typed(AttentionKVCache::Create);
+
+AttentionKVCache AttentionKVCacheAppend(AttentionKVCache cache, NDArray value) 
{
+  cache->Append(value);
+  return cache;
+}
+
+TVM_REGISTER_GLOBAL("vm.builtin.attention_kv_cache_append").set_body_typed(AttentionKVCacheAppend);
+
+NDArray AttentionKVCacheView(AttentionKVCache cache, ShapeTuple shape) {
+  return cache->View(shape);
+}
+
+TVM_REGISTER_GLOBAL("vm.builtin.attention_kv_cache_view").set_body_typed(AttentionKVCacheView);
+
+}  // namespace relax_vm
+}  // namespace runtime
+}  // namespace tvm
diff --git a/tests/python/relax/test_pipeline.py 
b/tests/python/relax/test_pipeline.py
index c66066f8f8..3c97d6b701 100644
--- a/tests/python/relax/test_pipeline.py
+++ b/tests/python/relax/test_pipeline.py
@@ -16,8 +16,9 @@
 # under the License.
 import numpy as np
 import tvm
+import tvm.testing
 from tvm import relax
-from tvm.script import relax as R
+from tvm.script import relax as R, tir as T
 
 
 def test_pipeline_compile():
@@ -43,3 +44,58 @@ def test_pipeline_compile():
     vm = relax.VirtualMachine(ex, tvm.cpu())
     z = vm["main"](x, y)
     tvm.testing.assert_allclose(z.numpy(), x_np + y_np, rtol=1e-7, atol=1e-7)
+
+
+def test_pipeline_with_kv_cache():
+    """A dummy pipline that simulates KV update."""
+    pipeline = relax.get_pipeline()
+
+    @tvm.script.ir_module
+    class Mod:
+        @R.function
+        def main(
+            x: R.Tensor((1, 4), "float32"),
+            y: R.Tensor((1, 4), "float32"),
+            shape: R.Shape(["L", 4]),
+            kv_cache: R.Object,
+        ):
+            L = T.int64()
+            # computation of the current value
+            curr_value = R.add(x, y)
+            # update cache
+            kv_cache = R.call_packed(
+                "vm.builtin.attention_kv_cache_append", kv_cache, curr_value, 
sinfo_args=[R.Object]
+            )
+            # return the updated cache view
+            kv = R.call_packed(
+                "vm.builtin.attention_kv_cache_view",
+                kv_cache,
+                shape,
+                sinfo_args=[R.Tensor((L, 4), "float32")],
+            )
+            return (kv, kv_cache)
+
+    mod = Mod
+    mod = pipeline(mod)
+
+    target = tvm.target.Target("llvm", host="llvm")
+
+    ex = relax.build(mod, target)
+
+    num_steps = 8
+    cache_np = np.empty((num_steps, 4), dtype="float32")
+
+    fcreate_cache = tvm.get_global_func("vm.builtin.attention_kv_cache_create")
+    kv_cache = fcreate_cache(tvm.nd.empty((2, 4), device=tvm.cpu(), 
dtype="float32"))
+
+    vm = relax.VirtualMachine(ex, tvm.cpu())
+    for i in range(num_steps):
+        x_np = np.random.rand(1, 4).astype(np.float32)
+        y_np = np.random.rand(1, 4).astype(np.float32)
+        x = tvm.nd.array(x_np)
+        y = tvm.nd.array(y_np)
+        np_shape = (i + 1, 4)
+        kv, kv_cache = vm["main"](x, y, tvm.runtime.ShapeTuple(np_shape), 
kv_cache)
+
+        cache_np[i, :] = x_np + y_np
+        tvm.testing.assert_allclose(kv.numpy(), cache_np[: np_shape[0], :], 
rtol=1e-7, atol=1e-7)
diff --git a/tests/python/relax/test_runtime_builtin.py 
b/tests/python/relax/test_runtime_builtin.py
index b4ba54b455..bb513eb357 100644
--- a/tests/python/relax/test_runtime_builtin.py
+++ b/tests/python/relax/test_runtime_builtin.py
@@ -15,6 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 import tvm
+import tvm.testing
 import pytest
 import numpy as np
 
@@ -149,5 +150,21 @@ def test_tuple_getitem():
     assert tuple_getitem(t, 1) == y
 
 
+def test_attention_kv_cache():
+    fcreate = tvm.get_global_func("vm.builtin.attention_kv_cache_create")
+    fappend = tvm.get_global_func("vm.builtin.attention_kv_cache_append")
+    fview = tvm.get_global_func("vm.builtin.attention_kv_cache_view")
+
+    cache = fcreate(tvm.nd.empty((2, 2), dtype="int32"))
+    num_steps = 0
+    for i in range(num_steps):
+        cache = fappend(cache, tvm.nd.array(i * np.ones((1, 
2).astype("int32"))))
+
+    res = fview(cache, tvm.runtime.ShapeTuple((num_steps, 2))).numpy()
+    for i in range(num_steps):
+        assert res[i][0] == i
+        assert res[i][1] == i
+
+
 if __name__ == "__main__":
     tvm.testing.main()
diff --git a/web/emcc/wasm_runtime.cc b/web/emcc/wasm_runtime.cc
index 8f16365eee..9b2b6c180c 100644
--- a/web/emcc/wasm_runtime.cc
+++ b/web/emcc/wasm_runtime.cc
@@ -53,6 +53,7 @@
 #include "src/runtime/system_library.cc"
 #include "src/runtime/workspace_pool.cc"
 // relax setup
+#include "src/runtime/relax_vm/attention_kv_cache.cc"
 #include "src/runtime/relax_vm/builtin.cc"
 #include "src/runtime/relax_vm/bytecode.cc"
 #include "src/runtime/relax_vm/executable.cc"

Reply via email to