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

ruihangl 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 51fa5763aa [Unity][WebGPU] Move NDArrayCache Support to relax runtime 
(#14689)
51fa5763aa is described below

commit 51fa5763aa98ac7938cf8c6951c9908abce9364b
Author: Tianqi Chen <[email protected]>
AuthorDate: Fri Apr 21 16:52:32 2023 -0400

    [Unity][WebGPU] Move NDArrayCache Support to relax runtime (#14689)
    
    [WebGPU] Move NDArrayCache Support to relax runtime
    
    This PR moves NDArrayCache support to relax runtime so
    it can be used in other native settings.
---
 python/tvm/contrib/tvmjs.py                   |   2 +-
 src/runtime/relax_vm/ndarray_cache_support.cc | 204 ++++++++++++++++++++++++++
 tests/python/relax/test_runtime_builtin.py    |  22 +++
 web/Makefile                                  |   3 +-
 web/emcc/wasm_runtime.cc                      |  80 +---------
 web/src/runtime.ts                            |  10 +-
 6 files changed, 235 insertions(+), 86 deletions(-)

diff --git a/python/tvm/contrib/tvmjs.py b/python/tvm/contrib/tvmjs.py
index 48fac1c66d..6bee154e96 100644
--- a/python/tvm/contrib/tvmjs.py
+++ b/python/tvm/contrib/tvmjs.py
@@ -186,7 +186,7 @@ def dump_ndarray_cache(
             v = v.numpy()
 
         # prefer to preserve original dtype, especially if the format was 
bfloat16
-        dtype = str(origin_v.dtype) if isinstance(origin_v, tvm.nd.NDArray) 
else v.dtype
+        dtype = str(origin_v.dtype) if isinstance(origin_v, tvm.nd.NDArray) 
else str(v.dtype)
 
         # convert fp32 to bf16
         if encode_format == "f32-to-bf16" and dtype == "float32":
diff --git a/src/runtime/relax_vm/ndarray_cache_support.cc 
b/src/runtime/relax_vm/ndarray_cache_support.cc
new file mode 100644
index 0000000000..311681228e
--- /dev/null
+++ b/src/runtime/relax_vm/ndarray_cache_support.cc
@@ -0,0 +1,204 @@
+/*
+ * 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/ndarray_cache_support.cc
+ * \brief Runtime to support ndarray cache file loading.
+ *
+ * This file provides a minimum support for ndarray cache file loading.
+ *
+ * The main focus of this implementation is to enable loading
+ * with minimum set of intermediate files while also being
+ * compatible to some of the multi-shard files that are more
+ * friendly in some of the environments.
+ *
+ * NDArray cache also provides a way to do system-wide
+ * parameter sharing across multiple VMs.
+ *
+ * There are likely other ways to load the parameters ndarray-ache.
+ * We will keep the impact minimum by puting it as a private
+ * runtime builtin provide as in this file.
+ */
+#define PICOJSON_USE_INT64
+
+#include <picojson.h>
+#include <tvm/runtime/ndarray.h>
+#include <tvm/runtime/registry.h>
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "../../support/utils.h"
+#include "../file_utils.h"
+
+namespace tvm {
+namespace runtime {
+namespace relax_vm {
+
+/*!
+ * A NDArray cache to store pre-loaded arrays in the system.
+ */
+class NDArrayCache {
+ public:
+  static NDArrayCache* Global() {
+    static NDArrayCache* inst = new NDArrayCache();
+    return inst;
+  }
+
+  static void Update(String name, NDArray arr, bool override) {
+    NDArrayCache* pool = Global();
+    if (!override) {
+      ICHECK_EQ(pool->pool_.count(name), 0) << "Name " << name << " already 
exists in the cache";
+    }
+    pool->pool_.Set(name, arr);
+  }
+
+  static Optional<NDArray> Get(String name) {
+    NDArrayCache* pool = Global();
+    auto it = pool->pool_.find(name);
+    if (it != pool->pool_.end()) {
+      return (*it).second;
+    } else {
+      return NullOpt;
+    }
+  }
+
+  static void Remove(String name) {
+    NDArrayCache* pool = Global();
+    pool->pool_.erase(name);
+  }
+
+  static void Clear() { Global()->pool_.clear(); }
+
+  /*!
+   * \brief Load parameters from path and append them.
+   *
+   * \param cache_path The cache to path.
+   * \param device_type The type of device to be loaded.
+   * \param device_id The device id.
+   */
+  static void Load(const std::string& cache_path, int device_type, int 
device_id) {
+    DLDevice device{static_cast<DLDeviceType>(device_type), device_id};
+    std::string json_str;
+    LoadBinaryFromFile(cache_path + "/ndarray-cache.json", &json_str);
+    picojson::value json_info;
+    picojson::parse(json_info, json_str);
+    auto shard_records = 
json_info.get<picojson::object>()["records"].get<picojson::array>();
+
+    Map<String, NDArray> result;
+
+    for (auto shard_item : shard_records) {
+      auto shard_rec = shard_item.get<picojson::object>();
+      ICHECK(shard_rec["dataPath"].is<std::string>());
+      std::string data_path = shard_rec["dataPath"].get<std::string>();
+
+      std::string raw_data;
+      LoadBinaryFromFile(cache_path + "/" + data_path, &raw_data);
+      CHECK_EQ(shard_rec["format"].get<std::string>(), "raw-shard");
+      int64_t raw_nbytes = shard_rec["nbytes"].get<int64_t>();
+      CHECK_EQ(raw_nbytes, raw_data.length());
+
+      for (auto nd_item : shard_rec["records"].get<picojson::array>()) {
+        auto nd_rec = nd_item.get<picojson::object>();
+        CHECK(nd_rec["name"].is<std::string>());
+        String name = nd_rec["name"].get<std::string>();
+
+        std::vector<int64_t> shape;
+        for (auto value : nd_rec["shape"].get<picojson::array>()) {
+          shape.push_back(value.get<int64_t>());
+        }
+
+        DataType dtype(String2DLDataType(nd_rec["dtype"].get<std::string>()));
+        std::string encode_format = nd_rec["format"].get<std::string>();
+        int64_t offset = nd_rec["byteOffset"].get<int64_t>();
+        int64_t nbytes = nd_rec["nbytes"].get<int64_t>();
+        NDArray arr = NDArray::Empty(ShapeTuple(shape.begin(), shape.end()), 
dtype, device);
+
+        if (dtype == DataType::Float(32) && encode_format == "f32-to-bf16") {
+          // decode bf16 to f32
+          std::vector<uint16_t> buffer(nbytes / 2);
+          std::vector<uint32_t> decoded(nbytes / 2);
+          std::memcpy(buffer.data(), raw_data.data() + offset, nbytes);
+          for (size_t i = 0; i < buffer.size(); ++i) {
+            decoded[i] = static_cast<uint32_t>(buffer[i]) << 16;
+          }
+          arr.CopyFromBytes(decoded.data(), decoded.size() * sizeof(uint32_t));
+        } else {
+          arr.CopyFromBytes(raw_data.data() + offset, nbytes);
+        }
+        Update(name, arr, true);
+      }
+    }
+  }
+
+ private:
+  Map<String, NDArray> pool_;
+};
+
+TVM_REGISTER_GLOBAL("vm.builtin.ndarray_cache.get").set_body_typed(NDArrayCache::Get);
+TVM_REGISTER_GLOBAL("vm.builtin.ndarray_cache.update").set_body_typed(NDArrayCache::Update);
+TVM_REGISTER_GLOBAL("vm.builtin.ndarray_cache.remove").set_body_typed(NDArrayCache::Remove);
+TVM_REGISTER_GLOBAL("vm.builtin.ndarray_cache.clear").set_body_typed(NDArrayCache::Clear);
+TVM_REGISTER_GLOBAL("vm.builtin.ndarray_cache.load").set_body_typed(NDArrayCache::Load);
+
+// This param module node can be useful to get param dict in RPC mode
+// when the remote already have loaded parameters from file.
+class ParamModuleNode : public runtime::ModuleNode {
+ public:
+  const char* type_key() const final { return "param_module"; }
+
+  PackedFunc GetFunction(const std::string& name, const ObjectPtr<Object>& 
sptr_to_self) final {
+    if (name == "get_params") {
+      auto params = params_;
+      return PackedFunc([params](TVMArgs args, TVMRetValue* rv) { *rv = 
params; });
+    } else {
+      return PackedFunc();
+    }
+  }
+
+  static Array<NDArray> GetParams(const std::string& prefix, int num_params) {
+    Array<NDArray> params;
+    for (int i = 0; i < num_params; ++i) {
+      std::string name = prefix + "_" + std::to_string(i);
+      auto opt = NDArrayCache::Get(name);
+      if (opt) {
+        params.push_back(opt.value());
+      } else {
+        LOG(FATAL) << "Cannot find " << name << " in cache";
+      }
+    }
+    return params;
+  }
+
+  static Module Create(const std::string& prefix, int num_params) {
+    auto n = make_object<ParamModuleNode>();
+    n->params_ = GetParams(prefix, num_params);
+    return Module(n);
+  }
+
+ private:
+  Array<NDArray> params_;
+};
+
+TVM_REGISTER_GLOBAL("vm.builtin.param_module_from_cache").set_body_typed(ParamModuleNode::Create);
+TVM_REGISTER_GLOBAL("vm.builtin.param_array_from_cache").set_body_typed(ParamModuleNode::GetParams);
+
+}  // namespace relax_vm
+}  // namespace runtime
+}  // namespace tvm
diff --git a/tests/python/relax/test_runtime_builtin.py 
b/tests/python/relax/test_runtime_builtin.py
index 6ba06d0693..f4ab3a2b54 100644
--- a/tests/python/relax/test_runtime_builtin.py
+++ b/tests/python/relax/test_runtime_builtin.py
@@ -16,6 +16,8 @@
 # under the License.
 import tvm
 import tvm.testing
+from tvm.contrib import tvmjs, utils
+
 import pytest
 import numpy as np
 
@@ -166,5 +168,25 @@ def test_attention_kv_cache():
         assert res[i][1] == i
 
 
+def test_ndarray_cache():
+    fload = tvm.get_global_func("vm.builtin.ndarray_cache.load")
+    fget_params = tvm.get_global_func("vm.builtin.param_array_from_cache")
+
+    param_dict = {
+        "x_0": np.array([1, 2, 3], dtype="int32"),
+        "x_1": np.random.uniform(size=[10, 20]).astype("float32"),
+    }
+
+    temp = utils.tempdir()
+    tvmjs.dump_ndarray_cache(param_dict, temp.path, 
encode_format="f32-to-bf16")
+    fload(str(temp.path), tvm.cpu().device_type, 0)
+    res = fget_params("x", 2)
+    for i, v in enumerate(res):
+        v_np = param_dict[f"x_{i}"]
+        if v_np.dtype == "float32":
+            v_np = tvmjs._convert_bf16_to_f32(tvmjs._convert_f32_to_bf16(v_np))
+        np.testing.assert_allclose(v.numpy(), v_np, atol=1e-6, rtol=1e-6)
+
+
 if __name__ == "__main__":
     tvm.testing.main()
diff --git a/web/Makefile b/web/Makefile
index 026cb7ebc0..af146b2329 100644
--- a/web/Makefile
+++ b/web/Makefile
@@ -18,7 +18,8 @@
 TVM_ROOT=$(shell cd ..; pwd)
 
 INCLUDE_FLAGS = -I$(TVM_ROOT) -I$(TVM_ROOT)/include\
-       -I$(TVM_ROOT)/3rdparty/dlpack/include 
-I$(TVM_ROOT)/3rdparty/dmlc-core/include -I$(TVM_ROOT)/3rdparty/compiler-rt
+       -I$(TVM_ROOT)/3rdparty/dlpack/include 
-I$(TVM_ROOT)/3rdparty/dmlc-core/include\
+       -I$(TVM_ROOT)/3rdparty/compiler-rt -I$(TVM_ROOT)/3rdparty/picojson
 
 .PHONY: clean all rmtypedep preparetest
 
diff --git a/web/emcc/wasm_runtime.cc b/web/emcc/wasm_runtime.cc
index 2412fb8d2d..5209a5899e 100644
--- a/web/emcc/wasm_runtime.cc
+++ b/web/emcc/wasm_runtime.cc
@@ -58,6 +58,7 @@
 #include "src/runtime/relax_vm/executable.cc"
 #include "src/runtime/relax_vm/lm_support.cc"
 #include "src/runtime/relax_vm/memory_manager.cc"
+#include "src/runtime/relax_vm/ndarray_cache_support.cc"
 #include "src/runtime/relax_vm/vm.cc"
 
 // --- Implementations of backend and wasm runtime API. ---
@@ -120,50 +121,6 @@ 
TVM_REGISTER_GLOBAL("testing.object_use_count").set_body([](TVMArgs args, TVMRet
   *ret = (obj.use_count() - 1);
 });
 
-/*!
- * A NDArray cache to store pre-loaded arrays in the system.
- */
-class NDArrayCache {
- public:
-  static NDArrayCache* Global() {
-    static NDArrayCache* inst = new NDArrayCache();
-    return inst;
-  }
-
-  static void Update(String name, NDArray arr, bool override) {
-    NDArrayCache* pool = Global();
-    if (!override) {
-      ICHECK_EQ(pool->pool_.count(name), 0) << "Name " << name << " already 
exists in the cache";
-    }
-    pool->pool_.Set(name, arr);
-  }
-
-  static Optional<NDArray> Get(String name) {
-    NDArrayCache* pool = Global();
-    auto it = pool->pool_.find(name);
-    if (it != pool->pool_.end()) {
-      return (*it).second;
-    } else {
-      return NullOpt;
-    }
-  }
-
-  static void Remove(String name) {
-    NDArrayCache* pool = Global();
-    pool->pool_.erase(name);
-  }
-
-  static void Clear() { Global()->pool_.clear(); }
-
- private:
-  Map<String, NDArray> pool_;
-};
-
-TVM_REGISTER_GLOBAL("tvmjs.ndarray_cache.get").set_body_typed(NDArrayCache::Get);
-TVM_REGISTER_GLOBAL("tvmjs.ndarray_cache.update").set_body_typed(NDArrayCache::Update);
-TVM_REGISTER_GLOBAL("tvmjs.ndarray_cache.remove").set_body_typed(NDArrayCache::Remove);
-TVM_REGISTER_GLOBAL("tvmjs.ndarray_cache.clear").set_body_typed(NDArrayCache::Clear);
-
 void ArrayDecodeStorage(NDArray cpu_arr, std::string bytes, std::string 
format) {
   if (format == "f32-to-bf16") {
     std::vector<uint16_t> buffer(bytes.length() / 2);
@@ -186,40 +143,5 @@ void ArrayDecodeStorage(NDArray cpu_arr, std::string 
bytes, std::string format)
 }
 
 
TVM_REGISTER_GLOBAL("tvmjs.array.decode_storage").set_body_typed(ArrayDecodeStorage);
-
-class ParamModuleNode : public runtime::ModuleNode {
- public:
-  const char* type_key() const final { return "param_module"; }
-
-  PackedFunc GetFunction(const std::string& name, const ObjectPtr<Object>& 
sptr_to_self) final {
-    if (name == "get_params") {
-      auto params = params_;
-      return PackedFunc([params](TVMArgs args, TVMRetValue* rv) { *rv = 
params; });
-    } else {
-      return PackedFunc();
-    }
-  }
-
-  static Module Create(std::string prefix, int num_params) {
-    Array<NDArray> params;
-    for (int i = 0; i < num_params; ++i) {
-      std::string name = prefix + "_" + std::to_string(i);
-      auto opt = NDArrayCache::Get(name);
-      if (opt) {
-        params.push_back(opt.value());
-      } else {
-        LOG(FATAL) << "Cannot find " << name << " in cache";
-      }
-    }
-    auto n = make_object<ParamModuleNode>();
-    n->params_ = params;
-    return Module(n);
-  }
-
- private:
-  Array<NDArray> params_;
-};
-
-TVM_REGISTER_GLOBAL("tvmjs.param_module_from_cache").set_body_typed(ParamModuleNode::Create);
 }  // namespace runtime
 }  // namespace tvm
diff --git a/web/src/runtime.ts b/web/src/runtime.ts
index 26ba1ecd67..3b77aba88f 100644
--- a/web/src/runtime.ts
+++ b/web/src/runtime.ts
@@ -161,12 +161,12 @@ class RuntimeContext implements Disposable {
     this.arrayGetSize = getGlobalFunc("runtime.ArraySize");
     this.arrayMake = getGlobalFunc("runtime.Array");
     this.getSysLib = getGlobalFunc("runtime.SystemLib");
-    this.arrayCacheGet = getGlobalFunc("tvmjs.ndarray_cache.get");
-    this.arrayCacheRemove = getGlobalFunc("tvmjs.ndarray_cache.remove");
-    this.arrayCacheUpdate = getGlobalFunc("tvmjs.ndarray_cache.update");
-    this.arrayCacheClear = getGlobalFunc("tvmjs.ndarray_cache.clear");
+    this.arrayCacheGet = getGlobalFunc("vm.builtin.ndarray_cache.get");
+    this.arrayCacheRemove = getGlobalFunc("vm.builtin.ndarray_cache.remove");
+    this.arrayCacheUpdate = getGlobalFunc("vm.builtin.ndarray_cache.update");
+    this.arrayCacheClear = getGlobalFunc("vm.builtin.ndarray_cache.clear");
     this.arrayDecodeStorage = getGlobalFunc("tvmjs.array.decode_storage");
-    this.paramModuleFromCache = getGlobalFunc("tvmjs.param_module_from_cache");
+    this.paramModuleFromCache = 
getGlobalFunc("vm.builtin.param_module_from_cache");
     this.makeShapeTuple = getGlobalFunc("runtime.ShapeTuple");
     this.ndarrayCreateView = getGlobalFunc("runtime.TVMArrayCreateView");
     this.sampleTopPFromLogits = 
getGlobalFunc("vm.builtin.sample_top_p_from_logits");

Reply via email to