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

tqchen 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 aca96b3358 [Refactor][Runtime] Unify tensor pools and VM ownership 
(#20670)
aca96b3358 is described below

commit aca96b3358e9b7eb2b4568be8f2a58c1c4bf440e
Author: Tianqi Chen <[email protected]>
AuthorDate: Mon Oct 12 11:59:57 2026 -0400

    [Refactor][Runtime] Unify tensor pools and VM ownership (#20670)
---
 CMakeLists.txt                                     |   1 -
 apps/android_rpc/app/src/main/jni/tvm_runtime.h    |   1 -
 docs/arch/relax_vm.rst                             |   6 +-
 include/tvm/runtime/device_api.h                   |  25 ++
 include/tvm/runtime/disco/cuda_ipc_memory.h        |   6 +-
 include/tvm/runtime/memory/memory_manager.h        | 199 --------------
 include/tvm/runtime/tensor.h                       | 171 ++++++++++++
 include/tvm/runtime/vm/vm.h                        |  18 +-
 python/tvm/rpc/remote.py                           |   8 +-
 python/tvm/runtime/vm.py                           |  33 +--
 src/backend/cuda/runtime/cuda_device_api.cc        |  21 ++
 src/backend/hexagon/runtime/hexagon_device_api.cc  |  18 ++
 src/backend/hexagon/runtime/hexagon_device_api.h   |  17 +-
 src/backend/metal/runtime/metal_common.h           |   7 +-
 src/backend/metal/runtime/metal_device_api.mm      |   8 +
 src/backend/opencl/runtime/opencl_common.h         |  24 +-
 src/backend/opencl/runtime/opencl_device_api.cc    | 294 +++++++++++----------
 src/backend/rocm/runtime/rocm_common.h             |   5 +
 src/backend/rocm/runtime/rocm_device_api.cc        |  15 +-
 src/backend/vulkan/runtime/vulkan_device_api.cc    |  12 +
 src/backend/vulkan/runtime/vulkan_device_api.h     |  11 +
 src/relax/backend/vm/codegen_vm.cc                 |   1 +
 src/runtime/cpu_device_api.cc                      |  22 ++
 src/runtime/device_api.cc                          |  41 ++-
 .../extra/contrib/nvshmem/memory_allocator.cc      |  82 +++---
 src/runtime/extra/disco/builtin.cc                 |   6 +-
 .../extra/disco/cuda_ipc/cuda_ipc_memory.cc        |  94 +++----
 .../extra/disco/cuda_ipc/custom_allreduce.cc       |   1 -
 src/runtime/memory/memory_manager.cc               | 278 -------------------
 src/runtime/memory/naive_allocator.h               | 143 ----------
 src/runtime/memory/pooled_allocator.h              | 176 ------------
 src/runtime/tensor.cc                              |  11 +
 src/runtime/vm/builtin.cc                          |  64 +++--
 src/runtime/vm/lm_support.cc                       |   1 -
 src/runtime/vm/paged_kv_cache.cc                   |   1 -
 src/runtime/vm/vm.cc                               |  57 ++--
 src/runtime/workspace_pool.cc                      | 121 +++------
 src/runtime/workspace_pool.h                       |  19 +-
 tests/python/disco/test_custom_allreduce.py        |   4 +-
 .../relax/test_vm_alloc_storage_with_scope.py      |   5 +-
 tests/python/relax/test_vm_execbuilder.py          |   4 +-
 web/emcc/wasm_runtime.cc                           |   1 -
 web/src/runtime.ts                                 |   7 -
 43 files changed, 755 insertions(+), 1284 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9ec883f229..6070108922 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -368,7 +368,6 @@ list(APPEND COMPILER_SRCS ${CODEGEN_SRCS})
 tvm_file_glob(GLOB RUNTIME_SRCS
   src/runtime/*.cc
   src/runtime/vm/*.cc
-  src/runtime/memory/*.cc
   src/runtime/rpc/minrpc/*.cc
 )
 # Note: src/runtime/extra/disco/** moves to libtvm_runtime_extra.
diff --git a/apps/android_rpc/app/src/main/jni/tvm_runtime.h 
b/apps/android_rpc/app/src/main/jni/tvm_runtime.h
index ea9c7eb278..fa08f82e31 100644
--- a/apps/android_rpc/app/src/main/jni/tvm_runtime.h
+++ b/apps/android_rpc/app/src/main/jni/tvm_runtime.h
@@ -47,7 +47,6 @@
 #include "../src/runtime/device_api.cc"
 #include "../src/runtime/file_utils.cc"
 #include "../src/runtime/logging.cc"
-#include "../src/runtime/memory/memory_manager.cc"
 #include "../src/runtime/registry.cc"
 #include "../src/runtime/rpc/rpc_channel.cc"
 #include "../src/runtime/rpc/rpc_endpoint.cc"
diff --git a/docs/arch/relax_vm.rst b/docs/arch/relax_vm.rst
index 91ca6d107a..26ced6469d 100644
--- a/docs/arch/relax_vm.rst
+++ b/docs/arch/relax_vm.rst
@@ -225,9 +225,9 @@ At runtime, a ``VirtualMachine`` is created and initialized:
 Under the hood:
 
 1. **LoadExecutable**: the bytecode and metadata are loaded from the 
``VMExecutable``.
-2. **Init**: devices and memory allocators are set up. Each device gets an 
``Allocator``
-   (either ``NAIVE_ALLOCATOR`` or ``POOLED_ALLOCATOR``, defaulting to pooled). 
A CPU device
-   is always added for shape computations.
+2. **Init**: devices and owning tensor pools are set up. Each device gets a
+   ``TensorMemoryPool`` from the shared device factory. A CPU device is always
+   added for shape computations.
 3. **InitFuncPool**: the function pool is populated — ``kPackedFunc`` entries 
are resolved from
    imports or the global registry; ``kVMFunc`` entries are wrapped in
    ``VMClosure`` objects.
diff --git a/include/tvm/runtime/device_api.h b/include/tvm/runtime/device_api.h
index 124dcdd14c..19d420d53a 100644
--- a/include/tvm/runtime/device_api.h
+++ b/include/tvm/runtime/device_api.h
@@ -203,6 +203,31 @@ class TVM_RUNTIME_DLL DeviceAPI {
    * \param event_dst The destination stream to synchronize.
    */
   virtual void SyncStreamFromTo(Device dev, TVMStreamHandle event_src, 
TVMStreamHandle event_dst);
+  /*!
+   * \brief Allocate temporal workspace for backend execution.
+   *
+   *  \note We have the following assumption about backend temporal
+   *   workspace allocation, and backend will optimize for such assumption:
+   *
+   *  - Only a few allocation will happen, and space will be released after 
use.
+   *  - The release order is usually in reverse order of allocate (stack 
style).
+   *  - Repeative pattern of same allocations over different runs.
+   *  - Workspace should not overlap between different threads(i.e. be 
threadlocal)
+   *
+   * \param dev The device of allocation.
+   * \param nbytes The size to be allocated.
+   * \param type_hint The type of elements. Only needed by certain backends 
such
+   * as OpenGL, as nbytes is sufficient for most backends.
+   */
+  virtual void* AllocWorkspace(Device dev, size_t nbytes, DLDataType type_hint 
= {});
+  /*!
+   * \brief Free temporal workspace in backend execution.
+   *
+   * \param dev The device of allocation.
+   * \param ptr The pointer to be freed.
+   */
+  virtual void FreeWorkspace(Device dev, void* ptr);
+
   /*!
    * \brief Get device API based on device.
    * \param dev The device
diff --git a/include/tvm/runtime/disco/cuda_ipc_memory.h 
b/include/tvm/runtime/disco/cuda_ipc_memory.h
index 38fe61a014..346fe09831 100644
--- a/include/tvm/runtime/disco/cuda_ipc_memory.h
+++ b/include/tvm/runtime/disco/cuda_ipc_memory.h
@@ -21,7 +21,7 @@
 #define TVM_RUNTIME_DISCO_CUDA_IPC_MEMORY_H_
 
 #include <tvm/runtime/base.h>
-#include <tvm/runtime/memory/memory_manager.h>
+#include <tvm/runtime/tensor.h>
 
 #include <vector>
 
@@ -79,8 +79,8 @@ class CUDAIPCMemoryObj : public ffi::Object {
  */
 class CUDAIPCMemory : public ffi::ObjectRef {
  public:
-  /*! \brief Get the global singleton CUDAIPCMemory allocator. */
-  TVM_RUNTIME_DLL static memory::Allocator* GlobalAllocator();
+  /*! \brief Get the global singleton CUDAIPCMemory pool. */
+  TVM_RUNTIME_DLL static TensorMemoryPool* GlobalMemoryPool();
   /*!
    * \brief Given a local CUDA data pointer, return the CUDAIPCMemory object 
of the pointer.
    * \note The pointer's CUDAIPCMemory is expected to have been allocated
diff --git a/include/tvm/runtime/memory/memory_manager.h 
b/include/tvm/runtime/memory/memory_manager.h
deleted file mode 100644
index cc499481d2..0000000000
--- a/include/tvm/runtime/memory/memory_manager.h
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * 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 tvm/runtime/memory/memory_manager.h
- * \brief Abstract device memory management API
- */
-#ifndef TVM_RUNTIME_MEMORY_MEMORY_MANAGER_H_
-#define TVM_RUNTIME_MEMORY_MEMORY_MANAGER_H_
-
-#include <tvm/runtime/base.h>
-#include <tvm/runtime/tensor.h>
-
-#include <functional>
-#include <memory>
-#include <mutex>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-namespace tvm {
-namespace runtime {
-namespace memory {
-
-enum AllocatorType {
-  kNaive = 1,
-  kPooled,
-};
-
-struct Buffer {
-  /*! \brief The pointer to the allocated block of memory. */
-  void* data{nullptr};
-  /*! \brief The size of the block. */
-  size_t size{0};
-  /*! \brief The context of the allocated buffers. */
-  Device device;
-  /*! \brief The allocator that created this buffer. */
-  AllocatorType alloc_type;
-};
-
-class TVM_RUNTIME_DLL Allocator {
- public:
-  explicit Allocator(AllocatorType type) : type_(type) {}
-  virtual ~Allocator() = default;
-  /*! \brief Allocate an empty Tensor using from the allocator.
-   *  \param shape The shape of the Tensor.
-   *  \param dtype The datatype of the Tensor.
-   *  \param dev The device where the array is allocated.
-   *  \param mem_scope The device memory scope hint.
-   *  \return The empty Tensor.
-   */
-  ffi::Tensor Empty(ffi::Shape shape, DLDataType dtype, Device dev,
-                    ffi::Optional<ffi::String> mem_scope = std::nullopt);
-  /*! \brief Return the allocator type. */
-  inline AllocatorType type() const { return type_; }
-  /*! \brief Allocate a buffer given a size, alignment and type.
-   *  \param dev The device where the array is allocated.
-   *  \param nbytes The size of the buffer.
-   *  \param alignment The alignment of the buffer.
-   *  \param type_hint A type hint to the allocator.
-   *  \return A sized allocation in the form of a buffer.
-   */
-  virtual Buffer Alloc(Device dev, size_t nbytes, size_t alignment, DLDataType 
type_hint) = 0;
-  /*! \brief Allocate a buffer given a shape and type.
-   *  \param dev The device where the array is allocated.
-   *  \param shape The shape of the tensor.
-   *  \param type_hint A type hint to the allocator.
-   *  \param mem_scope A memory scope of the buffer.
-   *  \return A sized allocation in the form of a buffer.
-   */
-  virtual Buffer Alloc(Device dev, ffi::Shape shape, DLDataType type_hint,
-                       const std::string& mem_scope = "");
-
-  /*! \brief Create a view for the buffer given a shape, type and scope.
-   *  \param buffer The existing buffer upon which we need to create a view.
-   *  \param shape The shape of the view.
-   *  \param type_hint A type hint to the view.
-   *  \param mem_scope A memory scope of the view.
-   *  \return A device pointer to the created view.
-   */
-  virtual void* CreateView(const Buffer& buffer, ffi::Shape shape, DLDataType 
type_hint,
-                           const std::string& mem_scope = "global") {
-    return buffer.data;
-  }
-
-  /*! \brief Release the view .
-   *  \param dev is the device where this view is created
-   *  \param data The view pointer to be freed.
-   */
-  virtual void FreeView(Device dev, void* data) {}
-
-  /*! \brief Free a buffer allocated by the allocator.
-   *  \param buffer The buffer to free.
-   */
-  virtual void Free(const Buffer& buffer) = 0;
-  /*! \brief Clear the allocated memory. */
-  virtual void Clear();
-  /*! \brief The amount of memory currently allocated.
-   *  \return The amount of memory currently allocated.
-   */
-  virtual size_t UsedMemory() const = 0;
-
- protected:
-  /*! \brief Check if the given memory scope is allowed to allocate by the 
allocator. */
-  virtual bool AllowMemoryScope(const std::string& mem_scope) const;
-
- private:
-  AllocatorType type_;
-};
-
-class MemoryManager {
- public:
-  TVM_RUNTIME_DLL static MemoryManager* Global();
-  /*!
-   * \brief Get or create an allocator given the context and allocator type.
-   * \param dev The TVM device
-   * \param type The allocator type
-   * \return The memory allocator.
-   */
-  TVM_RUNTIME_DLL static Allocator* GetOrCreateAllocator(Device dev, 
AllocatorType type);
-  /*!
-   * \brief Get an allocator given the context.
-   * \param dev The TVM device
-   * \param type The allocator type
-   * \return The memory allocator.
-   */
-  TVM_RUNTIME_DLL static Allocator* GetAllocator(Device dev, AllocatorType 
type);
-  /*! \brief Clear the allocators. */
-  static void Clear();
-
- private:
-  MemoryManager() {}
-
- protected:
-  std::mutex mu_;
-  std::unordered_map<Device, std::unordered_map<AllocatorType, 
std::unique_ptr<Allocator>>>
-      allocators_;
-};
-
-/*! \brief An object representing a storage allocation. */
-class StorageObj : public ffi::Object {
- public:
-  /*! \brief The index into the VM function table. */
-  Buffer buffer;
-  /*! \brief The allocator where the storage buffer is allocated from. */
-  Allocator* allocator = nullptr;
-
-  /*! \brief Allocate an Tensor from a given piece of storage. */
-  TVM_RUNTIME_DLL ffi::Tensor AllocTensor(int64_t offset, ffi::Shape shape, 
DLDataType dtype);
-
-  /*! \brief Allocate an Tensor with memory scope from a given piece of 
storage. */
-  TVM_RUNTIME_DLL ffi::Tensor AllocTensorScoped(int64_t offset, ffi::Shape 
shape, DLDataType dtype,
-                                                ffi::String scope = "global");
-
-  ~StorageObj() {
-    if (allocator) {
-      allocator->Free(buffer);
-    }
-  }
-
-  static constexpr const bool _type_mutable = true;
-  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("vm.Storage", StorageObj, ffi::Object);
-};
-
-/*! \brief reference to storage. */
-class Storage : public ffi::ObjectRef {
- public:
-  TVM_RUNTIME_DLL explicit Storage(Buffer buffer, Allocator* allocator);
-
-  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(Storage, ffi::ObjectRef, 
StorageObj);
-};
-
-}  // namespace memory
-
-using memory::Allocator;
-using memory::AllocatorType;
-using memory::MemoryManager;
-using memory::StorageObj;
-
-}  // namespace runtime
-}  // namespace tvm
-
-#endif  // TVM_RUNTIME_MEMORY_MEMORY_MANAGER_H_
diff --git a/include/tvm/runtime/tensor.h b/include/tvm/runtime/tensor.h
index ac14fec090..c729bef0d7 100644
--- a/include/tvm/runtime/tensor.h
+++ b/include/tvm/runtime/tensor.h
@@ -24,6 +24,7 @@
 #ifndef TVM_RUNTIME_TENSOR_H_
 #define TVM_RUNTIME_TENSOR_H_
 
+#include <tvm/ffi/cast.h>
 #include <tvm/ffi/container/shape.h>
 #include <tvm/ffi/container/tensor.h>
 #include <tvm/ffi/dtype.h>
@@ -34,8 +35,12 @@
 #include <tvm/support/io.h>
 #include <tvm/support/serializer.h>
 
+#include <algorithm>
 #include <atomic>
 #include <functional>
+#include <limits>
+#include <mutex>
+#include <unordered_map>
 #include <utility>
 #include <vector>
 
@@ -194,6 +199,172 @@ inline Device GetPreferredHostDevice(Device device) {
   }
 }
 
+class TensorMemoryPool;
+
+/*! \brief Create the default pool for a device. */
+TVM_RUNTIME_DLL ffi::ObjectPtr<TensorMemoryPool> 
CreateTensorMemoryPool(DLDevice device);
+
+/*!
+ * \brief A cache of owning device tensors.
+ *
+ * Construct pools with ffi::make_object. Live tensors retain the pool 
directly;
+ * only unpooled backing tensors enter its cache. A backend supplies 
independent
+ * backing owners, never a deleter that calls or retains this pool.
+ *
+ * This class protects host ownership, not device execution. An adapter must 
keep
+ * separate pools for incompatible contexts/streams, or establish device 
ordering
+ * before reuse. Callers must finish using tensors before releasing their 
owners.
+ */
+class TensorMemoryPool : public ffi::Object {
+ public:
+  explicit TensorMemoryPool(size_t page_size = 4096) : page_size_(page_size), 
used_memory_(0) {
+    TVM_FFI_CHECK_GT(page_size_, 0, ValueError) << "Pool page size must be 
positive";
+    TVM_FFI_CHECK_LE(page_size_, 
static_cast<size_t>(std::numeric_limits<int64_t>::max()),
+                     ValueError);
+  }
+
+  virtual ~TensorMemoryPool() { Clear(); }
+
+  /*! \brief Return an owning compact tensor in ordinary global memory. */
+  virtual ffi::Tensor Empty(ffi::ShapeView shape, DLDataType dtype, DLDevice 
device,
+                            ffi::Optional<ffi::String> mem_scope = 
std::nullopt) {
+    std::lock_guard<std::recursive_mutex> lock(mu_);
+    if (mem_scope.has_value() && !(*mem_scope).empty() && *mem_scope != 
"global") {
+      return DeviceAPI::Get(device)->Empty(shape, dtype, device, mem_scope);
+    }
+    size_t nbytes = ffi::GetDataSize(shape.Product(), dtype);
+    TVM_FFI_CHECK_LE(std::max<size_t>(nbytes, 1),
+                     static_cast<size_t>(std::numeric_limits<int64_t>::max()) 
- (page_size_ - 1),
+                     ValueError)
+        << "Rounded tensor allocation size overflows";
+    size_t size = ((std::max<size_t>(nbytes, 1) + page_size_ - 1) / 
page_size_) * page_size_;
+    size_t alignment = kAllocAlignment;
+    size_t element_bytes = (static_cast<size_t>(dtype.bits) * dtype.lanes + 7) 
/ 8;
+    while (alignment < element_bytes) alignment *= 2;
+
+    ffi::Tensor backing;
+    auto it = memory_pool_.find(size);
+    if (it != memory_pool_.end()) {
+      auto& pool = it->second;
+      bool addressable = 
DeviceAPI::Get(device)->SupportsDevicePointerArithmeticsOnHost();
+      auto candidate = std::find_if(pool.begin(), pool.end(), [&](const 
ffi::Tensor& tensor) {
+        return tensor.device().device_type == device.device_type &&
+               tensor.device().device_id == device.device_id &&
+               (!addressable || reinterpret_cast<uintptr_t>(tensor.data_ptr()) 
% alignment == 0);
+      });
+      if (candidate != pool.end()) {
+        backing = std::move(*candidate);
+        pool.erase(candidate);
+      }
+    }
+    if (!backing.defined()) {
+      // Collective backends cannot retry by unilaterally releasing cached 
buffers.
+      // Any allocation-pressure retry belongs to the non-collective adapter.
+      backing = AllocBacking(size, alignment, device);
+      used_memory_.fetch_add(size, std::memory_order_relaxed);
+    }
+    Lease lease{ffi::GetObjectPtr<TensorMemoryPool>(this), std::move(backing)};
+    try {
+      return ffi::Tensor::FromNDAlloc(lease, shape, dtype, device);
+    } catch (...) {
+      // Tensor metadata allocation may fail before the owning tensor exists.
+      Recycle(std::move(lease.backing));
+      throw;
+    }
+  }
+
+  /*! \brief Create a bounded owning view of a backing tensor. */
+  virtual ffi::Tensor CreateView(ffi::Tensor backing, ffi::Shape shape, 
DLDataType dtype,
+                                 uint64_t byte_offset = 0,
+                                 ffi::Optional<ffi::String> mem_scope = 
std::nullopt) {
+    TVM_FFI_CHECK(!mem_scope.has_value() || (*mem_scope).empty() || *mem_scope 
== "global" ||
+                      (backing.device().device_type == kDLHexagon &&
+                       (*mem_scope == "global.vtcm" || *mem_scope == 
"global.ddr")),
+                  ValueError)
+        << "Unsupported tensor view memory scope";
+    auto view = runtime::CreateView(std::move(backing), std::move(shape), 
dtype, byte_offset);
+    if (view.device().device_type == kDLHexagon &&
+        (!mem_scope.has_value() || (*mem_scope).empty() || *mem_scope == 
"global")) {
+      // Hexagon kernels receive the adjusted pointer rather than a byte 
offset.
+      auto* tensor = const_cast<DLTensor*>(view.GetDLTensorPtr());
+      if (tensor->byte_offset != 0) {
+        tensor->data = static_cast<char*>(tensor->data) + tensor->byte_offset;
+      }
+      tensor->byte_offset = 0;
+    }
+    return view;
+  }
+
+  /*! \brief Release cached backing; live tensors remain valid and may return 
later. */
+  void Clear() {
+    std::lock_guard<std::recursive_mutex> lock(mu_);
+    for (const auto& [size, pool] : memory_pool_) {
+      used_memory_.fetch_sub(size * pool.size(), std::memory_order_relaxed);
+    }
+    memory_pool_.clear();
+  }
+
+  /*! \brief Bytes held by this pool, including checked-out backing. */
+  size_t UsedMemory() const { return 
used_memory_.load(std::memory_order_relaxed); }
+
+  static constexpr bool _type_mutable = true;
+  TVM_FFI_DECLARE_OBJECT_INFO("runtime.TensorMemoryPool", TensorMemoryPool, 
ffi::Object);
+
+ protected:
+  /*!
+   * \brief Allocate fresh owning uint8[nbytes] backing at the requested 
alignment.
+   * \note Opaque handles must satisfy backend alignment requirements. The 
backing
+   * owner releases its resources independently of the pool and must not 
allocate
+   * through a DeviceAPI::Empty implementation that routes back to this pool.
+   */
+  virtual ffi::Tensor AllocBacking(size_t nbytes, size_t alignment, DLDevice 
device) {
+    auto allocate = [&]() {
+      auto* api = DeviceAPI::Get(device);
+      DLDataType bytes{kDLUInt, 8, 1};
+      if (alignment <= kAllocAlignment) {
+        return api->Empty({static_cast<int64_t>(nbytes)}, bytes, device);
+      }
+      // Preserve the backend's dtype alignment contract for wide vector 
elements.
+      TVM_FFI_CHECK_LE(alignment, std::numeric_limits<uint16_t>::max(), 
ValueError);
+      auto owner = api->Empty({static_cast<int64_t>((nbytes + alignment - 1) / 
alignment)},
+                              DLDataType{kDLUInt, 8, 
static_cast<uint16_t>(alignment)}, device);
+      return runtime::CreateView(std::move(owner), 
{static_cast<int64_t>(nbytes)}, bytes);
+    };
+    try {
+      return allocate();
+    } catch (const ffi::Error&) {
+      // This default allocator is non-collective; release cached memory and 
retry.
+      Clear();
+      return allocate();
+    }
+  }
+
+ private:
+  struct Lease {
+    ffi::ObjectPtr<TensorMemoryPool> pool;
+    ffi::Tensor backing;
+
+    void AllocData(DLTensor* tensor) { tensor->data = backing.data_ptr(); }
+    void FreeData(DLTensor*) { pool->Recycle(std::move(backing)); }
+  };
+
+  void Recycle(ffi::Tensor backing) {
+    std::lock_guard<std::recursive_mutex> lock(mu_);
+    size_t size = ffi::GetDataSize(backing);
+    try {
+      memory_pool_[size].push_back(std::move(backing));
+    } catch (const std::bad_alloc&) {
+      // A cache insertion failure must not escape tensor destruction.
+      used_memory_.fetch_sub(size, std::memory_order_relaxed);
+    }
+  }
+
+  size_t page_size_;
+  std::atomic<size_t> used_memory_;
+  std::unordered_map<size_t, std::vector<ffi::Tensor>> memory_pool_;
+  std::recursive_mutex mu_;
+};
+
 }  // namespace runtime
 }  // namespace tvm
 
diff --git a/include/tvm/runtime/vm/vm.h b/include/tvm/runtime/vm/vm.h
index 3ebab66fab..bc240043e7 100644
--- a/include/tvm/runtime/vm/vm.h
+++ b/include/tvm/runtime/vm/vm.h
@@ -24,25 +24,19 @@
 #define TVM_RUNTIME_VM_VM_H_
 
 #include <tvm/ffi/extra/module.h>
+#include <tvm/runtime/device_api.h>
+#include <tvm/runtime/tensor.h>
 
 #include <memory>
 #include <string>
 #include <unordered_map>
 #include <vector>
 
-#include "../memory/memory_manager.h"
 #include "./bytecode.h"
 #include "./executable.h"
 
 namespace tvm {
 namespace runtime {
-
-using memory::Allocator;
-using memory::AllocatorType;
-using memory::MemoryManager;
-using memory::Storage;
-using memory::StorageObj;
-
 namespace vm {
 
 /*!
@@ -128,10 +122,8 @@ class VirtualMachine : public ffi::ModuleObj {
   /*!
    * \brief Initialize the virtual machine for a set of devices.
    * \param devices The set of TVM devices.
-   * \param alloc_types The allocator types for each device.
    */
-  virtual void Init(const std::vector<Device>& devices,
-                    const std::vector<AllocatorType>& alloc_types) = 0;
+  virtual void Init(const std::vector<Device>& devices) = 0;
   /*!
    * \brief Load the executable for the virtual machine.
    * \param exec The executable.
@@ -210,8 +202,8 @@ class VirtualMachine : public ffi::ModuleObj {
   //--------------------------------------------------------------------------
   // The following section contains states that other builtin can depend on
   //--------------------------------------------------------------------------
-  /*! \brief The memory allocators. */
-  std::vector<Allocator*> allocators;
+  /*! \brief Owning memory pools for the physical devices. */
+  std::vector<ffi::ObjectPtr<TensorMemoryPool>> memory_pools;
   /*! \brief Runtime physical device list. */
   std::vector<Device> devices;
   /*! \brief The VM extensions. Mapping from the type index of the extension 
to the extension
diff --git a/python/tvm/rpc/remote.py b/python/tvm/rpc/remote.py
index 03a3a03433..f4994a519b 100644
--- a/python/tvm/rpc/remote.py
+++ b/python/tvm/rpc/remote.py
@@ -250,14 +250,10 @@ def time_evaluator(
     )
 
 
-def virtual_machine(module, device, memory_cfg=None):
+def virtual_machine(module, device):
     """Initialize a remote VM with session-checked endpoint device 
descriptors."""
     from tvm.runtime.vm import VirtualMachine
 
     devices = list(device) if isinstance(device, list | tuple) else [device]
     local = [_ffi_api.ValidateDevice(module, dev) for dev in devices]
-    if isinstance(memory_cfg, dict):
-        memory_cfg = {
-            _ffi_api.ValidateDevice(module, dev): value for dev, value in 
memory_cfg.items()
-        }
-    return VirtualMachine(module, local, memory_cfg)
+    return VirtualMachine(module, local)
diff --git a/python/tvm/runtime/vm.py b/python/tvm/runtime/vm.py
index 0e7eaee280..3dcf8f4f40 100644
--- a/python/tvm/runtime/vm.py
+++ b/python/tvm/runtime/vm.py
@@ -39,14 +39,10 @@ class VMInstrumentReturnKind(IntEnum):
 class VirtualMachine:
     """Relax VM runtime."""
 
-    NAIVE_ALLOCATOR = 1
-    POOLED_ALLOCATOR = 2
-
     def __init__(
         self,
         rt_mod: tvm.runtime.Module | tvm.runtime.Executable,
         device: Device | list[Device],
-        memory_cfg: str | dict[Device, str] | None = None,
     ) -> None:
         """
         Construct a VirtualMachine wrapper object.
@@ -58,14 +54,6 @@ class VirtualMachine:
 
         device : Union[Device, List[Device]]
             The device to deploy the module.
-
-        memory_cfg : Optional[Union[str, Dict[Device, str]]]
-            Config the type of memory allocator. The allocator type can be 
["naive",
-            "pooled"]. If memory_cfg is None, all devices will use pooled 
allocator
-            by default. If memory_cfg is string, all devices will use the 
specified
-            allocator type. If memory_cfg is a dict, each device uses the 
allocator
-            type specified in the dict, or pooled allocator if not specified 
in the
-            dict.
         """
         if not isinstance(rt_mod, tvm.runtime.Module):
             if isinstance(rt_mod, tvm.runtime.Executable):
@@ -83,10 +71,10 @@ class VirtualMachine:
         self._get_function_arity = self.module["get_function_arity"]
         self._get_function_param_name = self.module["get_function_param_name"]
         self._set_instrument = self.module["set_instrument"]
-        self._setup_device(device, memory_cfg)
+        self._setup_device(device)
 
-    def _setup_device(self, dev: Device, memory_cfg: str | dict[Device, str]) 
-> None:
-        """init devices and allocators."""
+    def _setup_device(self, dev: Device | list[Device]) -> None:
+        """Initialize devices and owning tensor pools."""
         devs = dev
         if not isinstance(dev, list | tuple):
             if not isinstance(dev, tvm.runtime.Device):
@@ -97,25 +85,10 @@ class VirtualMachine:
         if devs[-1].dlpack_device_type() != tvm.cpu().dlpack_device_type():
             devs.append(tvm.cpu())
 
-        default_alloc_type = VirtualMachine.POOLED_ALLOCATOR
-        if memory_cfg is None:
-            memory_cfg = {}
-        elif isinstance(memory_cfg, str):
-            assert memory_cfg in ["naive", "pooled"]
-            if memory_cfg == "naive":
-                default_alloc_type = VirtualMachine.NAIVE_ALLOCATOR
-            memory_cfg = {}
-        elif not isinstance(memory_cfg, dict):
-            raise TypeError(
-                "memory_cfg is expected be string or dictionary, "
-                + f"but received {type(memory_cfg)}"
-            )
         init_args = []
         for device in devs:
             init_args.append(device.dlpack_device_type())
             init_args.append(device.index)
-            alloc_type = memory_cfg[device] if device in memory_cfg else 
default_alloc_type
-            init_args.append(alloc_type)
         self.module["vm_initialization"](*init_args)
 
     def __getitem__(self, key: str) -> Function:
diff --git a/src/backend/cuda/runtime/cuda_device_api.cc 
b/src/backend/cuda/runtime/cuda_device_api.cc
index 03f627d981..bec8e0c680 100644
--- a/src/backend/cuda/runtime/cuda_device_api.cc
+++ b/src/backend/cuda/runtime/cuda_device_api.cc
@@ -34,6 +34,8 @@
 #include <algorithm>
 #include <cstring>
 
+#include "../../../runtime/workspace_pool.h"
+
 namespace tvm {
 namespace runtime {
 
@@ -291,6 +293,14 @@ class CUDADeviceAPI final : public DeviceAPI {
     
TVM_FFI_CHECK_CUDA_ERROR(cudaStreamSynchronize(static_cast<cudaStream_t>(stream)));
   }
 
+  void* AllocWorkspace(Device dev, size_t size, DLDataType type_hint) final {
+    return ThreadLocalWorkspacePool(dev.device_type)->AllocWorkspace(dev, 
size);
+  }
+
+  void FreeWorkspace(Device dev, void* data) final {
+    ThreadLocalWorkspacePool(dev.device_type)->FreeWorkspace(dev, data);
+  }
+
   bool SupportsDevicePointerArithmeticsOnHost() final { return true; }
 
   static CUDADeviceAPI* Global() {
@@ -301,12 +311,23 @@ class CUDADeviceAPI final : public DeviceAPI {
   }
 
  private:
+  static WorkspacePool* ThreadLocalWorkspacePool(DLDeviceType device_type);
+
   static void GPUCopy(const void* from, void* to, size_t size, cudaMemcpyKind 
kind,
                       cudaStream_t stream) {
     TVM_FFI_CHECK_CUDA_ERROR(cudaMemcpyAsync(to, from, size, kind, stream));
   }
 };
 
+WorkspacePool* CUDADeviceAPI::ThreadLocalWorkspacePool(DLDeviceType 
device_type) {
+  if (device_type == kDLCUDAHost) {
+    static thread_local WorkspacePool pool(kDLCUDAHost, 
CUDADeviceAPI::Global());
+    return &pool;
+  }
+  static thread_local WorkspacePool pool(kDLCUDA, CUDADeviceAPI::Global());
+  return &pool;
+}
+
 TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
   refl::GlobalDef()
diff --git a/src/backend/hexagon/runtime/hexagon_device_api.cc 
b/src/backend/hexagon/runtime/hexagon_device_api.cc
index 763cb82c1d..ed6491ba18 100644
--- a/src/backend/hexagon/runtime/hexagon_device_api.cc
+++ b/src/backend/hexagon/runtime/hexagon_device_api.cc
@@ -166,6 +166,24 @@ void HexagonDeviceAPI::FreeDataSpace(Device dev, void* 
ptr) {
   }
 }
 
+void* HexagonDeviceAPI::AllocWorkspace(Device dev, size_t size, DLDataType 
type_hint) {
+  TVM_FFI_ICHECK(IsValidDevice(dev)) << "dev.device_type: " << dev.device_type;
+  TVM_FFI_ICHECK(runtime_hexbuffs) << "Call AcquireResources before allocating 
Hexagon workspace";
+  std::lock_guard<std::mutex> lock(workspace_mutex_);
+  auto& pool = workspace_pools_[std::this_thread::get_id()];
+  if (!pool) pool = std::make_unique<WorkspacePool>(kDLHexagon, this);
+  return pool->AllocWorkspace(dev, size);
+}
+
+void HexagonDeviceAPI::FreeWorkspace(Device dev, void* data) {
+  TVM_FFI_ICHECK(IsValidDevice(dev)) << "dev.device_type: " << dev.device_type;
+  std::lock_guard<std::mutex> lock(workspace_mutex_);
+  auto it = workspace_pools_.find(std::this_thread::get_id());
+  TVM_FFI_ICHECK(it != workspace_pools_.end())
+      << "Attempt made to free unknown workspace allocation";
+  it->second->FreeWorkspace(dev, data);
+}
+
 void HexagonDeviceAPI::CopyDataFromTo(DLTensor* from, DLTensor* to, 
TVMStreamHandle stream) {
   TVM_FFI_ICHECK_EQ(from->byte_offset, 0);
   TVM_FFI_ICHECK_EQ(to->byte_offset, 0);
diff --git a/src/backend/hexagon/runtime/hexagon_device_api.h 
b/src/backend/hexagon/runtime/hexagon_device_api.h
index 016a00b4ba..319517c69b 100644
--- a/src/backend/hexagon/runtime/hexagon_device_api.h
+++ b/src/backend/hexagon/runtime/hexagon_device_api.h
@@ -27,6 +27,7 @@
 #include <memory>
 #include <mutex>
 #include <string>
+#include <thread>
 #include <unordered_map>
 #include <utility>
 #include <vector>
@@ -84,7 +85,10 @@ class HexagonDeviceAPI final : public DeviceAPI {
     TVM_FFI_ICHECK(runtime_threads) << "runtime_threads was not created in 
AcquireResources";
     runtime_threads.reset();
 
-    ResetWorkspacePool(kDLHexagon);
+    {
+      std::lock_guard<std::mutex> lock(workspace_mutex_);
+      for (auto& [thread, pool] : workspace_pools_) pool->Clear();
+    }
     TVM_FFI_ICHECK(runtime_hexbuffs) << "runtime_hexbuffs was not created in 
AcquireResources";
     runtime_hexbuffs.reset();
 
@@ -114,6 +118,12 @@ class HexagonDeviceAPI final : public DeviceAPI {
   //! \brief Allocate a flat allocation of global memory wrapped in a 
HexagonBuffer.
   void* AllocDataSpace(Device dev, size_t nbytes, size_t alignment, DLDataType 
type_hint);
 
+  //! \brief Request a dynamically allocated buffer from the thread's 
workspace pool.
+  void* AllocWorkspace(Device dev, size_t size, DLDataType type_hint) final;
+
+  //! \brief Return a workspace allocation to its owning pool.
+  void FreeWorkspace(Device dev, void* data) final;
+
   //! \brief Free the allocated HexagonBuffer.
   void FreeDataSpace(Device dev, void* ptr);
 
@@ -233,6 +243,11 @@ class HexagonDeviceAPI final : public DeviceAPI {
   // so a late deleter never frees an allocation through a replacement session.
   std::shared_ptr<HexagonBufferManager> runtime_hexbuffs;
 
+  // Backend ownership permits session teardown to clear every thread's cached 
workspace.
+  // Outstanding owners retain their original buffer manager until 
FreeWorkspace.
+  std::unordered_map<std::thread::id, std::unique_ptr<WorkspacePool>> 
workspace_pools_;
+  std::mutex workspace_mutex_;
+
   //! \brief Thread manager
   std::unique_ptr<HexagonThreadManager> runtime_threads;
   const unsigned threads{6};
diff --git a/src/backend/metal/runtime/metal_common.h 
b/src/backend/metal/runtime/metal_common.h
index 27f639e57f..e308264f46 100644
--- a/src/backend/metal/runtime/metal_common.h
+++ b/src/backend/metal/runtime/metal_common.h
@@ -41,6 +41,8 @@
 #include <utility>
 #include <vector>
 
+#include "../../../runtime/workspace_pool.h"
+
 /* Macro for convenience in using AutoReleasePoolWrapper.
  * With this macro we can add AutoReleasePoolWrapper to our ObjC code in more
  * native way.
@@ -317,6 +319,8 @@ class MetalWorkspace final : public DeviceAPI {
   TVMStreamHandle CreateStream(Device dev) final;
   void FreeStream(Device dev, TVMStreamHandle stream) final;
   void StreamSync(Device dev, TVMStreamHandle stream) final;
+  void* AllocWorkspace(Device dev, size_t size, DLDataType type_hint) final;
+  void FreeWorkspace(Device dev, void* data) final;
   void ReinitializeDefaultStreams();
 
   /**
@@ -413,7 +417,8 @@ class MetalThreadEntry {
   };
   std::vector<StagingBufferPool> staging_pools_;  // per device
   // constructor
-  MetalThreadEntry() {
+  WorkspacePool pool;
+  MetalThreadEntry() : pool(kDLMetal, MetalWorkspace::Global()) {
     device.device_id = 0;
     device.device_type = static_cast<DLDeviceType>(kDLMetal);
     MetalWorkspace* global_ws = MetalWorkspace::Global();
diff --git a/src/backend/metal/runtime/metal_device_api.mm 
b/src/backend/metal/runtime/metal_device_api.mm
index aabee12bcb..357a568667 100644
--- a/src/backend/metal/runtime/metal_device_api.mm
+++ b/src/backend/metal/runtime/metal_device_api.mm
@@ -319,6 +319,14 @@ void MetalWorkspace::StreamSync(Device dev, 
TVMStreamHandle stream) {
   };
 }
 
+void* MetalWorkspace::AllocWorkspace(Device dev, size_t size, DLDataType 
type_hint) {
+  return MetalThreadEntry::ThreadLocal()->pool.AllocWorkspace(dev, size);
+}
+
+void MetalWorkspace::FreeWorkspace(Device dev, void* data) {
+  MetalThreadEntry::ThreadLocal()->pool.FreeWorkspace(dev, data);
+}
+
 MetalThreadEntry::~MetalThreadEntry() {
   for (auto x : temp_buffer_) {
     if (x != nil) {
diff --git a/src/backend/opencl/runtime/opencl_common.h 
b/src/backend/opencl/runtime/opencl_common.h
index 1cd54b8c46..e197ded776 100644
--- a/src/backend/opencl/runtime/opencl_common.h
+++ b/src/backend/opencl/runtime/opencl_common.h
@@ -28,7 +28,6 @@
 #include <tvm/ffi/function.h>
 #include <tvm/runtime/base.h>
 #include <tvm/runtime/device_api.h>
-#include <tvm/runtime/memory/memory_manager.h>
 #include <tvm/runtime/tensor.h>
 #include <tvm/runtime/timer.h>
 
@@ -74,14 +73,13 @@
 #include "../../../runtime/metadata.h"
 #include "../../../runtime/pack_args.h"
 #include "../../../runtime/thread_storage_scope.h"
+#include "../../../runtime/workspace_pool.h"
 #include "texture.h"
 
 namespace tvm {
 namespace runtime {
 namespace cl {
 
-using tvm::runtime::memory::Buffer;
-
 static_assert(sizeof(cl_mem) == sizeof(void*), "Required to store cl_mem 
inside void*");
 
 inline const char* CLGetErrorString(cl_int error) {
@@ -266,6 +264,8 @@ class OpenCLWorkspace : public DeviceAPI {
   // the mutex for initialization
   std::mutex mu;
 
+  OpenCLWorkspace();
+
   // destructor
   ~OpenCLWorkspace() {
     for (auto& it : contexts) {
@@ -356,13 +356,19 @@ class OpenCLWorkspace : public DeviceAPI {
   ffi::Tensor Empty(ffi::ShapeView shape, DLDataType dtype, DLDevice device,
                     ffi::Optional<ffi::String> mem_scope = std::nullopt) final;
   void* AllocDataSpace(Device dev, int ndim, const int64_t* shape, DLDataType 
dtype,
-                       ffi::Optional<ffi::String> mem_scope = std::nullopt);
+                       ffi::Optional<ffi::String> mem_scope = std::nullopt,
+                       TensorMemoryPool* pool = nullptr);
   void* AllocDataSpace(Device dev, size_t width, size_t height, size_t depth, 
DLDataType type_hint,
-                       ffi::Optional<ffi::String> mem_scope = std::nullopt);
+                       ffi::Optional<ffi::String> mem_scope = std::nullopt,
+                       TensorMemoryPool* pool = nullptr);
   void* GetNativePtr(const tvm::ffi::Tensor& narr);
   void SetNativePtr(const tvm::ffi::Tensor& narr, void* host_ptr, size_t 
buf_size);
   void SetPerfHint(Device dev, cl_uint perf_hint);
   void FreeDataSpace(Device dev, void* ptr);
+  void ClearMemoryPool();
+  void* AllocWorkspace(Device dev, size_t size, DLDataType type_hint) final;
+  void FreeWorkspace(Device dev, void* data) final;
+  static ffi::Tensor AllocWorkspaceTensor(DeviceAPI* api, Device dev, size_t 
size);
   void StreamSync(Device dev, TVMStreamHandle stream) final;
   // cl_mem alloc utils
   void* AllocCLBuffer(Device dev, size_t size, size_t alignment, DLDataType 
type_hint);
@@ -387,6 +393,7 @@ class OpenCLWorkspace : public DeviceAPI {
     return "";
   }
   std::string noDevicesErrorMsg = "";
+  ffi::ObjectPtr<TensorMemoryPool> memory_pool_;
 };
 
 /*! \brief Thread local workspace */
@@ -401,10 +408,13 @@ class OpenCLThreadEntry {
   };
   /*! \brief The current device */
   Device device;
+  /*! \brief Thread-local linear workspace allocation cache. */
+  WorkspacePool pool;
   /*! \brief The thread-local kernel table */
   std::vector<KTEntry> kernel_table;
   // constructor
-  OpenCLThreadEntry(DLDeviceType device_type, DeviceAPI* device_api) {
+  OpenCLThreadEntry(DLDeviceType device_type, DeviceAPI* device_api)
+      : pool(device_type, device_api, OpenCLWorkspace::AllocWorkspaceTensor) {
     device.device_id = 0;
     device.device_type = device_type;
   }
@@ -449,7 +459,7 @@ struct BufferDescriptor {
   cl::BufferDescriptor* back_buffer{nullptr};
   cl_uchar* host_ptr{nullptr};
   MemoryLayout layout{MemoryLayout::kBuffer1D};
-  Buffer mbuf{nullptr};  // MemoryManager ref.
+  ffi::Tensor backing;  // Owns the pooled buffer underlying an image.
   bool is_compat_view{false};
 };
 }  // namespace cl
diff --git a/src/backend/opencl/runtime/opencl_device_api.cc 
b/src/backend/opencl/runtime/opencl_device_api.cc
index 510b8a0f82..04e4301bb0 100644
--- a/src/backend/opencl/runtime/opencl_device_api.cc
+++ b/src/backend/opencl/runtime/opencl_device_api.cc
@@ -27,7 +27,6 @@
 
 #include <sstream>
 
-#include "../../../runtime/memory/pooled_allocator.h"
 #include "../../../support/env.h"
 #include "opencl_common.h"
 
@@ -244,29 +243,118 @@ void* 
OpenCLWorkspace::CreateHostPtrIfEnabled(cl::BufferDescriptor* desc, Device
   return desc;
 }
 
-ffi::Tensor OpenCLWorkspace::Empty(ffi::ShapeView shape, DLDataType dtype, 
DLDevice device,
-                                   ffi::Optional<ffi::String> mem_scope) {
-  struct NDAlloc {
-    OpenCLWorkspace* api;
-    ffi::Optional<ffi::String> scope;
-    void AllocData(DLTensor* tensor) {
-      tensor->data =
-          api->AllocDataSpace(tensor->device, tensor->ndim, tensor->shape, 
tensor->dtype, scope);
+// Reuse follows the workspace's existing in-order command queue per device.
+class OpenCLTensorMemoryPool final : public TensorMemoryPool {
+ public:
+  ffi::Tensor Empty(ffi::ShapeView shape, DLDataType dtype, DLDevice device,
+                    ffi::Optional<ffi::String> mem_scope = std::nullopt) final 
{
+    if (!mem_scope.has_value() || (*mem_scope).empty() || *mem_scope == 
"global") {
+      return TensorMemoryPool::Empty(shape, dtype, device);
     }
-    void FreeData(DLTensor* tensor) {
-      auto* desc = static_cast<BufferDescriptor*>(tensor->data);
-      if (desc->layout == BufferDescriptor::MemoryLayout::kBuffer1D) {
-        MemoryManager::GetAllocator(tensor->device, 
desc->mbuf.alloc_type)->Free(desc->mbuf);
-      } else {
-        api->FreeDataSpace(tensor->device, desc);
+    struct NDAlloc {
+      OpenCLTensorMemoryPool* pool;
+      ffi::Optional<ffi::String> scope;
+      void AllocData(DLTensor* tensor) {
+        tensor->data = OpenCLWorkspace::Global()->AllocDataSpace(
+            tensor->device, tensor->ndim, tensor->shape, tensor->dtype, scope, 
pool);
+      }
+      void FreeData(DLTensor* tensor) {
+        OpenCLWorkspace::Global()->FreeDataSpace(tensor->device, tensor->data);
       }
+    };
+    return ffi::Tensor::FromNDAlloc(NDAlloc{this, mem_scope}, shape, dtype, 
device);
+  }
+
+  ffi::Tensor CreateView(ffi::Tensor backing, ffi::Shape shape, DLDataType 
dtype,
+                         uint64_t byte_offset = 0,
+                         ffi::Optional<ffi::String> mem_scope = std::nullopt) 
final {
+    TVM_FFI_CHECK_EQ(backing.device().device_type, kDLOpenCL, ValueError);
+    TVM_FFI_CHECK(backing.IsContiguous(), ValueError) << "OpenCL views require 
compact backing";
+    auto* desc = static_cast<BufferDescriptor*>(backing.data_ptr());
+    if ((!mem_scope.has_value() || (*mem_scope).empty() || *mem_scope == 
"global") &&
+        desc->layout == BufferDescriptor::MemoryLayout::kBuffer1D) {
+      return runtime::CreateView(std::move(backing), std::move(shape), dtype, 
byte_offset);
     }
-  };
-  return ffi::Tensor::FromNDAlloc(NDAlloc{this, mem_scope}, shape, dtype, 
device);
+    TVM_FFI_CHECK(byte_offset == 0 && backing.GetDLTensorPtr()->byte_offset == 
0, ValueError)
+        << "OpenCL physical views require a zero byte offset";
+    TVM_FFI_CHECK_LE(ffi::GetDataSize(shape.Product(), dtype), 
ffi::GetDataSize(backing),
+                     ValueError)
+        << "OpenCL view exceeds its backing tensor";
+    auto* api = OpenCLWorkspace::Global();
+    auto device = backing.device();
+    if (mem_scope.has_value() && !(*mem_scope).empty() && *mem_scope != 
"global") {
+      TVM_FFI_CHECK_GE(shape.size(), 2, ValueError);
+      TVM_FFI_CHECK_EQ(shape.back(), 4, ValueError) << "OpenCL textures 
require four channels";
+      // Physical image rows include backend padding, which must also fit the 
buffer.
+      if (api->IsBufferToImageSupported(device.device_id)) {
+        auto scope = BufferDescriptor::ScopeFromMemoryLayout(desc->layout);
+        size_t capacity =
+            desc->layout == BufferDescriptor::MemoryLayout::kBuffer1D
+                ? ffi::GetDataSize(backing)
+                : GetTextureMemorySize(backing.shape(), backing.dtype().bits, 
backing.dtype().lanes,
+                                       scope, 
api->GetImageAlignment(device.device_id));
+        size_t needed = GetTextureMemorySize(shape, dtype.bits, dtype.lanes, 
*mem_scope,
+                                             
api->GetImageAlignment(device.device_id));
+        TVM_FFI_CHECK_LE(needed, capacity, ValueError)
+            << "OpenCL image view exceeds its backing buffer";
+      }
+    }
+    struct NDAlloc {
+      ffi::Tensor backing;
+      ffi::Optional<ffi::String> scope;
+      void AllocData(DLTensor* tensor) {
+        tensor->data = OpenCLWorkspace::Global()->AllocDataSpaceView(
+            tensor->device, backing.data_ptr(),
+            ffi::Shape(tensor->shape, tensor->shape + tensor->ndim), 
tensor->dtype, scope);
+      }
+      void FreeData(DLTensor* tensor) {
+        if (tensor->data != backing.data_ptr()) {
+          OpenCLWorkspace::Global()->FreeDataSpaceView(tensor->device, 
tensor->data);
+        }
+      }
+    };
+    return ffi::Tensor::FromNDAlloc(NDAlloc{std::move(backing), mem_scope}, 
shape, dtype, device);
+  }
+
+ protected:
+  ffi::Tensor AllocBacking(size_t nbytes, size_t alignment, DLDevice device) 
final {
+    struct NDAlloc {
+      size_t alignment;
+      void AllocData(DLTensor* tensor) {
+        tensor->data = OpenCLWorkspace::Global()->AllocCLBuffer(
+            tensor->device, ffi::GetDataSize(*tensor), alignment, 
tensor->dtype);
+      }
+      void FreeData(DLTensor* tensor) {
+        OpenCLWorkspace::Global()->FreeDataSpace(tensor->device, tensor->data);
+      }
+    };
+    auto allocate = [&]() {
+      return ffi::Tensor::FromNDAlloc(NDAlloc{alignment}, 
{static_cast<int64_t>(nbytes)},
+                                      DLDataType{kDLUInt, 8, 1}, device);
+    };
+    try {
+      return allocate();
+    } catch (const ffi::Error& err) {
+      LOG(WARNING) << "OpenCL pool got an error during allocation: " << 
err.what();
+      LOG(WARNING) << "Trying to release all unused memory and reallocate...";
+      Clear();
+      return allocate();
+    }
+  }
+};
+
+OpenCLWorkspace::OpenCLWorkspace() : 
memory_pool_(ffi::make_object<OpenCLTensorMemoryPool>()) {}
+
+void OpenCLWorkspace::ClearMemoryPool() { memory_pool_->Clear(); }
+
+ffi::Tensor OpenCLWorkspace::Empty(ffi::ShapeView shape, DLDataType dtype, 
DLDevice device,
+                                   ffi::Optional<ffi::String> mem_scope) {
+  return memory_pool_->Empty(shape, dtype, device, mem_scope);
 }
 
 void* OpenCLWorkspace::AllocDataSpace(Device dev, size_t width, size_t height, 
size_t depth,
-                                      DLDataType type_hint, 
ffi::Optional<ffi::String> mem_scope) {
+                                      DLDataType type_hint, 
ffi::Optional<ffi::String> mem_scope,
+                                      TensorMemoryPool* pool) {
   // Texture allocation given width and height
   cl_uint row_align = GetImageAlignment(dev.device_id);
   size_t pixel_size = (type_hint.bits * type_hint.lanes + 7) / 8;
@@ -274,43 +362,35 @@ void* OpenCLWorkspace::AllocDataSpace(Device dev, size_t 
width, size_t height, s
   size_t mem_size = row_pitch * height * depth;
 
   // Alloc back buffer from pool
+  ffi::Tensor backing;
   cl::BufferDescriptor* back_buffer = nullptr;
   if (IsBufferToImageSupported(dev.device_id)) {
-    auto buf = MemoryManager::GetOrCreateAllocator(dev, AllocatorType::kPooled)
-                   ->Alloc(dev, mem_size, kTempAllocaAlignment, type_hint);
-    back_buffer = static_cast<cl::BufferDescriptor*>(buf.data);
-    back_buffer->mbuf = buf;
+    backing = (pool ? pool : memory_pool_.get())
+                  ->Empty({static_cast<int64_t>(mem_size)}, 
DLDataType{kDLUInt, 8, 1}, dev);
+    back_buffer = static_cast<cl::BufferDescriptor*>(backing.data_ptr());
   }
 
   if (!mem_scope.has_value()) {
     mem_scope = ffi::String("global.texture");
   }
-  try {
-    return AllocCLImage(dev, back_buffer, width, height, depth, row_pitch, 
type_hint, mem_scope);
-  } catch (...) {
-    if (back_buffer) {
-      MemoryManager::GetAllocator(dev, 
back_buffer->mbuf.alloc_type)->Free(back_buffer->mbuf);
-    }
-    throw;
-  }
+  auto* image = static_cast<cl::BufferDescriptor*>(
+      AllocCLImage(dev, back_buffer, width, height, depth, row_pitch, 
type_hint, mem_scope));
+  image->backing = std::move(backing);
+  return image;
 }
 
 void* OpenCLWorkspace::AllocDataSpace(Device dev, int ndim, const int64_t* 
shape, DLDataType dtype,
-                                      ffi::Optional<ffi::String> mem_scope) {
+                                      ffi::Optional<ffi::String> mem_scope,
+                                      TensorMemoryPool* pool) {
   this->Init();
   if (!mem_scope.has_value() || (*mem_scope).empty() || (*mem_scope) == 
"global") {
     size_t size = GetMemObjectSize(dev, ndim, shape, dtype);
-    cl::BufferDescriptor* ret_buffer = nullptr;
-    auto buf = MemoryManager::GetOrCreateAllocator(dev, AllocatorType::kPooled)
-                   ->Alloc(dev, size, kTempAllocaAlignment, dtype);
-    ret_buffer = static_cast<cl::BufferDescriptor*>(buf.data);
-    ret_buffer->mbuf = buf;
-    return ret_buffer;
+    return AllocCLBuffer(dev, size, kTempAllocaAlignment, dtype);
   }
   size_t axis = DefaultTextureLayoutSeparator(ndim, mem_scope.value());
   auto texture = ApplyTexture2DFlattening<int64_t>(shape, ndim, axis);
 
-  return AllocDataSpace(dev, texture.width, texture.height, texture.depth, 
dtype, mem_scope);
+  return AllocDataSpace(dev, texture.width, texture.height, texture.depth, 
dtype, mem_scope, pool);
 }
 
 void* OpenCLWorkspace::AllocCLBuffer(Device dev, size_t size, size_t alignment,
@@ -485,8 +565,6 @@ void OpenCLWorkspace::FreeDataSpace(Device dev, void* ptr) {
   if (desc->back_buffer) {
     // 2D Image w/ back buffer allocated from pool
     OPENCL_CALL(clReleaseMemObject(desc->buffer));
-    MemoryManager::GetAllocator(dev, desc->back_buffer->mbuf.alloc_type)
-        ->Free(desc->back_buffer->mbuf);
     delete desc;
   } else {
     if (desc->layout == cl::BufferDescriptor::MemoryLayout::kBuffer1D) {
@@ -586,6 +664,27 @@ void OpenCLWorkspace::CopyDataFromTo(DLTensor* from, 
DLTensor* to, TVMStreamHand
   }
 }
 
+ffi::Tensor OpenCLWorkspace::AllocWorkspaceTensor(DeviceAPI* api, Device dev, 
size_t size) {
+  struct NDAlloc {
+    OpenCLWorkspace* api;
+    void AllocData(DLTensor* tensor) {
+      tensor->data = api->AllocCLBuffer(tensor->device, 
ffi::GetDataSize(*tensor),
+                                        kTempAllocaAlignment, tensor->dtype);
+    }
+    void FreeData(DLTensor* tensor) { api->FreeDataSpace(tensor->device, 
tensor->data); }
+  };
+  return ffi::Tensor::FromNDAlloc(NDAlloc{static_cast<OpenCLWorkspace*>(api)},
+                                  {static_cast<int64_t>(size)}, 
DLDataType{kDLUInt, 8, 1}, dev);
+}
+
+void* OpenCLWorkspace::AllocWorkspace(Device dev, size_t size, DLDataType) {
+  return GetThreadEntry()->pool.AllocWorkspace(dev, size);
+}
+
+void OpenCLWorkspace::FreeWorkspace(Device dev, void* data) {
+  GetThreadEntry()->pool.FreeWorkspace(dev, data);
+}
+
 void OpenCLWorkspace::StreamSync(Device dev, TVMStreamHandle stream) {
   this->Init();
   TVM_FFI_ICHECK(stream == nullptr);
@@ -806,107 +905,24 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   });
 }
 
-class OpenCLPooledAllocator final : public memory::PooledAllocator {
- public:
-  explicit OpenCLPooledAllocator() : PooledAllocator() {}
-  ~OpenCLPooledAllocator() override { ReleaseAll(); }
-
-  bool AllowMemoryScope(const std::string& mem_scope) const final {
-    return ((mem_scope.find("texture") != std::string::npos) || 
mem_scope.empty() ||
-            ("global" == mem_scope));
-  }
-
-  Buffer Alloc(Device dev, size_t nbytes, size_t alignment, DLDataType 
type_hint) override {
-    std::lock_guard<std::recursive_mutex> lock(mu_);
-    size_t size = ((nbytes + page_size_ - 1) / page_size_) * page_size_;
-    auto&& it = memory_pool_.find(size);
-    if (it != memory_pool_.end() && !it->second.empty()) {
-      auto&& pool = it->second;
-      auto ret = pool.back();
-      pool.pop_back();
-      return ret;
-    }
-    Buffer buf;
-    buf.device = dev;
-    buf.size = size;
-    buf.alloc_type = AllocatorType::kPooled;
-    try {
-      buf.data = DeviceAllocDataSpace(dev, size, alignment, type_hint);
-    } catch (tvm::ffi::Error& err) {
-      LOG(WARNING) << "PooledAllocator got InternalError during allocation: " 
<< err.what();
-      LOG(WARNING) << "Trying to release all unused memory and reallocate...";
-      ReleaseAll();
-      buf.data = DeviceAllocDataSpace(dev, size, alignment, type_hint);
-    }
-
-    used_memory_.fetch_add(size, std::memory_order_relaxed);
-    VLOG(1) << "allocate " << size << " B, used memory " << used_memory_ << " 
B";
-    return buf;
-  }
-
-  Buffer Alloc(Device dev, ffi::Shape shape, DLDataType type_hint,
-               const std::string& mem_scope) override {
-    if (AllowMemoryScope(mem_scope)) {
-      size_t size = GetMemObjectSize(dev, shape.size(), shape.data(), 
type_hint);
-      if (mem_scope.empty() || mem_scope == "global") {
-        return Alloc(dev, size, kTempAllocaAlignment, type_hint);
-      }
-      Buffer buf;
-      buf.device = dev;
-      buf.size = size;
-      buf.alloc_type = AllocatorType::kPooled;
-      buf.data = OpenCLWorkspace::Global()->AllocDataSpace(dev, shape.size(), 
shape.data(),
-                                                           type_hint, 
ffi::String(mem_scope));
-      return buf;
-    }
-    TVM_FFI_THROW(InternalError) << "Unsupported memory scope for this 
Allocator:" << mem_scope;
-    return {};
-  }
-
-  void Free(const Buffer& buffer) override {
-    std::lock_guard<std::recursive_mutex> lock(mu_);
-    auto* desc = static_cast<BufferDescriptor*>(buffer.data);
-    if (desc->layout != BufferDescriptor::MemoryLayout::kBuffer1D) {
-      OpenCLWorkspace::Global()->FreeDataSpace(buffer.device, buffer.data);
-      return;
-    }
-    if (memory_pool_.find(buffer.size) == memory_pool_.end()) {
-      memory_pool_.emplace(buffer.size, std::vector<Buffer>{});
-    }
-    memory_pool_.at(buffer.size).push_back(buffer);
-    VLOG(1) << "reclaim buffer " << buffer.size;
-  }
-
-  void* CreateView(const Buffer& buffer, ffi::Shape shape, DLDataType 
type_hint,
-                   const std::string& mem_scope) final {
-    OpenCLWorkspace* ws_ = OpenCLWorkspace::Global();
-    return ws_->AllocDataSpaceView(buffer.device, buffer.data, shape, 
type_hint,
-                                   ffi::String(mem_scope));
-  }
-
-  void FreeView(Device dev, void* data) final {
-    OpenCLWorkspace* ws_ = OpenCLWorkspace::Global();
-    return ws_->FreeDataSpaceView(dev, data);
-  }
-
- protected:
-  // Empty uses this pool for its backing storage. Keep these hooks below Empty
-  // so a cache miss does not recursively enter owning tensor allocation.
-  void* DeviceAllocDataSpace(Device dev, size_t size, size_t alignment,
-                             DLDataType type_hint) override {
-    return OpenCLWorkspace::Global()->AllocCLBuffer(dev, size, alignment, 
type_hint);
-  }
-  void DeviceFreeDataSpace(Device dev, void* data) override {
-    OpenCLWorkspace::Global()->FreeDataSpace(dev, data);
-  }
-};
-
 TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
-  refl::GlobalDef().def_packed("DeviceAllocator.opencl", [](ffi::PackedArgs 
args, ffi::Any* rv) {
-    Allocator* alloc = new OpenCLPooledAllocator();
-    *rv = static_cast<void*>(alloc);
-  });
+  refl::GlobalDef()
+      .def("runtime.opencl.clear_pool", []() { 
OpenCLWorkspace::Global()->ClearMemoryPool(); })
+      .def("runtime.opencl.alloc_tensor",
+           [](ffi::Shape shape, DLDataType dtype, DLDevice device,
+              ffi::Optional<ffi::String> scope) {
+             return OpenCLWorkspace::Global()->Empty(shape, dtype, device, 
scope);
+           })
+      .def("runtime.opencl.create_view",
+           [](ffi::Tensor backing, ffi::Shape shape, DLDataType dtype,
+              ffi::Optional<ffi::String> scope) {
+             return ffi::make_object<OpenCLTensorMemoryPool>()->CreateView(
+                 std::move(backing), std::move(shape), dtype, 0, scope);
+           })
+      .def("runtime.create_tensor_memory_pool.opencl", [](DLDevice) {
+        return 
ffi::ObjectPtr<TensorMemoryPool>(ffi::make_object<OpenCLTensorMemoryPool>());
+      });
 }
 
 }  // namespace cl
diff --git a/src/backend/rocm/runtime/rocm_common.h 
b/src/backend/rocm/runtime/rocm_common.h
index 96b41f71e8..efd67c7a21 100644
--- a/src/backend/rocm/runtime/rocm_common.h
+++ b/src/backend/rocm/runtime/rocm_common.h
@@ -30,6 +30,8 @@
 
 #include <string>
 
+#include "../../../runtime/workspace_pool.h"
+
 namespace tvm {
 namespace runtime {
 
@@ -53,6 +55,9 @@ class ROCMThreadEntry {
  public:
   /*! \brief The hip stream */
   hipStream_t stream{nullptr};
+  /*! \brief thread local pool */
+  WorkspacePool pool;
+  WorkspacePool host_pool;
   /*! \brief constructor */
   ROCMThreadEntry();
   // get the threadlocal workspace
diff --git a/src/backend/rocm/runtime/rocm_device_api.cc 
b/src/backend/rocm/runtime/rocm_device_api.cc
index 6c5efcc947..8addf188c3 100644
--- a/src/backend/rocm/runtime/rocm_device_api.cc
+++ b/src/backend/rocm/runtime/rocm_device_api.cc
@@ -233,6 +233,18 @@ class ROCMDeviceAPI final : public DeviceAPI {
     ROCM_CALL(hipStreamSynchronize(static_cast<hipStream_t>(stream)));
   }
 
+  void* AllocWorkspace(Device dev, size_t size, DLDataType type_hint) final {
+    auto* entry = ROCMThreadEntry::ThreadLocal();
+    auto& pool = dev.device_type == kDLROCMHost ? entry->host_pool : 
entry->pool;
+    return pool.AllocWorkspace(dev, size);
+  }
+
+  void FreeWorkspace(Device dev, void* data) final {
+    auto* entry = ROCMThreadEntry::ThreadLocal();
+    auto& pool = dev.device_type == kDLROCMHost ? entry->host_pool : 
entry->pool;
+    pool.FreeWorkspace(dev, data);
+  }
+
   static ROCMDeviceAPI* Global() {
     static ROCMDeviceAPI* inst = new ROCMDeviceAPI();
     return inst;
@@ -249,7 +261,8 @@ class ROCMDeviceAPI final : public DeviceAPI {
   }
 };
 
-ROCMThreadEntry::ROCMThreadEntry() {}
+ROCMThreadEntry::ROCMThreadEntry()
+    : pool(kDLROCM, ROCMDeviceAPI::Global()), host_pool(kDLROCMHost, 
ROCMDeviceAPI::Global()) {}
 
 ROCMThreadEntry* ROCMThreadEntry::ThreadLocal() {
   static thread_local ROCMThreadEntry inst;
diff --git a/src/backend/vulkan/runtime/vulkan_device_api.cc 
b/src/backend/vulkan/runtime/vulkan_device_api.cc
index c5174e56ed..bff473d868 100644
--- a/src/backend/vulkan/runtime/vulkan_device_api.cc
+++ b/src/backend/vulkan/runtime/vulkan_device_api.cc
@@ -286,6 +286,18 @@ void VulkanDeviceAPI::FreeDataSpace(Device dev, void* ptr) 
{
   delete pbuf;
 }
 
+void* VulkanDeviceAPI::AllocWorkspace(Device dev, size_t size, DLDataType 
type_hint) {
+  auto& pool = pool_per_thread.GetOrMake(kDLVulkan, this);
+  return pool.AllocWorkspace(dev, size);
+}
+
+void VulkanDeviceAPI::FreeWorkspace(Device dev, void* data) {
+  auto* pool = pool_per_thread.Get();
+  TVM_FFI_ICHECK(pool) << "Attempted to free a vulkan workspace on a 
CPU-thread "
+                       << "that has never allocated a workspace";
+  pool->FreeWorkspace(dev, data);
+}
+
 TVMStreamHandle VulkanDeviceAPI::CreateStream(Device dev) { return nullptr; }
 
 void VulkanDeviceAPI::FreeStream(Device dev, TVMStreamHandle stream) {
diff --git a/src/backend/vulkan/runtime/vulkan_device_api.h 
b/src/backend/vulkan/runtime/vulkan_device_api.h
index 7cc41e759a..7e2ee41e79 100644
--- a/src/backend/vulkan/runtime/vulkan_device_api.h
+++ b/src/backend/vulkan/runtime/vulkan_device_api.h
@@ -26,6 +26,7 @@
 #include <string>
 #include <vector>
 
+#include "../../../runtime/workspace_pool.h"
 #include "thread_map.h"
 #include "vulkan/vulkan_core.h"
 #include "vulkan_device.h"
@@ -47,6 +48,9 @@ class VulkanDeviceAPI final : public DeviceAPI {
   ffi::Tensor Empty(ffi::ShapeView shape, DLDataType dtype, DLDevice device,
                     ffi::Optional<ffi::String> mem_scope = std::nullopt) final;
 
+  void* AllocWorkspace(Device dev, size_t size, DLDataType type_hint) final;
+  void FreeWorkspace(Device dev, void* data) final;
+
   // Current vulkan implementation has one "stream" per CPU thread,
   // with all commands writing into a single command buffer that is
   // submitted on a call to StreamSync.  Therefore, for now, these are
@@ -114,6 +118,13 @@ class VulkanDeviceAPI final : public DeviceAPI {
    */
   std::vector<VulkanDevice> devices_;
 
+  /*! \brief One pool of device memory for each CPU thread.
+   *
+   * These allocate memory based on the devices stored in devices_.
+   * The memory pools must be destructed before devices_.
+   */
+  ThreadMap<WorkspacePool> pool_per_thread;
+
   /*! \brief The index of the active device for each CPU thread.
    *
    * To mimic the semantics of cudaSetDevice, each CPU thread can set
diff --git a/src/relax/backend/vm/codegen_vm.cc 
b/src/relax/backend/vm/codegen_vm.cc
index d2ad11c7d9..bf5388f534 100644
--- a/src/relax/backend/vm/codegen_vm.cc
+++ b/src/relax/backend/vm/codegen_vm.cc
@@ -364,6 +364,7 @@ class CodeGenVM : public ExprFunctor<Instruction::Arg(const 
Expr&)> {
   void EmitAllocTensor(const Call& call_node, RegName dst_reg) {
     TVM_FFI_ICHECK_EQ(call_node->args.size(), 5);
     std::vector<Instruction::Arg> args;
+    args.push_back(Instruction::Arg::Register(Instruction::kVMRegister));
     for (int i = 0; i < 4; ++i) {
       args.push_back(this->VisitExpr(call_node->args[i]));
     }
diff --git a/src/runtime/cpu_device_api.cc b/src/runtime/cpu_device_api.cc
index a3b43e16c3..374e89f4c6 100644
--- a/src/runtime/cpu_device_api.cc
+++ b/src/runtime/cpu_device_api.cc
@@ -29,6 +29,8 @@
 #include <cstdlib>
 #include <cstring>
 
+#include "workspace_pool.h"
+
 #ifdef __ANDROID__
 #include <android/api-level.h>
 #endif
@@ -132,6 +134,9 @@ class CPUDeviceAPI final : public DeviceAPI {
  public:
   void StreamSync(Device dev, TVMStreamHandle stream) final {}
 
+  void* AllocWorkspace(Device dev, size_t size, DLDataType type_hint) final;
+  void FreeWorkspace(Device dev, void* data) final;
+
   bool SupportsDevicePointerArithmeticsOnHost() final { return true; }
 
   static CPUDeviceAPI* Global() {
@@ -149,6 +154,23 @@ class CPUDeviceAPI final : public DeviceAPI {
   }
 };
 
+struct CPUWorkspacePool : public WorkspacePool {
+  CPUWorkspacePool() : WorkspacePool(kDLCPU, CPUDeviceAPI::Global()) {}
+};
+
+static CPUWorkspacePool* CPUWorkspacePoolThreadLocal() {
+  static thread_local CPUWorkspacePool inst;
+  return &inst;
+}
+
+void* CPUDeviceAPI::AllocWorkspace(Device dev, size_t size, DLDataType 
type_hint) {
+  return CPUWorkspacePoolThreadLocal()->AllocWorkspace(dev, size);
+}
+
+void CPUDeviceAPI::FreeWorkspace(Device dev, void* data) {
+  CPUWorkspacePoolThreadLocal()->FreeWorkspace(dev, data);
+}
+
 TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
   refl::GlobalDef().def_packed("device_api.cpu", [](ffi::PackedArgs args, 
ffi::Any* rv) {
diff --git a/src/runtime/device_api.cc b/src/runtime/device_api.cc
index bfeb959165..a94b7f28ee 100644
--- a/src/runtime/device_api.cc
+++ b/src/runtime/device_api.cc
@@ -37,7 +37,7 @@
 #include <cctype>
 #include <cstdlib>
 #include <limits>
-#include <memory>
+#include <map>
 #include <mutex>
 #include <sstream>
 #include <string>
@@ -45,8 +45,6 @@
 #include <unordered_map>
 #include <variant>
 
-#include "workspace_pool.h"
-
 namespace tvm {
 namespace runtime {
 
@@ -99,6 +97,28 @@ int DeviceAPI::GetDeviceIndex() {
   TVM_FFI_THROW(NotImplementedError) << "Device does not support 
GetDeviceIndex.";
 }
 
+namespace {
+// Default workspace allocation has no cache.  Backends with reuse own their 
pools.
+std::unordered_map<void*, ffi::Tensor>& DefaultWorkspaces(Device dev) {
+  static thread_local std::map<std::pair<int, int>, std::unordered_map<void*, 
ffi::Tensor>>
+      allocations;
+  return allocations[{dev.device_type, dev.device_id}];
+}
+}  // namespace
+
+void* DeviceAPI::AllocWorkspace(Device dev, size_t size, DLDataType type_hint) 
{
+  int64_t count =
+      static_cast<int64_t>(size / kTempAllocaAlignment + (size % 
kTempAllocaAlignment != 0));
+  auto owner = Empty(ffi::ShapeView(&count, 1), DLDataType{kDLUInt, 8, 
kTempAllocaAlignment}, dev);
+  void* ptr = owner.data_ptr();
+  DefaultWorkspaces(dev).emplace(ptr, std::move(owner));
+  return ptr;
+}
+
+void DeviceAPI::FreeWorkspace(Device dev, void* ptr) {
+  TVM_FFI_ICHECK_EQ(DefaultWorkspaces(dev).erase(ptr), 1);
+}
+
 void DeviceAPI::CopyDataFromTo(DLTensor* from, DLTensor* to, TVMStreamHandle 
stream) {
   // by default, we can always redirect to the flat memory copy operation.
   size_t nbytes = ffi::GetDataSize(*from);
@@ -200,25 +220,18 @@ int TVMBackendGetDeviceIndex(int device_type, int* 
out_device_index) {
   TVM_FFI_SAFE_CALL_END();
 }
 
-namespace {
-WorkspacePool* BackendWorkspacePool(DLDevice dev) {
-  static thread_local std::unordered_map<int, std::unique_ptr<WorkspacePool>> 
pools;
-  auto& pool = pools[static_cast<int>(dev.device_type)];
-  if (!pool) pool = std::make_unique<WorkspacePool>(dev.device_type, 
DeviceAPI::Get(dev));
-  return pool.get();
-}
-}  // namespace
-
 void* TVMBackendAllocWorkspace(int device_type, int device_id, uint64_t size, 
int dtype_code_hint,
                                int dtype_bits_hint) {
   DLDevice dev{static_cast<DLDeviceType>(device_type), device_id};
   TVM_FFI_CHECK_LE(size, std::numeric_limits<size_t>::max(), ValueError)
       << "Workspace size exceeds addressable memory";
-  return BackendWorkspacePool(dev)->AllocWorkspace(dev, 
static_cast<size_t>(size));
+  DLDataType type_hint{static_cast<uint8_t>(dtype_code_hint), 
static_cast<uint8_t>(dtype_bits_hint),
+                       1};
+  return DeviceAPI::Get(dev)->AllocWorkspace(dev, static_cast<size_t>(size), 
type_hint);
 }
 
 int TVMBackendFreeWorkspace(int device_type, int device_id, void* ptr) {
   DLDevice dev{static_cast<DLDeviceType>(device_type), device_id};
-  BackendWorkspacePool(dev)->FreeWorkspace(dev, ptr);
+  DeviceAPI::Get(dev)->FreeWorkspace(dev, ptr);
   return 0;
 }
diff --git a/src/runtime/extra/contrib/nvshmem/memory_allocator.cc 
b/src/runtime/extra/contrib/nvshmem/memory_allocator.cc
index 2c2682a553..3c006e2fe4 100644
--- a/src/runtime/extra/contrib/nvshmem/memory_allocator.cc
+++ b/src/runtime/extra/contrib/nvshmem/memory_allocator.cc
@@ -21,76 +21,58 @@
 #include <tvm/ffi/extra/cuda/base.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
-#include <tvm/runtime/memory/memory_manager.h>
+#include <tvm/runtime/tensor.h>
 
-#include <thread>
-
-#include "../../../memory/pooled_allocator.h"
 #include "../../disco/utils.h"
 
 namespace tvm {
 namespace runtime {
 
-using tvm::runtime::memory::Buffer;
-using tvm::runtime::memory::PooledAllocator;
-
 /*!
- * \brief The memory allocator of NVSHMEM.
- * Overriding PooledAllocator for efficient memory management.
+ * \brief Tensor pool for NVSHMEM symmetric memory.
+ * Allocations, releases and finalization retain NVSHMEM's collective ordering
+ * requirements. All tensor owners must be released before finalization.
  */
-class NVSHMEMAllocator final : public PooledAllocator {
+class NVSHMEMPool final : public TensorMemoryPool {
  public:
-  explicit NVSHMEMAllocator() : PooledAllocator() {}
-
-  ~NVSHMEMAllocator() { PooledAllocator::ReleaseAll(); }
-
-  void Clear() final { PooledAllocator::ReleaseAll(); }
+  explicit NVSHMEMPool() = default;
 
-  bool AllowMemoryScope(const std::string& mem_scope) const final {
-    // The allowed memory scope of NVSHMEM is "nvshmem";
-    return mem_scope == "nvshmem";
+  /*! \brief Return the global NVSHMEM singleton pool. */
+  static NVSHMEMPool* Global() {
+    static auto* pool = new 
ffi::ObjectPtr<NVSHMEMPool>(ffi::make_object<NVSHMEMPool>());
+    return pool->get();
   }
 
-  /*! \brief Return the global NVSHMEM singleton allocator. */
-  static NVSHMEMAllocator* Global() {
-    static NVSHMEMAllocator* allocator = new NVSHMEMAllocator();
-    return allocator;
-  }
-
-  ffi::Tensor Empty(ffi::Shape shape, DLDataType dtype, Device device) {
-    class NVSHMEMAlloc {
-     public:
-      explicit NVSHMEMAlloc(Buffer buffer) : buffer_(buffer) {}
-      void AllocData(DLTensor* tensor) { tensor->data = buffer_.data; }
-      void FreeData(DLTensor* tensor) { 
NVSHMEMAllocator::Global()->Free(buffer_); }
-
-     private:
-      Buffer buffer_;
-    };
-
-    Buffer buffer = PooledAllocator::Alloc(device, shape, dtype, 
ffi::String("nvshmem"));
-    return ffi::Tensor::FromNDAlloc(NVSHMEMAlloc(buffer), shape, dtype, 
device);
+  ffi::Tensor Empty(ffi::ShapeView shape, DLDataType dtype, DLDevice device,
+                    ffi::Optional<ffi::String> mem_scope = std::nullopt) final 
{
+    TVM_FFI_ICHECK_EQ(device.device_type, DLDeviceType::kDLCUDA)
+        << "nvshmem can only allocate cuda device memory space.";
+    TVM_FFI_ICHECK(dtype.code == kDLInt || dtype.code == kDLUInt || dtype.code 
== kDLFloat ||
+                   dtype.code == kDLBfloat)
+        << "nvshmem can only allocate tensor with int, unsigned int, float, or 
bfloat data types.";
+    TVM_FFI_CHECK(!mem_scope.has_value() || mem_scope.value() == "nvshmem", 
ValueError)
+        << "NVSHMEM memory requires the nvshmem scope";
+    return TensorMemoryPool::Empty(shape, dtype, device);
   }
 
  private:
-  void* DeviceAllocDataSpace(Device dev, size_t size, size_t alignment,
-                             DLDataType type_hint) final {
-    TVM_FFI_ICHECK_EQ(dev.device_type, DLDeviceType::kDLCUDA)
-        << "nvshmem can only allocate cuda device memory space.";
-    TVM_FFI_ICHECK(
-        type_hint.code == DLDataTypeCode::kDLInt || type_hint.code == 
DLDataTypeCode::kDLUInt ||
-        type_hint.code == DLDataTypeCode::kDLFloat || type_hint.code == 
DLDataTypeCode::kDLBfloat)
-        << "nvshmem can only allocate tensor with int, usingned int, float, or 
bfloat data types.";
-    return nvshmem_align(alignment, size);
+  ffi::Tensor AllocBacking(size_t size, size_t alignment, DLDevice device) 
final {
+    struct Backing {
+      size_t size;
+      size_t alignment;
+      void AllocData(DLTensor* tensor) { tensor->data = 
nvshmem_align(alignment, size); }
+      void FreeData(DLTensor* tensor) { nvshmem_free(tensor->data); }
+    };
+    int64_t extent = static_cast<int64_t>(size);
+    return ffi::Tensor::FromNDAlloc(Backing{size, alignment}, 
ffi::ShapeView(&extent, 1),
+                                    DLDataType{kDLUInt, 8, 1}, device);
   }
-
-  void DeviceFreeDataSpace(Device dev, void* ptr) final { nvshmem_free(ptr); }
 };
 
 ffi::Tensor NVSHMEMEmpty(ffi::Shape shape, DLDataType dtype, 
ffi::Optional<Device> device) {
   Device resolved_device =
       device.has_value() ? device.value() : 
UseDefaultDeviceIfNone(std::nullopt);
-  return NVSHMEMAllocator::Global()->Empty(shape, dtype, resolved_device);
+  return NVSHMEMPool::Global()->Empty(shape, dtype, resolved_device);
 }
 
 TVM_FFI_STATIC_INIT_BLOCK() {
@@ -99,7 +81,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 }
 
 void NVSHMEMFinalize() {
-  NVSHMEMAllocator::Global()->Clear();
+  NVSHMEMPool::Global()->Clear();
   nvshmem_finalize();
 }
 
diff --git a/src/runtime/extra/disco/builtin.cc 
b/src/runtime/extra/disco/builtin.cc
index ce6f5e9729..1651441e24 100644
--- a/src/runtime/extra/disco/builtin.cc
+++ b/src/runtime/extra/disco/builtin.cc
@@ -23,7 +23,7 @@
 #include <tvm/runtime/disco/builtin.h>
 #include <tvm/runtime/disco/disco_worker.h>
 #include <tvm/runtime/disco/session.h>
-#include <tvm/runtime/vm/vm.h>
+#include <tvm/runtime/tensor.h>
 
 #include <sstream>
 
@@ -65,9 +65,7 @@ ffi::Module LoadVMModule(std::string path, 
ffi::Optional<Device> device) {
         << "File `" << path
         << "` is not built by RelaxVM, because `vm_initialization` does not 
exist";
   }
-  (*vm_initialization)(static_cast<int>(dev.device_type), 
static_cast<int>(dev.device_id),
-                       static_cast<int>(AllocatorType::kPooled), 
static_cast<int>(kDLCPU), 0,
-                       static_cast<int>(AllocatorType::kPooled));
+  ffi::Function::GetGlobalRequired("vm.builtin.initialize_default")(mod, dev);
   return mod;
 }
 
diff --git a/src/runtime/extra/disco/cuda_ipc/cuda_ipc_memory.cc 
b/src/runtime/extra/disco/cuda_ipc/cuda_ipc_memory.cc
index 1d7cb73e6f..aea7665361 100644
--- a/src/runtime/extra/disco/cuda_ipc/cuda_ipc_memory.cc
+++ b/src/runtime/extra/disco/cuda_ipc/cuda_ipc_memory.cc
@@ -22,10 +22,11 @@
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/disco/cuda_ipc_memory.h>
-#include <tvm/runtime/memory/memory_manager.h>
+#include <tvm/runtime/tensor.h>
+
+#include <unordered_map>
 
 #include "../../../../../3rdparty/tensorrt_llm/custom_allreduce_kernels.h"
-#include "../../../memory/pooled_allocator.h"
 #include "../nccl/nccl_context.h"
 
 namespace tvm {
@@ -34,7 +35,6 @@ namespace cuda_ipc {
 
 using tensorrt_llm::MAX_ALL_REDUCE_BLOCKS;
 using tensorrt_llm::MAX_RANKS_PER_NODE;
-using tvm::runtime::memory::Buffer;
 
 /*!
  * \brief All-gather the IPC memory handles across all distributed workers.
@@ -66,15 +66,17 @@ std::vector<cudaIpcMemHandle_t> 
AllGatherIPCHandles(nccl::CCLThreadLocalContext*
 
 /*!
  * \brief The memory allocator of CUDAIPCMemory.
- * Overriding PooledAllocator for efficient memory management.
+ * Overriding TensorMemoryPool for efficient memory management.
  */
-class CUDAIPCMemoryAllocator final : public memory::PooledAllocator {
+class CUDAIPCMemoryPool final : public TensorMemoryPool {
  public:
-  explicit CUDAIPCMemoryAllocator() : PooledAllocator() {}
+  explicit CUDAIPCMemoryPool() = default;
 
-  bool AllowMemoryScope(const std::string& mem_scope) const final {
-    // The allowed memory scope of CUDAIPCMemory is "ipc_memory";
-    return mem_scope == "ipc_memory";
+  ffi::Tensor Empty(ffi::ShapeView shape, DLDataType dtype, DLDevice device,
+                    ffi::Optional<ffi::String> mem_scope = std::nullopt) final 
{
+    TVM_FFI_CHECK(!mem_scope.has_value() || mem_scope.value() == "ipc_memory", 
ValueError)
+        << "CUDA IPC memory requires the ipc_memory scope";
+    return TensorMemoryPool::Empty(shape, dtype, device);
   }
 
   CUDAIPCMemory GetIPCMemoryFromDevicePtr(void* ptr) const {
@@ -86,16 +88,16 @@ class CUDAIPCMemoryAllocator final : public 
memory::PooledAllocator {
   }
 
   /*! \brief Return the global CUDAIPCMemory singleton allocator. */
-  static CUDAIPCMemoryAllocator* Global() {
-    static CUDAIPCMemoryAllocator* allocator = new CUDAIPCMemoryAllocator();
-    return allocator;
+  static CUDAIPCMemoryPool* Global() {
+    static auto* pool =
+        new 
ffi::ObjectPtr<CUDAIPCMemoryPool>(ffi::make_object<CUDAIPCMemoryPool>());
+    return pool->get();
   }
 
  private:
-  void* DeviceAllocDataSpace(Device dev, size_t size, size_t alignment,
-                             DLDataType type_hint) final {
-    auto [data_ptr, data_comm_ptrs] =
-        AllocIPCMemory(dev, size, alignment, type_hint, 
/*reset_memory_to_zero=*/false);
+  ffi::Tensor AllocBacking(size_t size, size_t alignment, DLDevice dev) final {
+    auto [data_ptr, data_comm_ptrs] = AllocIPCMemory(
+        dev, size, alignment, DLDataType{kDLUInt, 8, 1}, 
/*reset_memory_to_zero=*/false);
     int barrier_ptr_size = sizeof(uint32_t) * (MAX_ALL_REDUCE_BLOCKS + 2) * 
MAX_RANKS_PER_NODE;
     auto [barrier_in_ptr, barrier_in_comm_ptrs] =
         AllocIPCMemory(dev, barrier_ptr_size, alignment, DLDataType{kDLUInt, 
32, 1},
@@ -113,21 +115,24 @@ class CUDAIPCMemoryAllocator final : public 
memory::PooledAllocator {
     ipc_memory->barrier_flag = 1;
     ipc_memory->num_workers = nccl_ctx->worker->num_workers;
     ipc_memory->worker_id = nccl_ctx->worker->worker_id;
-    ipc_memory_map_.insert_or_assign(data_ptr,
-                                     CUDAIPCMemory(ffi::UnsafeInit{}, 
std::move(ipc_memory)));
-    return data_ptr;
-  }
-
-  void DeviceFreeDataSpace(Device dev, void* ptr) final {
-    TVM_FFI_ICHECK(dev.device_type == kDLCUDA);
-    ffi::CUDADeviceGuard device_guard(dev.device_id);
-    nccl::CCLThreadLocalContext* ctx = nccl::CCLThreadLocalContext::Get();
-    auto it = ipc_memory_map_.find(ptr);
-    TVM_FFI_ICHECK(it != ipc_memory_map_.end());
-    FreeIPCMemory(it->second->remote_data, ctx->worker->worker_id);
-    FreeIPCMemory(it->second->barrier_in, ctx->worker->worker_id);
-    FreeIPCMemory(it->second->barrier_out, ctx->worker->worker_id);
-    ipc_memory_map_.erase(it);
+    CUDAIPCMemory memory(ffi::UnsafeInit{}, std::move(ipc_memory));
+    ipc_memory_map_.insert_or_assign(data_ptr, memory);
+    struct Backing {
+      CUDAIPCMemory memory;
+      void AllocData(DLTensor* tensor) { tensor->data = 
memory->remote_data[memory->worker_id]; }
+      void FreeData(DLTensor* tensor) {
+        ffi::CUDADeviceGuard device_guard(tensor->device.device_id);
+        FreeIPCMemory(memory->remote_data, memory->worker_id);
+        FreeIPCMemory(memory->barrier_in, memory->worker_id);
+        FreeIPCMemory(memory->barrier_out, memory->worker_id);
+        // The original global lookup map has process lifetime, independently 
of
+        // the thread-local NCCL context used to exchange the handles.
+        Global()->ipc_memory_map_.erase(tensor->data);
+      }
+    };
+    int64_t extent = static_cast<int64_t>(size);
+    return ffi::Tensor::FromNDAlloc(Backing{std::move(memory)}, 
ffi::ShapeView(&extent, 1),
+                                    DLDataType{kDLUInt, 8, 1}, dev);
   }
 
   /*!
@@ -185,7 +190,7 @@ class CUDAIPCMemoryAllocator final : public 
memory::PooledAllocator {
   }
 
   /*! \brief Free the IPC memory pointers. */
-  void FreeIPCMemory(std::vector<void*> comm_ptrs, int worker_id) {
+  static void FreeIPCMemory(std::vector<void*> comm_ptrs, int worker_id) {
     for (int i = 0; i < static_cast<int>(comm_ptrs.size()); ++i) {
       if (i != worker_id) {
         // Free ipc handle.
@@ -202,35 +207,32 @@ class CUDAIPCMemoryAllocator final : public 
memory::PooledAllocator {
 };
 
 /*!
- * \brief Allocate a storage object with CUDA IPC memory.
- * \param buffer_shape The shape of the storage to allocate.
- * \param dtype_hint The dtype of the storage to allocate.
- * \return The allocated storage object with internal CUDA IPC memory buffer.
+ * \brief Allocate owning CUDA IPC backing for VM tensors.
  */
-memory::Storage IPCAllocStorage(ffi::Shape buffer_shape, DLDataType 
dtype_hint) {
+ffi::Tensor IPCAllocTensor(ffi::Shape buffer_shape, DLDataType dtype_hint) {
   nccl::CCLThreadLocalContext* nccl_ctx = nccl::CCLThreadLocalContext::Get();
   Device device{DLDeviceType::kDLCUDA, nccl_ctx->device_id};
-  CUDAIPCMemoryAllocator* allocator = CUDAIPCMemoryAllocator::Global();
-  auto buffer = allocator->Alloc(device, std::move(buffer_shape), dtype_hint, 
"ipc_memory");
-  return memory::Storage(std::move(buffer), allocator);
+  return CUDAIPCMemoryPool::Global()->Empty(buffer_shape, dtype_hint, device,
+                                            ffi::String("ipc_memory"));
 }
 
 TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
   refl::GlobalDef()
-      .def("runtime.disco.cuda_ipc.alloc_storage", IPCAllocStorage)
+      .def("runtime.disco.cuda_ipc.empty", IPCAllocTensor)
+      .def("runtime.disco.cuda_ipc.alloc_storage", IPCAllocTensor)
       .def("runtime.disco.cuda_ipc.cuda_ipc_memory_allocator_clear",
-           []() { CUDAIPCMemoryAllocator::Global()->Clear(); });
+           []() { CUDAIPCMemoryPool::Global()->Clear(); });
 }
 
 /******************** CUDAIPCMemoryObj ********************/
 
-// Direct to CUDAIPCMemoryAllocator::Global.
-memory::Allocator* CUDAIPCMemory::GlobalAllocator() { return 
CUDAIPCMemoryAllocator::Global(); }
+// Direct to CUDAIPCMemoryPool::Global.
+TensorMemoryPool* CUDAIPCMemory::GlobalMemoryPool() { return 
CUDAIPCMemoryPool::Global(); }
 
-// Direct to CUDAIPCMemoryAllocator::GlobalGetIPCMemoryFromDevicePtr.
+// Direct to CUDAIPCMemoryPool::GlobalGetIPCMemoryFromDevicePtr.
 CUDAIPCMemory CUDAIPCMemory::GetIPCMemoryFromDevicePtr(void* ptr) {
-  return CUDAIPCMemoryAllocator::Global()->GetIPCMemoryFromDevicePtr(ptr);
+  return CUDAIPCMemoryPool::Global()->GetIPCMemoryFromDevicePtr(ptr);
 }
 
 }  // namespace cuda_ipc
diff --git a/src/runtime/extra/disco/cuda_ipc/custom_allreduce.cc 
b/src/runtime/extra/disco/cuda_ipc/custom_allreduce.cc
index 3eaca5ba98..4ee47ed312 100644
--- a/src/runtime/extra/disco/cuda_ipc/custom_allreduce.cc
+++ b/src/runtime/extra/disco/cuda_ipc/custom_allreduce.cc
@@ -21,7 +21,6 @@
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/disco/cuda_ipc_memory.h>
-#include <tvm/runtime/memory/memory_manager.h>
 
 #include "../../../../../3rdparty/tensorrt_llm/custom_allreduce_kernels.h"
 #include "../nccl/nccl_context.h"
diff --git a/src/runtime/memory/memory_manager.cc 
b/src/runtime/memory/memory_manager.cc
deleted file mode 100644
index 04eaf8960a..0000000000
--- a/src/runtime/memory/memory_manager.cc
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * 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 tvm/runtime/memory/memory_manager.cc
- * \brief Allocate and manage memory for the runtime.
- */
-#include <tvm/ffi/cast.h>
-#include <tvm/ffi/function.h>
-#include <tvm/ffi/reflection/registry.h>
-#include <tvm/runtime/logging.h>
-#include <tvm/runtime/memory/memory_manager.h>
-
-#include <memory>
-#include <utility>
-
-#include "naive_allocator.h"
-#include "pooled_allocator.h"
-
-namespace tvm {
-namespace runtime {
-namespace memory {
-
-Storage::Storage(Buffer buffer, Allocator* allocator) {
-  auto n = ffi::make_object<StorageObj>();
-  n->buffer = std::move(buffer);
-  n->allocator = allocator;
-  data_ = std::move(n);
-}
-
-inline void VerifyDataType(DLDataType dtype) {
-  TVM_FFI_ICHECK_GE(dtype.lanes, 1);
-  if (dtype.code == kDLFloat) {
-    TVM_FFI_ICHECK_EQ(dtype.bits % 8, 0);
-  } else {
-    // allow uint1 as a special flag for bool.
-    if (dtype.bits == 1 && dtype.code == kDLUInt) return;
-    TVM_FFI_ICHECK_EQ(dtype.bits % 8, 0);
-  }
-  TVM_FFI_ICHECK_EQ(dtype.bits & (dtype.bits - 1), 0);
-}
-
-inline size_t GetDataAlignment(const DLDataType& dtype) {
-  size_t align = dtype.lanes * dtype.bits / 8;
-  if (align < kAllocAlignment) return kAllocAlignment;
-  return align;
-}
-
-ffi::Tensor StorageObj::AllocTensorScoped(int64_t offset, ffi::Shape shape, 
DLDataType dtype,
-                                          ffi::String scope) {
-  if (scope == "global" || scope.empty()) {
-    return AllocTensor(offset, shape, dtype);
-  }
-  VerifyDataType(dtype);
-
-  struct StorageScopedAlloc {
-   public:
-    explicit StorageScopedAlloc(Storage storage) : storage_(storage) {}
-
-    void AllocData(DLTensor* tensor, const ffi::Shape& shape, const 
ffi::String& scope,
-                   int64_t byte_offset) {
-      tensor->data = storage_->allocator->CreateView(storage_->buffer, shape, 
tensor->dtype, scope);
-      tensor->byte_offset = byte_offset;
-    }
-    void FreeData(DLTensor* tensor) { 
storage_->allocator->FreeView(tensor->device, tensor->data); }
-
-   private:
-    Storage storage_;
-  };
-
-  size_t needed_size = ffi::GetDataSize(shape.Product(), dtype);
-  TVM_FFI_ICHECK(offset + needed_size <= this->buffer.size)
-      << "storage allocation failure, attempted to allocate " << needed_size 
<< " at offset "
-      << offset << " in region that is " << this->buffer.size << "bytes";
-
-  return 
ffi::Tensor::FromNDAlloc(StorageScopedAlloc(ffi::GetRef<Storage>(this)), shape, 
dtype,
-                                  this->buffer.device, shape, scope, offset);
-}
-
-ffi::Tensor StorageObj::AllocTensor(int64_t offset, ffi::Shape shape, 
DLDataType dtype) {
-  VerifyDataType(dtype);
-
-  size_t needed_size = ffi::GetDataSize(shape.Product(), dtype);
-  TVM_FFI_ICHECK(offset + needed_size <= this->buffer.size)
-      << "storage allocation failure, attempted to allocate " << needed_size 
<< " at offset "
-      << offset << " in region that is " << this->buffer.size << "bytes";
-  class StorageAlloc {
-   public:
-    explicit StorageAlloc(Storage storage) : storage_(storage) {}
-
-    void AllocData(DLTensor* tensor, int64_t offset) {
-      if (storage_->buffer.device.device_type == kDLHexagon) {
-        // For Hexagon, non-zero offset support simply requires adjusting the
-        // beginning of data pointer
-        auto offset_ptr = reinterpret_cast<uint8_t*>(storage_->buffer.data) + 
offset;
-        tensor->data = reinterpret_cast<void*>(offset_ptr);
-        tensor->byte_offset = 0;
-      } else {
-        tensor->data = storage_->buffer.data;
-        tensor->byte_offset = offset;
-      }
-    }
-    void FreeData(DLTensor* tensor) {}
-
-   private:
-    Storage storage_;
-  };
-
-  return ffi::Tensor::FromNDAlloc(StorageAlloc(ffi::GetRef<Storage>(this)), 
shape, dtype,
-                                  this->buffer.device, offset);
-}
-
-MemoryManager* MemoryManager::Global() {
-  // NOTE: explicitly use new to avoid exit-time destruction of global state
-  // Global state will be recycled by OS as the process exits.
-  static auto* inst = new MemoryManager();
-  return inst;
-}
-
-std::string DeviceTypeStr(DLDeviceType type) {
-  switch (type) {
-    case kDLOpenCL:
-      return "opencl";
-      break;
-    case kDLVulkan:
-      return "vulkan";
-      break;
-    default:
-      return "";
-  }
-}
-
-Allocator* GetDeviceSpecificAllocator(Device dev, AllocatorType type) {
-  std::string dev_str = DeviceTypeStr(dev.device_type);
-  auto device_alloc_helper = tvm::ffi::Function::GetGlobal("DeviceAllocator." 
+ dev_str);
-  void* valloc;
-  Allocator* allocator = nullptr;
-  if (device_alloc_helper) {
-    valloc = (*device_alloc_helper)(dev, static_cast<int>(type)).cast<void*>();
-    allocator = static_cast<Allocator*>(valloc);
-  }
-  if (nullptr == allocator) {
-    switch (type) {
-      case kNaive: {
-        VLOG(1) << "New naive allocator for " << dev;
-        allocator = new NaiveAllocator();
-        break;
-      }
-      case kPooled: {
-        VLOG(1) << "New pooled allocator for " << dev;
-        allocator = new PooledAllocator();
-        break;
-      }
-      default:
-        TVM_FFI_THROW(InternalError) << "Unknown allocator type: " << type;
-    }
-  }
-  return allocator;
-}
-
-Allocator* MemoryManager::GetOrCreateAllocator(Device dev, AllocatorType type) 
{
-  MemoryManager* m = MemoryManager::Global();
-  std::lock_guard<std::mutex> lock(m->mu_);
-  if (m->allocators_.find(dev) == m->allocators_.end()) {
-    m->allocators_.emplace(dev, std::unordered_map<AllocatorType, 
std::unique_ptr<Allocator>>());
-  }
-  if (m->allocators_.at(dev).find(type) == m->allocators_.at(dev).end()) {
-    std::unique_ptr<Allocator> alloc;
-    alloc.reset(GetDeviceSpecificAllocator(dev, type));
-    auto ret = alloc.get();
-    m->allocators_.at(dev).emplace(type, std::move(alloc));
-    return ret;
-  }
-  auto alloc = m->allocators_.at(dev).at(type).get();
-
-  return alloc;
-}
-
-Allocator* MemoryManager::GetAllocator(Device dev, AllocatorType type) {
-  MemoryManager* m = MemoryManager::Global();
-  std::lock_guard<std::mutex> lock(m->mu_);
-  auto it = m->allocators_.find(dev);
-  if (it == m->allocators_.end()) {
-    TVM_FFI_THROW(InternalError) << "Allocator for " << dev << " has not been 
created yet.";
-  }
-  if (it->second.find(type) == it->second.end()) {
-    TVM_FFI_THROW(InternalError) << "Allocator for " << dev << " of type " << 
type
-                                 << " has not been created yet.";
-  }
-  return it->second.at(type).get();
-}
-
-void MemoryManager::Clear() {
-  MemoryManager* m = MemoryManager::Global();
-  std::lock_guard<std::mutex> lock(m->mu_);
-  for (const auto& [device, allocators] : m->allocators_) {
-    for (const auto& [allocator_type, allocator] : allocators) {
-      allocator->Clear();
-    }
-  }
-}
-
-ffi::Tensor Allocator::Empty(ffi::Shape shape, DLDataType dtype, DLDevice dev,
-                             ffi::Optional<ffi::String> mem_scope) {
-  VerifyDataType(dtype);
-
-  class BufferAlloc {
-   public:
-    explicit BufferAlloc(Buffer buffer) : buffer_(buffer) {}
-
-    void AllocData(DLTensor* tensor) { tensor->data = buffer_.data; }
-    void FreeData(DLTensor* tensor) {
-      MemoryManager::GetAllocator(buffer_.device, 
buffer_.alloc_type)->Free(buffer_);
-    }
-
-   private:
-    Buffer buffer_;
-  };
-
-  size_t alignment = GetDataAlignment(dtype);
-  size_t size = ffi::GetDataSize(shape.Product(), dtype);
-
-  Buffer buffer;
-  if (!mem_scope.has_value() || (*mem_scope).empty() || (*mem_scope) == 
"global") {
-    buffer = this->Alloc(dev, size, alignment, dtype);
-  } else {
-    buffer = this->Alloc(dev, shape, dtype, *mem_scope);
-  }
-  return ffi::Tensor::FromNDAlloc(BufferAlloc(buffer), shape, dtype, dev);
-}
-
-bool Allocator::AllowMemoryScope(const std::string& mem_scope) const {
-  return mem_scope.empty() || mem_scope == "global";
-}
-
-Buffer Allocator::Alloc(Device dev, ffi::Shape shape, DLDataType type_hint,
-                        const std::string& mem_scope) {
-  if (AllowMemoryScope(mem_scope)) {
-    // by default, we can always redirect to the flat memory allocations
-    size_t alignment = GetDataAlignment(type_hint);
-    size_t size = ffi::GetDataSize(shape.Product(), type_hint);
-    return Alloc(dev, size, alignment, type_hint);
-  }
-  TVM_FFI_THROW(InternalError) << "Allocator cannot allocate data space with "
-                               << "specified memory scope: " << mem_scope;
-  return {};
-}
-
-void Allocator::Clear() {
-  // This function by default does nothing.
-  // For naive allocator, no explicit manual clear is needed.
-  // Pooled allocator will override this method.
-}
-
-TVM_FFI_STATIC_INIT_BLOCK() {
-  namespace refl = tvm::ffi::reflection;
-  refl::GlobalDef().def("vm.builtin.memory_manager.clear", 
MemoryManager::Clear);
-}
-
-}  // namespace memory
-}  // namespace runtime
-}  // namespace tvm
diff --git a/src/runtime/memory/naive_allocator.h 
b/src/runtime/memory/naive_allocator.h
deleted file mode 100644
index 6b0f6d0b05..0000000000
--- a/src/runtime/memory/naive_allocator.h
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * 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/memory/naive_allocator.h
- */
-#ifndef TVM_RUNTIME_MEMORY_NAIVE_ALLOCATOR_H_
-#define TVM_RUNTIME_MEMORY_NAIVE_ALLOCATOR_H_
-
-#include <tvm/runtime/device_api.h>
-#include <tvm/runtime/logging.h>
-#include <tvm/runtime/memory/memory_manager.h>
-
-#include <algorithm>
-#include <atomic>
-#include <limits>
-#include <memory>
-#include <mutex>
-#include <string>
-#include <unordered_map>
-
-namespace tvm {
-namespace runtime {
-namespace memory {
-
-class NaiveAllocator final : public Allocator {
- public:
-  explicit NaiveAllocator() : Allocator(kNaive), used_memory_(0) {}
-
-  Buffer Alloc(Device dev, size_t nbytes, size_t alignment, DLDataType 
type_hint) final {
-    Buffer buf;
-    buf.device = dev;
-    buf.size = nbytes;
-    buf.alloc_type = kNaive;
-    TVM_FFI_CHECK(alignment && (alignment & (alignment - 1)) == 0, ValueError)
-        << "Allocation alignment must be a power of two";
-    size_t natural_alignment = std::max(static_cast<size_t>(kAllocAlignment),
-                                        static_cast<size_t>(type_hint.bits / 
8) * type_hint.lanes);
-    DLDataType storage_dtype = type_hint;
-    size_t storage_bytes = nbytes;
-    bool align_cpu = dev.device_type == kDLCPU && alignment > 
natural_alignment;
-    if (align_cpu) {
-      TVM_FFI_CHECK_LE(nbytes, std::numeric_limits<size_t>::max() - (alignment 
- 1), ValueError)
-          << "Aligned allocation size overflow";
-      storage_bytes += alignment - 1;
-    } else if (alignment > natural_alignment) {
-      TVM_FFI_CHECK_LE(alignment, std::numeric_limits<uint16_t>::max(), 
ValueError)
-          << "Backend allocation alignment cannot be represented";
-      storage_dtype = DLDataType{kDLUInt, 8, static_cast<uint16_t>(alignment)};
-    }
-    size_t element_bits = static_cast<size_t>(storage_dtype.bits) * 
storage_dtype.lanes;
-    TVM_FFI_CHECK_GT(element_bits, 0, ValueError) << "Invalid allocation 
datatype";
-    TVM_FFI_CHECK_LE(storage_bytes, (std::numeric_limits<int64_t>::max() - 
element_bits) / 8,
-                     ValueError)
-        << "Allocation size overflow";
-    int64_t length = static_cast<int64_t>(
-        (static_cast<uint64_t>(storage_bytes) * 8 + element_bits - 1) / 
element_bits);
-    ffi::Tensor owner = DeviceAPI::Get(dev)->Empty(ffi::ShapeView(&length, 1), 
storage_dtype, dev);
-    void* data = owner.data_ptr();
-    if (align_cpu) {
-      size_t space = ffi::GetDataSize(owner);
-      TVM_FFI_ICHECK(std::align(alignment, nbytes, data, space) != nullptr);
-    }
-    buf.data = data;
-    {
-      std::lock_guard<std::mutex> lock(mu_);
-      owners_.emplace(buf.data, std::move(owner));
-    }
-    used_memory_.fetch_add(nbytes, std::memory_order_relaxed);
-    DLOG(INFO) << "allocate " << nbytes << " B, used memory " << used_memory_ 
<< " B";
-    return buf;
-  }
-
-  Buffer Alloc(Device dev, ffi::Shape shape, DLDataType type_hint,
-               const std::string& mem_scope) final {
-    Buffer buf;
-    size_t nbytes = 1;
-    for (int i = 0; i < static_cast<int>(shape.size()); ++i) {
-      nbytes *= static_cast<size_t>(shape[i]);
-    }
-    nbytes *= (type_hint.bits * type_hint.lanes + 7) / 8;
-    buf.device = dev;
-    if (AllowMemoryScope(mem_scope)) {
-      auto tmp_buf = Allocator::Alloc(dev, shape, type_hint, mem_scope);
-      buf.size = tmp_buf.size;
-      buf.data = tmp_buf.data;
-      buf.alloc_type = kNaive;
-      return buf;
-    }
-
-    buf.size = nbytes;
-    auto owner = DeviceAPI::Get(dev)->Empty(shape, type_hint, dev, 
ffi::String(mem_scope));
-    buf.data = owner.data_ptr();
-    {
-      std::lock_guard<std::mutex> lock(mu_);
-      owners_.emplace(buf.data, std::move(owner));
-    }
-    used_memory_.fetch_add(nbytes, std::memory_order_relaxed);
-    DLOG(INFO) << "allocate " << nbytes << " B, used memory " << used_memory_ 
<< " B";
-    buf.alloc_type = kNaive;
-    return buf;
-  }
-
-  void Free(const Buffer& buffer) override {
-    {
-      std::lock_guard<std::mutex> lock(mu_);
-      auto it = owners_.find(buffer.data);
-      TVM_FFI_ICHECK(it != owners_.end()) << "Unknown allocation owner";
-      owners_.erase(it);
-    }
-    used_memory_.fetch_sub(buffer.size, std::memory_order_relaxed);
-    DLOG(INFO) << "free " << buffer.size << " B, used memory " << used_memory_ 
<< " B";
-  }
-
-  size_t UsedMemory() const override { return 
used_memory_.load(std::memory_order_relaxed); }
-
- private:
-  std::atomic<size_t> used_memory_;
-  std::mutex mu_;
-  std::unordered_multimap<void*, ffi::Tensor> owners_;
-};
-
-}  // namespace memory
-}  // namespace runtime
-}  // namespace tvm
-
-#endif  // TVM_RUNTIME_MEMORY_NAIVE_ALLOCATOR_H_
diff --git a/src/runtime/memory/pooled_allocator.h 
b/src/runtime/memory/pooled_allocator.h
deleted file mode 100644
index 2d20039fa0..0000000000
--- a/src/runtime/memory/pooled_allocator.h
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * 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/memory/pooled_allocator.h
- */
-#ifndef TVM_RUNTIME_MEMORY_POOLED_ALLOCATOR_H_
-#define TVM_RUNTIME_MEMORY_POOLED_ALLOCATOR_H_
-
-#include <tvm/runtime/device_api.h>
-#include <tvm/runtime/logging.h>
-#include <tvm/runtime/memory/memory_manager.h>
-
-#include <algorithm>
-#include <atomic>
-#include <limits>
-#include <memory>
-#include <mutex>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-namespace tvm {
-namespace runtime {
-namespace memory {
-
-class PooledAllocator : public Allocator {
- public:
-  static constexpr size_t kDefaultPageSize = 4096;
-
-  explicit PooledAllocator(size_t page_size = kDefaultPageSize)
-      : Allocator(kPooled), page_size_(page_size), used_memory_(0) {}
-
-  ~PooledAllocator() { ReleaseAll(); }
-
-  Buffer Alloc(Device dev, size_t nbytes, size_t alignment, DLDataType 
type_hint) override {
-    std::lock_guard<std::recursive_mutex> lock(mu_);
-    size_t size = ((nbytes + page_size_ - 1) / page_size_) * page_size_;
-    auto&& it = memory_pool_.find(size);
-    if (it != memory_pool_.end() && !it->second.empty()) {
-      auto&& pool = it->second;
-      bool addressable = 
DeviceAPI::Get(dev)->SupportsDevicePointerArithmeticsOnHost();
-      auto candidate = std::find_if(pool.begin(), pool.end(), [&](const 
Buffer& buffer) {
-        return !addressable ||
-               (alignment != 0 && reinterpret_cast<uintptr_t>(buffer.data) % 
alignment == 0);
-      });
-      if (candidate != pool.end()) {
-        Buffer ret = *candidate;
-        pool.erase(candidate);
-        return ret;
-      }
-    }
-    Buffer buf;
-    buf.device = dev;
-    buf.size = size;
-    buf.alloc_type = kPooled;
-    try {
-      buf.data = DeviceAllocDataSpace(dev, size, alignment, type_hint);
-    } catch (tvm::ffi::Error& err) {
-      LOG(WARNING) << "PooledAllocator got InternalError during allocation: " 
<< err.what();
-      LOG(WARNING) << "Trying to release all unused memory and reallocate...";
-      ReleaseAll();
-      buf.data = DeviceAllocDataSpace(dev, size, alignment, type_hint);
-    }
-
-    used_memory_.fetch_add(size, std::memory_order_relaxed);
-    VLOG(1) << "allocate " << size << " B, used memory " << used_memory_ << " 
B";
-    return buf;
-  }
-
-  Buffer Alloc(Device dev, ffi::Shape shape, DLDataType type_hint,
-               const std::string& mem_scope) override {
-    if (AllowMemoryScope(mem_scope)) {
-      return Allocator::Alloc(dev, shape, type_hint, mem_scope);
-    }
-    TVM_FFI_THROW(InternalError) << "This alloc should be implemented";
-    return {};
-  }
-
-  void Free(const Buffer& buffer) override {
-    std::lock_guard<std::recursive_mutex> lock(mu_);
-    if (memory_pool_.find(buffer.size) == memory_pool_.end()) {
-      memory_pool_.emplace(buffer.size, std::vector<Buffer>{});
-    }
-    memory_pool_.at(buffer.size).push_back(buffer);
-    VLOG(1) << "reclaim buffer " << buffer.size;
-  }
-
-  void Clear() override { ReleaseAll(); }
-
-  size_t UsedMemory() const override { return 
used_memory_.load(std::memory_order_relaxed); }
-
- protected:
-  virtual void* DeviceAllocDataSpace(Device dev, size_t nbytes, size_t 
alignment,
-                                     DLDataType type_hint) {
-    TVM_FFI_CHECK(alignment && (alignment & (alignment - 1)) == 0, ValueError)
-        << "Allocation alignment must be a power of two";
-    size_t natural_alignment = std::max(static_cast<size_t>(kAllocAlignment),
-                                        static_cast<size_t>(type_hint.bits / 
8) * type_hint.lanes);
-    DLDataType storage_dtype = type_hint;
-    size_t storage_bytes = nbytes;
-    bool align_cpu = dev.device_type == kDLCPU && alignment > 
natural_alignment;
-    if (align_cpu) {
-      TVM_FFI_CHECK_LE(nbytes, std::numeric_limits<size_t>::max() - (alignment 
- 1), ValueError)
-          << "Aligned allocation size overflow";
-      storage_bytes += alignment - 1;
-    } else if (alignment > natural_alignment) {
-      TVM_FFI_CHECK_LE(alignment, std::numeric_limits<uint16_t>::max(), 
ValueError)
-          << "Backend allocation alignment cannot be represented";
-      storage_dtype = DLDataType{kDLUInt, 8, static_cast<uint16_t>(alignment)};
-    }
-    size_t element_bits = static_cast<size_t>(storage_dtype.bits) * 
storage_dtype.lanes;
-    TVM_FFI_CHECK_GT(element_bits, 0, ValueError) << "Invalid allocation 
datatype";
-    TVM_FFI_CHECK_LE(storage_bytes, (std::numeric_limits<int64_t>::max() - 
element_bits) / 8,
-                     ValueError)
-        << "Allocation size overflow";
-    int64_t length = static_cast<int64_t>(
-        (static_cast<uint64_t>(storage_bytes) * 8 + element_bits - 1) / 
element_bits);
-    ffi::Tensor owner = DeviceAPI::Get(dev)->Empty(ffi::ShapeView(&length, 1), 
storage_dtype, dev);
-    void* data = owner.data_ptr();
-    if (align_cpu) {
-      size_t space = ffi::GetDataSize(owner);
-      TVM_FFI_ICHECK(std::align(alignment, nbytes, data, space) != nullptr);
-    }
-    owners_.emplace(data, std::move(owner));
-    return data;
-  }
-
-  virtual void DeviceFreeDataSpace(Device dev, void* ptr) {
-    auto it = owners_.find(ptr);
-    TVM_FFI_ICHECK(it != owners_.end()) << "Unknown allocation owner";
-    owners_.erase(it);
-  }
-
-  virtual void ReleaseAll() {
-    std::lock_guard<std::recursive_mutex> lock(mu_);
-    for (auto const& it : memory_pool_) {
-      auto const& pool = it.second;
-      for (auto const& buf : pool) {
-        DeviceFreeDataSpace(buf.device, buf.data);
-      }
-    }
-    memory_pool_.clear();
-    used_memory_ = 0;
-    VLOG(1) << "release all buffers";
-  }
-
- protected:
-  std::unordered_multimap<void*, ffi::Tensor> owners_;
-  size_t page_size_;
-  std::atomic<size_t> used_memory_;
-  std::unordered_map<size_t, std::vector<Buffer>> memory_pool_;
-  std::recursive_mutex mu_;
-};
-
-}  // namespace memory
-}  // namespace runtime
-}  // namespace tvm
-
-#endif  // TVM_RUNTIME_MEMORY_POOLED_ALLOCATOR_H_
diff --git a/src/runtime/tensor.cc b/src/runtime/tensor.cc
index f6bea42368..b04c13b47c 100644
--- a/src/runtime/tensor.cc
+++ b/src/runtime/tensor.cc
@@ -89,6 +89,17 @@ ffi::Tensor Empty(ffi::Shape shape, DLDataType dtype, Device 
dev,
   return DeviceAPI::Get(dev)->Empty(shape, dtype, dev, mem_scope);
 }
 
+ffi::ObjectPtr<TensorMemoryPool> CreateTensorMemoryPool(DLDevice device) {
+  auto factory = 
ffi::Function::GetGlobal(std::string("runtime.create_tensor_memory_pool.") +
+                                          
DLDeviceType2Str(device.device_type));
+  if (factory.has_value()) {
+    auto pool = (*factory)(device).cast<ffi::ObjectPtr<TensorMemoryPool>>();
+    TVM_FFI_CHECK(pool != nullptr, ValueError) << "Tensor memory pool factory 
returned null";
+    return pool;
+  }
+  return ffi::make_object<TensorMemoryPool>();
+}
+
 ffi::Tensor CreateView(ffi::Tensor source, ffi::Shape shape, DLDataType dtype,
                        uint64_t relative_byte_offset) {
   TVM_FFI_ICHECK(source.defined());
diff --git a/src/runtime/vm/builtin.cc b/src/runtime/vm/builtin.cc
index f8662dddbf..85ea9c8a79 100644
--- a/src/runtime/vm/builtin.cc
+++ b/src/runtime/vm/builtin.cc
@@ -28,7 +28,6 @@
 #include <tvm/ffi/memory.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/device_api.h>
-#include <tvm/runtime/memory/memory_manager.h>
 #include <tvm/runtime/tensor.h>
 #include <tvm/runtime/vm/builtin.h>
 #include <tvm/runtime/vm/bytecode.h>
@@ -43,6 +42,15 @@ namespace vm {
 
 using tvm::ffi::Tensor;
 
+TVM_FFI_STATIC_INIT_BLOCK() {
+  namespace refl = tvm::ffi::reflection;
+  refl::GlobalDef().def("vm.builtin.initialize_default", [](ffi::Module mod, 
DLDevice dev) {
+    auto init = mod->GetFunction("vm_initialization");
+    TVM_FFI_CHECK(init.has_value(), ValueError) << "Module has no VM 
initialization function";
+    (*init)(static_cast<int>(dev.device_type), dev.device_id, 
static_cast<int>(kDLCPU), 0);
+  });
+}
+
 //-------------------------------------------------
 //  Shape/Type handling.
 //-------------------------------------------------
@@ -54,7 +62,7 @@ using tvm::ffi::Tensor;
  */
 ffi::Tensor AllocShapeHeap(void* ctx_ptr, int64_t size) {
   VirtualMachine* vm = static_cast<VirtualMachine*>(ctx_ptr);
-  // use host allocator, which is always last element.
+  // Use the host pool, which is always the last element.
   size_t host_device_index = vm->devices.size() - 1;
   // specially handle hexagon on-device RT.
   // TODO(relax-team): visit and consider other possible choices.
@@ -63,8 +71,8 @@ ffi::Tensor AllocShapeHeap(void* ctx_ptr, int64_t size) {
   } else {
     TVM_FFI_ICHECK_EQ(vm->devices[host_device_index].device_type, kDLCPU);
   }
-  auto* alloc = vm->allocators[host_device_index];
-  return alloc->Empty({size}, DLDataType{kDLInt, 64, 1}, 
vm->devices[host_device_index]);
+  auto& pool = vm->memory_pools[host_device_index];
+  return pool->Empty({size}, DLDataType{kDLInt, 64, 1}, 
vm->devices[host_device_index]);
 }
 
 TVM_FFI_STATIC_INIT_BLOCK() {
@@ -377,24 +385,19 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 //-------------------------------------------------
 //  Storage management.
 //-------------------------------------------------
-Storage VMAllocStorage(void* ctx_ptr, ffi::Shape buffer_shape, Index 
device_index,
-                       DLDataType dtype_hint, ffi::String mem_scope) {
+ffi::Tensor VMAllocStorage(void* ctx_ptr, ffi::Shape buffer_shape, Index 
device_index,
+                           DLDataType dtype_hint, ffi::String mem_scope) {
   VirtualMachine* vm = static_cast<VirtualMachine*>(ctx_ptr);
-
-  TVM_FFI_ICHECK_LT(device_index, vm->devices.size())
-      << "The device index is out of VM physical devices list";
-
   if (device_index == -1) {
     // Allocate on host. Host is always the last element of vm->devices.
     device_index = vm->devices.size() - 1;
   }
-
-  auto* alloc = vm->allocators[device_index];
-  TVM_FFI_ICHECK(alloc) << "Did you forget to init the VirtualMachine with 
devices?";
-
-  auto buffer = alloc->Alloc(vm->devices[device_index], buffer_shape, 
dtype_hint, mem_scope);
-
-  return Storage(buffer, alloc);
+  TVM_FFI_ICHECK_GE(device_index, 0);
+  TVM_FFI_ICHECK_LT(device_index, vm->devices.size())
+      << "The device index is out of VM physical devices list";
+  auto& pool = vm->memory_pools[device_index];
+  TVM_FFI_ICHECK(pool) << "Did you forget to init the VirtualMachine with 
devices?";
+  return pool->Empty(buffer_shape, dtype_hint, vm->devices[device_index], 
mem_scope);
 }
 
 TVM_FFI_STATIC_INIT_BLOCK() {
@@ -402,16 +405,25 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   refl::GlobalDef()
       .def("vm.builtin.alloc_storage", VMAllocStorage)
       .def_packed("vm.builtin.alloc_tensor", [](ffi::PackedArgs args, 
ffi::Any* rv) {
-        Storage sobj = args[0].cast<Storage>();
-        int64_t offset = args[1].cast<int64_t>();
-        ffi::Shape shape = args[2].cast<ffi::Shape>();
-        DLDataType dtype = args[3].cast<DLDataType>();
-        if (args.size() == 5) {
-          ffi::String scope = args[4].cast<ffi::String>();
-          *rv = sobj->AllocTensorScoped(offset, shape, dtype, scope);
-        } else {
-          *rv = sobj->AllocTensor(offset, shape, dtype);
+        VirtualMachine* vm = VirtualMachine::GetContextPtr(args[0]);
+        ffi::Tensor backing = args[1].cast<ffi::Tensor>();
+        int64_t offset = args[2].cast<int64_t>();
+        ffi::Shape shape = args[3].cast<ffi::Shape>();
+        DLDataType dtype = args[4].cast<DLDataType>();
+        ffi::Optional<ffi::String> scope = std::nullopt;
+        if (args.size() == 6) {
+          scope = args[5].cast<ffi::String>();
+        }
+        TVM_FFI_CHECK_GE(offset, 0, ValueError) << "Tensor byte offset must be 
nonnegative";
+        DLDevice device = backing.device();
+        for (size_t i = 0; i < vm->devices.size(); ++i) {
+          if (vm->devices[i].device_type == device.device_type &&
+              vm->devices[i].device_id == device.device_id) {
+            *rv = vm->memory_pools[i]->CreateView(std::move(backing), shape, 
dtype, offset, scope);
+            return;
+          }
         }
+        TVM_FFI_THROW(ValueError) << "Tensor backing device is not initialized 
in the VM";
       });
 }
 
diff --git a/src/runtime/vm/lm_support.cc b/src/runtime/vm/lm_support.cc
index 79d81a61f7..38286f60a2 100644
--- a/src/runtime/vm/lm_support.cc
+++ b/src/runtime/vm/lm_support.cc
@@ -42,7 +42,6 @@
 #include <tvm/ffi/memory.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/device_api.h>
-#include <tvm/runtime/memory/memory_manager.h>
 #include <tvm/runtime/tensor.h>
 #include <tvm/runtime/vm/vm.h>
 
diff --git a/src/runtime/vm/paged_kv_cache.cc b/src/runtime/vm/paged_kv_cache.cc
index 59d6192bbf..836a90c948 100644
--- a/src/runtime/vm/paged_kv_cache.cc
+++ b/src/runtime/vm/paged_kv_cache.cc
@@ -28,7 +28,6 @@
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/device_api.h>
 #include <tvm/runtime/disco/disco_worker.h>
-#include <tvm/runtime/memory/memory_manager.h>
 #include <tvm/runtime/tensor.h>
 #include <tvm/support/cuda/nvtx.h>
 
diff --git a/src/runtime/vm/vm.cc b/src/runtime/vm/vm.cc
index 751762bc79..12aa482c18 100644
--- a/src/runtime/vm/vm.cc
+++ b/src/runtime/vm/vm.cc
@@ -25,7 +25,6 @@
 #include <tvm/ffi/container/tensor.h>
 #include <tvm/ffi/function.h>
 #include <tvm/runtime/logging.h>
-#include <tvm/runtime/memory/memory_manager.h>
 #include <tvm/runtime/vm/vm.h>
 #include <tvm/support/cuda/nvtx.h>
 
@@ -87,27 +86,27 @@ ffi::Any IndexIntoNestedObject(ffi::Any obj, 
ffi::PackedArgs args, int starting_
   return obj;
 }
 
-ffi::Tensor ConvertTensorToDevice(ffi::Tensor src, const DLDevice& dev, 
Allocator* alloc,
+ffi::Tensor ConvertTensorToDevice(ffi::Tensor src, const DLDevice& dev, 
TensorMemoryPool* pool,
                                   ffi::String scope = "global") {
   TVM_FFI_ICHECK(src.defined());
   if (src.device().device_type == dev.device_type && src.device().device_id == 
dev.device_id) {
     return src;
   } else {
-    auto res = alloc->Empty(src.shape(), src.dtype(), dev, scope);
+    auto res = pool->Empty(src.shape(), src.dtype(), dev, scope);
     tvm::runtime::CopyFromTo(src, res);
     return res;
   }
 }
 
-Any ConvertObjectToDevice(Any src, const Device& dev, Allocator* alloc,
+Any ConvertObjectToDevice(Any src, const Device& dev, TensorMemoryPool* pool,
                           ffi::String scope = "global") {
   if (src.as<ffi::Tensor::ContainerType>()) {
-    return ConvertTensorToDevice(src.cast<ffi::Tensor>(), dev, alloc, scope);
+    return ConvertTensorToDevice(src.cast<ffi::Tensor>(), dev, pool, scope);
   } else if (src.as<ffi::ArrayObj>()) {
     std::vector<Any> ret;
     auto arr = src.cast<ffi::Array<Any>>();
     for (size_t i = 0; i < arr.size(); i++) {
-      ret.push_back(ConvertObjectToDevice(arr[i], dev, alloc));
+      ret.push_back(ConvertObjectToDevice(arr[i], dev, pool));
     }
     return ffi::Array<Any>(ret.begin(), ret.end());
   } else {
@@ -115,18 +114,18 @@ Any ConvertObjectToDevice(Any src, const Device& dev, 
Allocator* alloc,
   }
 }
 
-ffi::Any ConvertArgToDevice(ffi::AnyView input, Device dev, Allocator* alloc) {
+ffi::Any ConvertArgToDevice(ffi::AnyView input, Device dev, TensorMemoryPool* 
pool) {
   // in terms of memory-behavior.
   // To be extra careful, we copy DLTensor.
   // The developer can still explicitly allocate Tensor
   // in TVM Native API or Tensor::FromDLPack to regain zero copy behavior.
   ffi::Any ret;
   if (auto opt_obj = input.as<ffi::ObjectRef>()) {
-    ret = ConvertObjectToDevice(opt_obj.value(), dev, alloc);
+    ret = ConvertObjectToDevice(opt_obj.value(), dev, pool);
   } else if (auto opt_dltensor = input.as<DLTensor*>()) {
     DLTensor* tensor = opt_dltensor.value();
-    std::vector<int64_t> shape(tensor->shape, tensor->shape + tensor->ndim);
-    auto dst = alloc->Empty(shape, tensor->dtype, dev);
+    ffi::ShapeView shape(tensor->shape, tensor->ndim);
+    auto dst = pool->Empty(shape, tensor->dtype, dev);
     tvm::runtime::CopyFromTo(tensor, dst);
     ret = dst;
   } else {
@@ -135,11 +134,11 @@ ffi::Any ConvertArgToDevice(ffi::AnyView input, Device 
dev, Allocator* alloc) {
   return ret;
 }
 
-ffi::Any ConvertRegToDevice(ffi::Any input, Device dev, Allocator* alloc,
+ffi::Any ConvertRegToDevice(ffi::Any input, Device dev, TensorMemoryPool* pool,
                             ffi::String scope = "global") {
   ffi::Any ret;
   if (auto opt_obj = input.as<ffi::ObjectRef>()) {
-    ret = ConvertObjectToDevice(opt_obj.value(), dev, alloc, scope);
+    ret = ConvertObjectToDevice(opt_obj.value(), dev, pool, scope);
   } else {
     ret = input;
   }
@@ -194,8 +193,7 @@ class VirtualMachineImpl : public VirtualMachine {
   // Public facing functions overloading
   //---------------------------------------------------
   void LoadExecutable(ffi::ObjectPtr<VMExecutable> exec) final;
-  void Init(const std::vector<Device>& devices,
-            const std::vector<AllocatorType>& alloc_types) final;
+  void Init(const std::vector<Device>& devices) final;
   VMClosure GetClosure(const ffi::String& func_name) final {
     return this->GetClosureInternal(func_name, false).value();
   }
@@ -463,23 +461,19 @@ void 
VirtualMachineImpl::LoadExecutable(ffi::ObjectPtr<VMExecutable> exec) {
   this->imports_ = exec->imports();
 }
 
-void VirtualMachineImpl::Init(const std::vector<Device>& devices,
-                              const std::vector<AllocatorType>& alloc_types) {
-  TVM_FFI_ICHECK_EQ(devices.size(), alloc_types.size());
-
+void VirtualMachineImpl::Init(const std::vector<Device>& devices) {
   this->devices.reserve(devices.size());
-  this->allocators.reserve(alloc_types.size());
-  for (size_t i = 0; i < devices.size(); i++) {
-    auto alloc = MemoryManager::GetOrCreateAllocator(devices[i], 
alloc_types[i]);
-    this->devices.push_back(devices[i]);
-    this->allocators.push_back(alloc);
+  this->memory_pools.reserve(devices.size());
+  for (Device device : devices) {
+    this->devices.push_back(device);
+    this->memory_pools.push_back(CreateTensorMemoryPool(device));
   }
   // Setup constant sections.
   this->const_pool_.reserve(exec_->constants.size());
   for (size_t i = 0; i < exec_->constants.size(); ++i) {
     if (auto opt_nd = exec_->constants[i].as<ffi::Tensor>(); opt_nd && 
opt_nd->defined()) {
-      this->const_pool_.push_back(
-          ConvertRegToDevice(opt_nd.value(), devices[0], allocators[0], 
exec_->memory_scopes[i]));
+      this->const_pool_.push_back(ConvertRegToDevice(
+          opt_nd.value(), devices[0], memory_pools[0].get(), 
exec_->memory_scopes[i]));
     } else {
       this->const_pool_.push_back(exec_->constants[i]);
     }
@@ -519,7 +513,7 @@ void VirtualMachineImpl::SetInput(std::string func_name, 
bool with_param_module,
         // call param func to get the arguments(usually corresponds to param 
pack.)
         func_args[i] = 
(args[i].cast<ffi::Module>())->GetFunction("get_params").value()();
       } else {
-        func_args[i] = ConvertArgToDevice(args[i], devices[0], allocators[0]);
+        func_args[i] = ConvertArgToDevice(args[i], devices[0], 
memory_pools[0].get());
       }
     }
     inputs_[func_name] = func_args;
@@ -585,7 +579,7 @@ void VirtualMachineImpl::SaveClosure(const ffi::String& 
func_name, const ffi::St
   VMClosure clo = this->GetClosure(func_name);
   std::vector<RegType> inputs(args.size());
   for (int i = 0; i < args.size(); ++i) {
-    inputs[i] = ConvertArgToDevice(args[i], this->devices[0], 
this->allocators[0]);
+    inputs[i] = ConvertArgToDevice(args[i], this->devices[0], 
this->memory_pools[0].get());
   }
   ffi::Function impl = VMClosure::BindLastArgs(clo->impl, inputs);
   if (!include_return) {
@@ -837,17 +831,14 @@ ffi::ObjectPtr<VirtualMachine> VirtualMachine::Create() {
 //--------------------------------------------------------------------
 
 void VirtualMachineImpl::_Init(ffi::PackedArgs args, ffi::Any* rv) {
-  TVM_FFI_ICHECK_EQ(args.size() % 3, 0);
+  TVM_FFI_ICHECK_EQ(args.size() % 2, 0);
   std::vector<Device> devices;
-  std::vector<AllocatorType> alloc_types;
-  for (int i = 0; i < args.size(); i += 3) {
+  for (int i = 0; i < args.size(); i += 2) {
     int device_type = args[i].cast<int>();
     int device_id = args[i + 1].cast<int>();
-    int alloc_type = args[i + 2].cast<int>();
     devices.push_back(Device{DLDeviceType(device_type), device_id});
-    alloc_types.push_back(AllocatorType(alloc_type));
   }
-  this->Init(devices, alloc_types);
+  this->Init(devices);
 }
 
 void VirtualMachineImpl::_SaveClosure(ffi::PackedArgs args, ffi::Any* rv) {
diff --git a/src/runtime/workspace_pool.cc b/src/runtime/workspace_pool.cc
index c0ba44f647..1549d3cbef 100644
--- a/src/runtime/workspace_pool.cc
+++ b/src/runtime/workspace_pool.cc
@@ -24,33 +24,11 @@
 #include "workspace_pool.h"
 
 #include <algorithm>
-#include <array>
-#include <limits>
 #include <memory>
 
 namespace tvm {
 namespace runtime {
 
-namespace {
-struct WorkspacePools {
-  std::mutex mutex;
-  std::vector<WorkspacePool*> pools;
-};
-WorkspacePools* AllWorkspacePools() {
-  // Kept alive through thread-local pool destruction at process exit.
-  static auto* registry = new WorkspacePools();
-  return registry;
-}
-}  // namespace
-
-void ResetWorkspacePool(DLDeviceType device_type) {
-  auto* registry = AllWorkspacePools();
-  std::lock_guard<std::mutex> lock(registry->mutex);
-  for (auto* pool : registry->pools) {
-    if (pool->device_type_ == device_type) pool->Reset();
-  }
-}
-
 // page size.
 constexpr size_t kWorkspacePageSize = 4 << 10;
 
@@ -66,29 +44,25 @@ class WorkspacePool::Pool {
     allocated_.push_back(e);
   }
   // allocate from pool
-  void* Alloc(Device dev, DeviceAPI* device, size_t nbytes) {
-    TVM_FFI_CHECK_LE(nbytes, std::numeric_limits<size_t>::max() - 
kWorkspacePageSize, ValueError)
-        << "Workspace size overflow";
+  void* Alloc(Device dev, DeviceAPI* device, Allocator alloc, size_t nbytes) {
     // Allocate align to page.
     nbytes = (nbytes + (kWorkspacePageSize - 1)) / kWorkspacePageSize * 
kWorkspacePageSize;
     if (nbytes == 0) nbytes = kWorkspacePageSize;
     Entry e;
     auto allocate = [&]() {
-      TVM_FFI_CHECK_LE(nbytes / kTempAllocaAlignment, 
std::numeric_limits<int64_t>::max(),
-                       ValueError)
-          << "Workspace shape overflow";
-      int64_t shape = static_cast<int64_t>(nbytes / kTempAllocaAlignment +
-                                           (nbytes % kTempAllocaAlignment != 
0));
-      DLDataType dtype{kDLUInt, 8, kTempAllocaAlignment};
-      return device->Empty(ffi::ShapeView(&shape, 1), dtype, dev);
+      if (alloc) return alloc(device, dev, nbytes);
+      // Preserve the workspace alignment when adapting raw allocations to 
Empty.
+      int64_t size = static_cast<int64_t>(nbytes / kTempAllocaAlignment +
+                                          (nbytes % kTempAllocaAlignment != 
0));
+      return device->Empty(ffi::ShapeView(&size, 1), DLDataType{kDLUInt, 8, 
kTempAllocaAlignment},
+                           dev);
     };
     if (free_list_.size() == 2) {
       e = free_list_.back();
       free_list_.pop_back();
       if (e.size < nbytes) {
-        // Release the cached page before growing it.
+        // resize the page
         e.owner = ffi::Tensor();
-        e.data = nullptr;
         e.owner = allocate();
         e.data = e.owner.data_ptr();
         e.size = nbytes;
@@ -110,19 +84,16 @@ class WorkspacePool::Pool {
         e = free_list_.back();
         free_list_.pop_back();
         e.owner = ffi::Tensor();
-        e.data = nullptr;
         e.owner = allocate();
         e.data = e.owner.data_ptr();
         e.size = nbytes;
       }
     }
-    e.generation = generation_;
     allocated_.push_back(e);
     return e.data;
   }
   // free resource back to pool
   void Free(void* data) {
-    TVM_FFI_CHECK(data != nullptr, ValueError) << "Cannot free a null 
workspace";
     Entry e;
     if (allocated_.back().data == data) {
       // quick path, last allocated.
@@ -136,7 +107,8 @@ class WorkspacePool::Pool {
       e = allocated_[index];
       allocated_.erase(allocated_.begin() + index);
     }
-    if (e.generation != generation_) return;
+    // Clear marks outstanding allocations so they cannot enter a replacement 
session's cache.
+    if (e.size == 0) return;
     if (free_list_.back().size < e.size) {
       free_list_.push_back(e);
     } else if (free_list_.size() == 2) {
@@ -151,77 +123,64 @@ class WorkspacePool::Pool {
       free_list_[i + 1] = e;
     }
   }
-  void Reset() {
-    // Live allocations keep their original owners and are discarded when 
freed.
-    ++generation_;
-    free_list_.clear();
-    free_list_.emplace_back();
+  void Clear() {
+    free_list_.resize(1);
+    for (auto& entry : allocated_) entry.size = 0;
   }
-  // Release all resources
-  void Release() {
-    free_list_.clear();
-    allocated_.clear();
+
+  bool Contains(void* data) const {
+    return std::any_of(allocated_.begin() + 1, allocated_.end(),
+                       [data](const Entry& entry) { return entry.data == data; 
});
   }
 
  private:
   /*! \brief a single entry in the pool */
   struct Entry {
-    uint64_t generation{0};
     ffi::Tensor owner;
-    void* data{nullptr};
-    size_t size{0};
+    void* data;
+    size_t size;
   };
-  uint64_t generation_{0};
   /*! \brief List of free items, sorted from small to big size */
   std::vector<Entry> free_list_;
   /*! \brief List of allocated items */
   std::vector<Entry> allocated_;
 };
 
-WorkspacePool::WorkspacePool(DLDeviceType device_type, DeviceAPI* device)
-    : device_type_(device_type), device_(device) {
-  auto* registry = AllWorkspacePools();
-  std::lock_guard<std::mutex> lock(registry->mutex);
-  registry->pools.push_back(this);
-}
+WorkspacePool::WorkspacePool(DLDeviceType device_type, DeviceAPI* device, 
Allocator alloc)
+    : device_type_(device_type), device_(device), alloc_(alloc) {}
 
 WorkspacePool::~WorkspacePool() {
-  auto* registry = AllWorkspacePools();
-  std::lock_guard<std::mutex> lock(registry->mutex);
-  auto& pools = registry->pools;
-  pools.erase(std::remove(pools.begin(), pools.end(), this), pools.end());
-  for (size_t i = 0; i < array_.size(); ++i) {
-    if (array_[i] != nullptr) {
-      array_[i]->Release();
-      delete array_[i];
-    }
+  for (auto& streams : array_) {
+    for (auto& [stream, pool] : streams) delete pool;
   }
 }
 
-void WorkspacePool::Reset() {
-  std::lock_guard<std::mutex> lock(mutex_);
-  for (auto* pool : array_) {
-    if (pool) pool->Reset();
+void WorkspacePool::Clear() {
+  for (auto& streams : array_) {
+    for (auto& [stream, pool] : streams) pool->Clear();
   }
 }
 
 void* WorkspacePool::AllocWorkspace(Device dev, size_t size) {
-  TVM_FFI_CHECK_GE(dev.device_id, 0, ValueError) << "Invalid workspace device 
index";
-  std::lock_guard<std::mutex> lock(mutex_);
   if (static_cast<size_t>(dev.device_id) >= array_.size()) {
-    array_.resize(dev.device_id + 1, nullptr);
+    array_.resize(dev.device_id + 1);
   }
-  if (array_[dev.device_id] == nullptr) {
-    array_[dev.device_id] = new Pool();
-  }
-  return array_[dev.device_id]->Alloc(dev, device_, size);
+  // Keep the historical size-sorted cache separate for each execution stream.
+  auto& pool = array_[dev.device_id][device_->GetCurrentStream(dev)];
+  if (pool == nullptr) pool = new Pool();
+  return pool->Alloc(dev, device_, alloc_, size);
 }
 
 void WorkspacePool::FreeWorkspace(Device dev, void* ptr) {
-  std::lock_guard<std::mutex> lock(mutex_);
-  TVM_FFI_ICHECK(static_cast<size_t>(dev.device_id) < array_.size() &&
-                 array_[dev.device_id] != nullptr);
-  array_[dev.device_id]->Free(ptr);
+  TVM_FFI_ICHECK(static_cast<size_t>(dev.device_id) < array_.size());
+  // A caller may change streams between allocation and release.
+  for (auto& [stream, pool] : array_[dev.device_id]) {
+    if (pool->Contains(ptr)) {
+      pool->Free(ptr);
+      return;
+    }
+  }
+  TVM_FFI_THROW(InternalError) << "trying to free things that has not been 
allocated";
 }
 
 }  // namespace runtime
diff --git a/src/runtime/workspace_pool.h b/src/runtime/workspace_pool.h
index 27e9005d8a..f72e0089af 100644
--- a/src/runtime/workspace_pool.h
+++ b/src/runtime/workspace_pool.h
@@ -27,14 +27,11 @@
 #include <tvm/runtime/device_api.h>
 
 #include <memory>
-#include <mutex>
+#include <unordered_map>
 #include <vector>
 
 namespace tvm {
 namespace runtime {
-// Invalidate cached workspace owners when a backend tears down a resource 
session.
-TVM_RUNTIME_DLL void ResetWorkspacePool(DLDeviceType device_type);
-
 /*!
  * \brief A workspace pool to manage
  *
@@ -46,14 +43,16 @@ TVM_RUNTIME_DLL void ResetWorkspacePool(DLDeviceType 
device_type);
  *  - The release order is usually in reverse order of allocate
  *  - Repeative pattern of same allocations over different runs.
  */
-class WorkspacePool {
+class TVM_RUNTIME_DLL WorkspacePool {
  public:
   /*!
    * \brief Create pool with specific device type and device.
    * \param device_type The device type.
    * \param device_api The device API.
+   * \param alloc Optional backend allocation for unpooled owning workspace 
tensors.
    */
-  WorkspacePool(DLDeviceType device_type, DeviceAPI* device_api);
+  using Allocator = ffi::Tensor (*)(DeviceAPI*, Device, size_t);
+  WorkspacePool(DLDeviceType device_type, DeviceAPI* device_api, Allocator 
alloc = nullptr);
   /*! \brief destructor */
   ~WorkspacePool();
   /*!
@@ -69,18 +68,18 @@ class WorkspacePool {
    * \param ptr The pointer to be freed.
    */
   void FreeWorkspace(Device dev, void* ptr);
+  /*! \brief Release cached allocations; outstanding allocations remain valid 
until freed. */
+  void Clear();
 
  private:
   class Pool;
-  friend void ResetWorkspacePool(DLDeviceType device_type);
-  void Reset();
-  std::mutex mutex_;
   /*! \brief pool of device local array */
-  std::vector<Pool*> array_;
+  std::vector<std::unordered_map<TVMStreamHandle, Pool*>> array_;
   /*! \brief device type this pool support */
   DLDeviceType device_type_;
   /*! \brief The device API */
   DeviceAPI* device_;
+  Allocator alloc_;
 };
 
 }  // namespace runtime
diff --git a/tests/python/disco/test_custom_allreduce.py 
b/tests/python/disco/test_custom_allreduce.py
index db23257703..255571e20f 100644
--- a/tests/python/disco/test_custom_allreduce.py
+++ b/tests/python/disco/test_custom_allreduce.py
@@ -64,10 +64,8 @@ def test_allreduce(shape, ccl, strategy):
     num_elements = reduce(lambda x, y: x * y, shape)
     dtype = "float32"
     falloc_ipc_storage = 
sess.get_global_func("runtime.disco.cuda_ipc.alloc_storage")
-    falloc_tensor = sess.get_global_func("vm.builtin.alloc_tensor")
     fallreduce = 
sess.get_global_func("runtime.disco.cuda_ipc.custom_allreduce")
-    d_storage = sess.call_packed(falloc_ipc_storage, Shape(shape), 
DataType(dtype))
-    d_input = sess.call_packed(falloc_tensor, d_storage, 0, Shape(shape), 
DataType(dtype))
+    d_input = sess.call_packed(falloc_ipc_storage, Shape(shape), 
DataType(dtype))
 
     array_1 = np.arange(num_elements, dtype="float32").reshape(*shape)
     array_2 = np.arange(start=1, stop=-(num_elements - 1), step=-1, 
dtype="float32").reshape(*shape)
diff --git a/tests/python/relax/test_vm_alloc_storage_with_scope.py 
b/tests/python/relax/test_vm_alloc_storage_with_scope.py
index 4b929c8fe1..a7ceb5a4bf 100644
--- a/tests/python/relax/test_vm_alloc_storage_with_scope.py
+++ b/tests/python/relax/test_vm_alloc_storage_with_scope.py
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-"""Test Naive allocator with memory scope for Relax VM"""
+"""Test tensor allocation with memory scope for Relax VM"""
 
 import numpy as np
 
@@ -66,8 +66,7 @@ def test_alloc_storage_with_scope_global():
         lib = tvm.relax.build(mod, target=target)
 
     dev = tvm.cpu()
-    # This is the important line which tests nd allocator
-    vm_rt = relax.VirtualMachine(lib, dev, memory_cfg="naive")
+    vm_rt = relax.VirtualMachine(lib, dev)
     x = tvm.runtime.tensor(arg0, dev)
     vm_rt.set_input("main", x)
     vm_rt.invoke_stateful("main")
diff --git a/tests/python/relax/test_vm_execbuilder.py 
b/tests/python/relax/test_vm_execbuilder.py
index 35ac921956..785aea568c 100644
--- a/tests/python/relax/test_vm_execbuilder.py
+++ b/tests/python/relax/test_vm_execbuilder.py
@@ -183,7 +183,9 @@ def test_vm_storage():
             dst=ib.r(1),
         )
         ib.emit_call(
-            "vm.builtin.alloc_tensor", args=[ib.r(1), ib.imm(0), shape, 
dtype], dst=ib.r(2)
+            "vm.builtin.alloc_tensor",
+            args=[ib.vm_state(), ib.r(1), ib.imm(0), shape, dtype],
+            dst=ib.r(2),
         )
         ib.emit_ret(ib.r(2))
     ex = ib.get()
diff --git a/web/emcc/wasm_runtime.cc b/web/emcc/wasm_runtime.cc
index b1eaf6c32f..6d3c1b7bb0 100644
--- a/web/emcc/wasm_runtime.cc
+++ b/web/emcc/wasm_runtime.cc
@@ -67,7 +67,6 @@
 #include "3rdparty/tvm-ffi/src/ffi/object.cc"
 #include "3rdparty/tvm-ffi/src/ffi/tensor.cc"
 #include "3rdparty/tvm-ffi/src/ffi/testing/testing.cc"
-#include "src/runtime/memory/memory_manager.cc"
 #include "src/runtime/vm/attn_backend.cc"
 #include "src/runtime/vm/builtin.cc"
 #include "src/runtime/vm/bytecode.cc"
diff --git a/web/src/runtime.ts b/web/src/runtime.ts
index d615f2bbbd..1f3b26a74b 100644
--- a/web/src/runtime.ts
+++ b/web/src/runtime.ts
@@ -833,11 +833,6 @@ export class TVMArray extends TVMObject {
   }
 }
 
-export enum VMAllocatorKind {
-  NAIVE_ALLOCATOR = 1,
-  POOLED_ALLOCATOR = 2,
-}
-
 /**
  *  VirtualMachine Executor.
  *
@@ -857,11 +852,9 @@ export class VirtualMachine implements Disposable {
     this.mod.getFunction("vm_initialization")(
       new Scalar(device.deviceType, "int"),
       new Scalar(device.deviceId, "int"),
-      new Scalar(VMAllocatorKind.POOLED_ALLOCATOR, "int"),
       // explicitly specify host device type
       new Scalar(DeviceStrToEnum.cpu, "int"),
       new Scalar(0, "int"),
-      new Scalar(VMAllocatorKind.POOLED_ALLOCATOR, "int"),
     );
   }
 

Reply via email to