You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tvm.apache.org by tq...@apache.org on 2026/02/14 22:42:16 UTC

(tvm) branch dmlc created (now 0c2dacbcd5)

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

tqchen pushed a change to branch dmlc
in repository https://gitbox.apache.org/repos/asf/tvm.git


      at 0c2dacbcd5 [REFACTOR] Minimize the dmlc dep

This branch includes the following new commits:

     new 0c2dacbcd5 [REFACTOR] Minimize the dmlc dep

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(tvm) 01/01: [REFACTOR] Minimize the dmlc dep

Posted by tq...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 0c2dacbcd5ef1088ca35e5282cad17c915791e0a
Author: tqchen <ti...@gmail.com>
AuthorDate: Sat Feb 14 17:10:00 2026 -0500

    [REFACTOR] Minimize the dmlc dep
    
    This PR simplifies the project by removing dmlc dep and
    modernize to use standard ffi and support utils.
    
    JSON usages now redirects to tvm/ffi/extra/json.h
    the io related feature are internalized to tvm/support/io.h
    
    This change will help us to reduce the overall complexity and
    remove indirections like logging customization macros.
---
 3rdparty/nvbench/l2_cache_flush.h                  |   2 +-
 CMakeLists.txt                                     |  16 +-
 Makefile                                           |   5 +-
 apps/android_rpc/app/src/main/jni/Android.mk       |   1 -
 apps/android_rpc/app/src/main/jni/tvm_runtime.h    |   1 -
 apps/cpp_rpc/CMakeLists.txt                        |   1 -
 apps/cpp_rpc/main.cc                               |   3 -
 apps/cpp_rpc/rpc_env.cc                            |   2 +-
 apps/cpp_rpc/rpc_server.cc                         |   2 -
 apps/hexagon_launcher/cmake/HexagonLauncher.cmake  |   1 -
 apps/hexagon_launcher/cmake/android/CMakeLists.txt |   1 -
 apps/hexagon_launcher/cmake/hexagon/CMakeLists.txt |   2 -
 apps/hexagon_launcher/launcher_core.cc             |  88 ++--
 apps/hexagon_launcher/launcher_core.h              |  10 +-
 apps/ios_rpc/tvmrpc.xcodeproj/project.pbxproj      |   4 -
 cmake/modules/CUDA.cmake                           |   1 -
 cmake/modules/LLVM.cmake                           |   4 +-
 cmake/modules/LibInfo.cmake                        |   1 -
 cmake/modules/contrib/CUTLASS.cmake                |   2 -
 cmake/modules/contrib/MSCCLPP.cmake                |   1 -
 docs/Doxyfile                                      |   2 +-
 include/tvm/ir/attrs.h                             |   1 -
 include/tvm/ir/op.h                                |   2 +-
 include/tvm/runtime/disco/session.h                |   2 +
 include/tvm/runtime/logging.h                      |  20 +-
 include/tvm/runtime/module.h                       |   2 +-
 include/tvm/runtime/serializer.h                   |  49 +-
 include/tvm/runtime/tensor.h                       |  29 +-
 include/tvm/runtime/vm/executable.h                |  39 +-
 include/tvm/s_tir/schedule/instruction.h           |   2 +-
 include/tvm/support/io.h                           | 136 ++++++
 include/tvm/support/serializer.h                   | 232 +++++++++
 include/tvm/support/with.h                         |   4 +-
 include/tvm/target/tag.h                           |   2 +-
 include/tvm/target/target_kind.h                   |   2 +-
 python/tvm/contrib/hexagon/tools.py                |   2 -
 python/tvm/libinfo.py                              |   5 -
 python/tvm/relax/frontend/nn/extern.py             |   7 -
 src/arith/solve_linear_equation.cc                 |   2 +
 src/contrib/msc/core/codegen/base_codegen.h        |   7 +-
 src/contrib/msc/core/codegen/codegen_json.cc       |   2 +-
 src/contrib/msc/core/codegen/codegen_json.h        |  25 +-
 src/contrib/msc/core/codegen/codegen_utils.h       |  60 ++-
 src/contrib/msc/core/codegen/cpp_codegen.h         |   1 -
 src/contrib/msc/core/codegen/py_codegen.h          |   1 -
 src/contrib/msc/core/ir/graph.cc                   |  48 +-
 src/contrib/msc/core/ir/graph.h                    | 537 ++++++++++++++-------
 src/contrib/msc/core/ir/graph_builder.h            |  88 ++--
 src/contrib/msc/core/ir/plugin.cc                  |  22 +-
 src/contrib/msc/core/ir/plugin.h                   | 326 ++++++++-----
 src/contrib/msc/core/printer/msc_base_printer.h    |  34 +-
 src/contrib/msc/core/transform/set_expr_name.cc    |   2 +
 .../msc/framework/tensorflow/codegen_utils.h       |   8 +-
 src/contrib/msc/framework/tensorflow/config.h      |  13 +-
 src/contrib/msc/framework/tensorrt/codegen_utils.h |  66 +--
 .../msc/framework/tensorrt/transform_tensorrt.cc   |  28 +-
 src/contrib/msc/framework/torch/codegen_utils.h    |   8 +-
 src/contrib/msc/framework/torch/config.h           |  13 +-
 src/contrib/msc/framework/tvm/codegen_utils.h      |  18 +-
 src/contrib/msc/framework/tvm/config.h             |  18 +-
 src/contrib/msc/plugin/base_codegen.h              |   7 +-
 src/contrib/msc/plugin/codegen_utils.h             |  81 +++-
 src/contrib/msc/plugin/tensorrt_codegen.h          |  13 +-
 src/contrib/msc/plugin/torch_codegen.h             |  18 +-
 src/contrib/msc/plugin/tvm_codegen.cc              |   1 -
 src/contrib/msc/plugin/tvm_codegen.h               |  18 +-
 src/ir/instrument.cc                               |  17 +-
 src/ir/transform.cc                                |  12 +-
 src/node/structural_hash.cc                        |  11 +-
 src/relax/analysis/var2value.cc                    |   2 +
 src/relax/backend/contrib/clml/codegen.cc          |  10 +-
 .../backend/contrib/codegen_json/codegen_json.h    |  52 +-
 src/relax/backend/contrib/cublas/codegen.cc        |   2 +-
 src/relax/backend/contrib/cudnn/codegen.cc         |   4 +-
 src/relax/backend/contrib/nnapi/codegen.cc         |  20 +-
 src/relax/backend/contrib/tensorrt/codegen.cc      |   2 +-
 src/relax/backend/vm/exec_builder.cc               |   1 +
 src/relax/ir/transform.cc                          |   1 -
 src/runtime/const_loader_module.cc                 |  38 +-
 src/runtime/contrib/clml/clml_runtime.cc           |  50 +-
 src/runtime/contrib/coreml/coreml_runtime.mm       |  23 +-
 src/runtime/contrib/cublas/cublas_utils.cc         |  11 +-
 src/runtime/contrib/cudnn/cudnn_json_runtime.cc    |   1 +
 src/runtime/contrib/cudnn/cudnn_utils.cc           |   6 +-
 src/runtime/contrib/hipblas/hipblas_utils.cc       |  11 +-
 src/runtime/contrib/json/json_node.h               | 292 +++++------
 src/runtime/contrib/json/json_runtime.h            |  72 ++-
 src/runtime/contrib/miopen/miopen_utils.cc         |   6 +-
 src/runtime/contrib/mps/mps_utils.h                |   1 -
 src/runtime/contrib/mps/mps_utils.mm               |   7 +-
 src/runtime/contrib/mrvl/mrvl_hw_runtime.cc        |  35 +-
 src/runtime/contrib/mrvl/mrvl_runtime.cc           |  23 +-
 src/runtime/contrib/msc/tensorrt_runtime.cc        |   1 -
 src/runtime/contrib/random/random.cc               |   6 +-
 src/runtime/contrib/rocblas/rocblas.cc             |  12 +-
 src/runtime/contrib/tensorrt/tensorrt_runtime.cc   |  76 +--
 src/runtime/cpu_device_api.cc                      |  10 +-
 src/runtime/cuda/cuda_device_api.cc                |   8 +-
 src/runtime/cuda/cuda_module.cc                    |  25 +-
 src/runtime/cuda/l2_cache_flush.cc                 |   8 +-
 src/runtime/device_api.cc                          |   1 +
 src/runtime/disco/loader.cc                        |   1 +
 src/runtime/disco/message_queue.h                  |  16 +-
 src/runtime/disco/protocol.h                       |  11 +-
 src/runtime/disco/threaded_session.cc              |  12 +-
 src/runtime/file_utils.cc                          | 138 +++---
 src/runtime/file_utils.h                           |   6 +-
 src/runtime/hexagon/hexagon_device_api.cc          |  10 +-
 src/runtime/hexagon/hexagon_module.cc              |  16 +-
 src/runtime/logging.cc                             |   4 +-
 src/runtime/meta_data.h                            |  27 +-
 src/runtime/metal/metal_device_api.mm              |   8 +-
 src/runtime/metal/metal_module.mm                  |  42 +-
 src/runtime/opencl/opencl_device_api.cc            |  12 +-
 src/runtime/opencl/opencl_module.cc                |  48 +-
 src/runtime/opencl/opencl_module_spirv.cc          |  14 +-
 .../opencl/opencl_wrapper/opencl_wrapper.cc        |   1 -
 src/runtime/pack_args.h                            |   1 +
 src/runtime/profiling.cc                           |  90 ++--
 src/runtime/rocm/rocm_device_api.cc                |   8 +-
 src/runtime/rocm/rocm_module.cc                    |  25 +-
 src/runtime/rpc/rpc_endpoint.cc                    |  30 +-
 src/runtime/rpc/rpc_pipe_impl.cc                   |   6 +-
 src/runtime/rpc/rpc_socket_impl.cc                 |  14 +-
 src/runtime/spirv/spirv_shader.h                   |  24 +-
 src/runtime/static_library.cc                      |  22 +-
 src/runtime/thread_pool.cc                         |  11 +-
 src/runtime/thread_storage_scope.h                 |   1 +
 src/runtime/vm/executable.cc                       |  37 +-
 src/runtime/vm/tensor_cache_support.cc             |   2 +-
 src/runtime/vulkan/vulkan_device.cc                |   4 +-
 src/runtime/vulkan/vulkan_module.cc                |  19 +-
 src/runtime/vulkan/vulkan_wrapped_func.cc          |  27 +-
 .../analysis/sblock_access_region_detector.cc      |   1 +
 src/s_tir/meta_schedule/mutator/mutate_parallel.cc |   1 +
 .../postproc/disallow_async_strided_mem_copy.cc    |   2 +-
 .../meta_schedule/postproc/verify_gpu_code.cc      |   4 +-
 .../search_strategy/evolutionary_search.cc         |   2 +
 src/s_tir/meta_schedule/utils.h                    |  16 +-
 src/s_tir/schedule/primitive/hide_buffer_access.cc |   2 +
 .../schedule/primitive/reorder_block_iter_var.cc   |   2 +
 src/s_tir/transform/inject_software_pipeline.cc    |   1 +
 src/s_tir/transform/lower_async_dma.cc             |   1 +
 .../transform/merge_shared_memory_allocations.cc   |   2 +
 .../transform/using_assume_to_reduce_branches.cc   |   2 +
 .../printer/doc_printer/python_doc_printer.cc      |   1 +
 src/support/base64.h                               |  21 +-
 src/support/bytes_io.h                             | 103 ++++
 src/support/env.h                                  |  63 +++
 src/support/libinfo.cc                             |   1 -
 src/support/pipe.h                                 |   4 +-
 src/support/socket.h                               |   4 +-
 src/target/codegen.cc                              |  31 +-
 src/target/llvm/codegen_hexagon.cc                 |   2 +-
 src/target/llvm/llvm_instance.cc                   |   3 +-
 src/target/llvm/llvm_module.cc                     |   2 +-
 src/target/source/codegen_webgpu.cc                |  25 +-
 src/target/source/source_module.cc                 |  42 +-
 src/target/spirv/spirv_utils.cc                    |   4 +-
 src/target/target.cc                               |  12 +-
 src/tir/ir/buffer.cc                               |   1 +
 src/tir/transform/storage_rewrite.cc               |   1 +
 src/topi/einsum.cc                                 |   3 +
 tests/lint/cpplint.sh                              |  11 +-
 tests/lint/jnilint.sh                              |   3 +-
 .../python/relax/test_frontend_nn_extern_module.py |   2 -
 web/Makefile                                       |   2 +-
 web/emcc/tvmjs_support.cc                          |   1 -
 web/emcc/wasm_runtime.cc                           |   1 -
 web/emcc/webgpu_runtime.cc                         |  32 +-
 170 files changed, 2520 insertions(+), 1586 deletions(-)

diff --git a/3rdparty/nvbench/l2_cache_flush.h b/3rdparty/nvbench/l2_cache_flush.h
index 3d02115645..6c6ccc7938 100644
--- a/3rdparty/nvbench/l2_cache_flush.h
+++ b/3rdparty/nvbench/l2_cache_flush.h
@@ -22,7 +22,7 @@
 
 #include <cuda.h>
 #include <cuda_runtime.h>
-#include <dmlc/logging.h>
+#include <tvm/runtime/logging.h>
 
 namespace tvm {
 namespace runtime {
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d07ed962e4..4143774c63 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -73,7 +73,6 @@ tvm_option(USE_ALTERNATIVE_LINKER "Use 'mold' or 'lld' if found when invoking co
 tvm_option(USE_CCACHE "Use ccache if found when invoking compiler" AUTO)
 
 # 3rdparty libraries
-tvm_option(DMLC_PATH "Path to DMLC" "3rdparty/dmlc-core/include")
 tvm_option(RANG_PATH "Path to RANG" "3rdparty/rang/include")
 tvm_option(COMPILER_RT_PATH "Path to COMPILER-RT" "3rdparty/compiler-rt")
 # Contrib library options
@@ -119,7 +118,6 @@ tvm_option(TVM_BUILD_PYTHON_MODULE "Build Python module with scikit-build-core"
 # include directories
 include_directories(${CMAKE_INCLUDE_PATH})
 include_directories("include")
-include_directories(SYSTEM ${DMLC_PATH})
 include_directories(SYSTEM ${RANG_PATH})
 include_directories(SYSTEM ${COMPILER_RT_PATH})
 
@@ -140,7 +138,7 @@ endif()
 if(TVM_LOG_BEFORE_THROW)
   # log error before throw as
   # when system have issues with stack trace
-  add_definitions(-DDMLC_LOG_BEFORE_THROW=1)
+  add_definitions(-DTVM_LOG_BEFORE_THROW=1)
 endif()
 
 # Generic compilation options
@@ -335,7 +333,6 @@ if(BUILD_FOR_HEXAGON)
   endif()
 
   add_definitions(-D_MACH_I32=int)
-  add_definitions(-DDMLC_CXX11_THREAD_LOCAL=0)
 endif()
 
 # distributed disco runtime are disabled for hexagon
@@ -346,7 +343,6 @@ endif()
 
 # Package runtime rules
 if(NOT USE_RTTI)
-  add_definitions(-DDMLC_ENABLE_RTTI=0)
 endif()
 
 if (INDEX_DEFAULT_I64)
@@ -534,11 +530,6 @@ endif()
 target_include_directories(tvm_runtime PUBLIC "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
 set_property(TARGET tvm_runtime APPEND PROPERTY LINK_OPTIONS "${TVM_VISIBILITY_FLAG}")
 
-target_compile_definitions(tvm_objs PUBLIC DMLC_USE_LOGGING_LIBRARY=<tvm/runtime/logging.h>)
-target_compile_definitions(tvm_runtime_objs PUBLIC DMLC_USE_LOGGING_LIBRARY=<tvm/runtime/logging.h>)
-target_compile_definitions(tvm_libinfo_objs PUBLIC DMLC_USE_LOGGING_LIBRARY=<tvm/runtime/logging.h>)
-target_compile_definitions(tvm PUBLIC DMLC_USE_LOGGING_LIBRARY=<tvm/runtime/logging.h>)
-target_compile_definitions(tvm_runtime PUBLIC DMLC_USE_LOGGING_LIBRARY=<tvm/runtime/logging.h>)
 
 # logging option for libbacktrace
 include(cmake/modules/Logging.cmake)
@@ -665,11 +656,6 @@ if (INSTALL_DEV)
     FILES_MATCHING
     PATTERN "*.h"
   )
-  install(
-    DIRECTORY "3rdparty/dmlc-core/include/" DESTINATION "include"
-    FILES_MATCHING
-    PATTERN "*.h"
-    )
 else(INSTALL_DEV)
   install(
     DIRECTORY "include/tvm/runtime/" DESTINATION "include/tvm/runtime"
diff --git a/Makefile b/Makefile
index 8ebc284123..b22a3ff2fb 100644
--- a/Makefile
+++ b/Makefile
@@ -36,7 +36,6 @@ TVM_BUILD_PATH := $(abspath $(TVM_BUILD_PATH))
 
 # Allow environment variables for 3rd-party libraries, default to
 # packaged version.
-DMLC_CORE_PATH ?= $(ROOTDIR)/3rdparty/dmlc-core
 DLPACK_PATH ?= $(ROOTDIR)/3rdparty/tvm-ffi/3rdparty/dlpack
 
 all: $(addsuffix /all,$(TVM_BUILD_PATH))
@@ -99,7 +98,7 @@ pylint:
 	tests/lint/pylint.sh
 
 jnilint:
-	python3 3rdparty/dmlc-core/scripts/lint.py tvm4j-jni cpp jvm/native/src
+	python3 -m cpplint jvm/native/src
 
 mypy:
 	tests/scripts/task_mypy.sh
@@ -116,7 +115,7 @@ webclean:
 
 
 # JVM build rules
-INCLUDE_FLAGS = -Iinclude -I$(DLPACK_PATH)/include -I$(DMLC_CORE_PATH)/include
+INCLUDE_FLAGS = -Iinclude -I$(DLPACK_PATH)/include
 PKG_CFLAGS = -Wall -O3 $(INCLUDE_FLAGS) -fPIC
 PKG_LDFLAGS =
 
diff --git a/apps/android_rpc/app/src/main/jni/Android.mk b/apps/android_rpc/app/src/main/jni/Android.mk
index d482f94295..b7540521c2 100644
--- a/apps/android_rpc/app/src/main/jni/Android.mk
+++ b/apps/android_rpc/app/src/main/jni/Android.mk
@@ -39,7 +39,6 @@ LOCAL_LDFLAGS := -L$(SYSROOT)/usr/lib/ -llog
 LOCAL_C_INCLUDES := $(ROOT_PATH)/include \
                     $(ROOT_PATH)/3rdparty/tvm-ffi/include \
                     $(ROOT_PATH)/3rdparty/tvm-ffi/3rdparty/dlpack/include \
-                    $(ROOT_PATH)/3rdparty/dmlc-core/include \
                     $(ROOT_PATH)/3rdparty/OpenCL-Headers
 
 LOCAL_MODULE = tvm4j_runtime_packed
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 a522f0e996..035d8b980e 100644
--- a/apps/android_rpc/app/src/main/jni/tvm_runtime.h
+++ b/apps/android_rpc/app/src/main/jni/tvm_runtime.h
@@ -25,7 +25,6 @@
 
 #include <fstream>
 
-#define DMLC_USE_LOGGING_LIBRARY <tvm/runtime/logging.h>
 #define TVM_USE_LIBBACKTRACE 0
 /* Enable custom logging - this will cause TVM to use a custom implementation
  * of tvm::runtime::detail::LogMessage. We use this to pass TVM log messages to
diff --git a/apps/cpp_rpc/CMakeLists.txt b/apps/cpp_rpc/CMakeLists.txt
index 6d58308c9c..f3ce31d759 100644
--- a/apps/cpp_rpc/CMakeLists.txt
+++ b/apps/cpp_rpc/CMakeLists.txt
@@ -45,7 +45,6 @@ endif()
 target_include_directories(
   tvm_rpc
   PUBLIC "../../include"
-  PUBLIC DMLC_PATH
 )
 
 target_link_libraries(tvm_rpc PUBLIC tvm_ffi_header)
diff --git a/apps/cpp_rpc/main.cc b/apps/cpp_rpc/main.cc
index 17b79141b1..e9a2262811 100644
--- a/apps/cpp_rpc/main.cc
+++ b/apps/cpp_rpc/main.cc
@@ -177,8 +177,6 @@ void ParseCmdArgs(int argc, char* argv[], struct RpcServerArgs& args) {
   const string silent = GetCmdOption(argc, argv, "--silent", true);
   if (!silent.empty()) {
     args.silent = true;
-    // Only errors and fatal is logged
-    dmlc::InitLogging("--minloglevel=2");
   }
 
   const string host = GetCmdOption(argc, argv, "--host=");
@@ -239,7 +237,6 @@ void ParseCmdArgs(int argc, char* argv[], struct RpcServerArgs& args) {
   const string mmap_path = GetCmdOption(argc, argv, "--child_proc=");
   if (!mmap_path.empty()) {
     args.mmap_path = mmap_path;
-    dmlc::InitLogging("--minloglevel=0");
   }
 #endif
   const string work_dir = GetCmdOption(argc, argv, "--work-dir=");
diff --git a/apps/cpp_rpc/rpc_env.cc b/apps/cpp_rpc/rpc_env.cc
index c4a43dc9f3..0ff063da41 100644
--- a/apps/cpp_rpc/rpc_env.cc
+++ b/apps/cpp_rpc/rpc_env.cc
@@ -162,7 +162,7 @@ RPCEnv::RPCEnv(const std::string& wd) {
                              size_t size = static_cast<size_t>(fs.tellg());
                              fs.seekg(0, std::ios::beg);
                              bin.resize(size);
-                             fs.read(dmlc::BeginPtr(bin), size);
+                             fs.read(bin.data(), size);
                              LOG(INFO) << "Send linked module " << file_name << " to client";
                              return ffi::Bytes(bin);
                            }));
diff --git a/apps/cpp_rpc/rpc_server.cc b/apps/cpp_rpc/rpc_server.cc
index fd8fc476bb..8a948c9efa 100644
--- a/apps/cpp_rpc/rpc_server.cc
+++ b/apps/cpp_rpc/rpc_server.cc
@@ -390,8 +390,6 @@ void ServerLoopFromChild(SOCKET socket) {
 void RPCServerCreate(std::string host, int port, int port_end, std::string tracker_addr,
                      std::string key, std::string custom_addr, std::string work_dir, bool silent) {
   if (silent) {
-    // Only errors and fatal is logged
-    dmlc::InitLogging("--minloglevel=2");
   }
   // Start the rpc server
   RPCServer rpc(std::move(host), port, port_end, std::move(tracker_addr), std::move(key),
diff --git a/apps/hexagon_launcher/cmake/HexagonLauncher.cmake b/apps/hexagon_launcher/cmake/HexagonLauncher.cmake
index 31aaa17b85..3d3ce0c771 100644
--- a/apps/hexagon_launcher/cmake/HexagonLauncher.cmake
+++ b/apps/hexagon_launcher/cmake/HexagonLauncher.cmake
@@ -72,5 +72,4 @@ include_directories(
   "${LAUNCHER_SRC}"
   "${TVM_SOURCE_DIR}/include"
   "${TVM_SOURCE_DIR}/3rdparty/dlpack/include"
-  "${TVM_SOURCE_DIR}/3rdparty/dmlc-core/include"
 )
diff --git a/apps/hexagon_launcher/cmake/android/CMakeLists.txt b/apps/hexagon_launcher/cmake/android/CMakeLists.txt
index ea02efd144..4296652e51 100644
--- a/apps/hexagon_launcher/cmake/android/CMakeLists.txt
+++ b/apps/hexagon_launcher/cmake/android/CMakeLists.txt
@@ -52,7 +52,6 @@ include_directories(SYSTEM
 
 link_directories(${DSPRPC_LIB_DIRS})
 
-add_definitions(-DDMLC_USE_LOGGING_LIBRARY=<tvm/runtime/logging.h>)
 
 set(STUB_SRCS
   "${LAUNCHER_SRC}/launcher_android.cc"
diff --git a/apps/hexagon_launcher/cmake/hexagon/CMakeLists.txt b/apps/hexagon_launcher/cmake/hexagon/CMakeLists.txt
index e250f5a3cc..a0557307ba 100644
--- a/apps/hexagon_launcher/cmake/hexagon/CMakeLists.txt
+++ b/apps/hexagon_launcher/cmake/hexagon/CMakeLists.txt
@@ -52,8 +52,6 @@ include_directories(SYSTEM
 link_directories(${QURT_LIB_DIRS})
 
 add_definitions(-D_MACH_I32=int)
-add_definitions(-DDMLC_CXX11_THREAD_LOCAL=0)
-add_definitions(-DDMLC_USE_LOGGING_LIBRARY=<tvm/runtime/logging.h>)
 
 # Extra compile flags (both C and C++).
 set(EXTRA_COMP_FLAGS
diff --git a/apps/hexagon_launcher/launcher_core.cc b/apps/hexagon_launcher/launcher_core.cc
index 3bf6ce23cf..0099f7fc79 100644
--- a/apps/hexagon_launcher/launcher_core.cc
+++ b/apps/hexagon_launcher/launcher_core.cc
@@ -19,9 +19,11 @@
 
 #include "launcher_core.h"
 
+#include <tvm/ffi/extra/json.h>
 #include <tvm/ffi/function.h>
 #include <tvm/runtime/c_backend_api.h>
 
+#include <algorithm>
 #include <fstream>
 #include <ios>
 #include <iterator>
@@ -46,20 +48,25 @@ std::string tensor_meta::to_string() const {
   return out.str();
 }
 
-void TensorConfig::Load(dmlc::JSONReader* reader) {
-  reader->BeginObject();
-  std::string key;
-  while (!bad && reader->NextObjectItem(&key)) {
+void TensorConfig::Load(tvm::ffi::json::Object obj) {
+  namespace json = ::tvm::ffi::json;
+  for (const auto& kv : obj) {
+    std::string key = std::string(kv.first.cast<tvm::ffi::String>());
     if (key == file_key) {
-      reader->Read(&file_name);
+      file_name = std::string(kv.second.cast<tvm::ffi::String>());
     } else if (key == shape_key) {
-      reader->Read(&shape);
+      auto arr = kv.second.cast<json::Array>();
+      shape.clear();
+      shape.reserve(arr.size());
+      for (const auto& elem : arr) {
+        shape.push_back(static_cast<int>(elem.cast<int64_t>()));
+      }
       if (shape.empty()) {
         std::cout << "error: empty shape\n";
         bad = true;
       }
     } else if (key == dtype_key) {
-      reader->Read(&dtype);
+      dtype = std::string(kv.second.cast<tvm::ffi::String>());
     } else {
       std::cout << "unknown tensor config key: " << key << '\n';
       bad = true;
@@ -67,24 +74,36 @@ void TensorConfig::Load(dmlc::JSONReader* reader) {
   }
 }
 
-void TensorConfig::Save(dmlc::JSONWriter* writer) const {
-  writer->BeginObject(true);
-  writer->WriteObjectKeyValue(file_key, file_name);
-  writer->WriteObjectKeyValue(shape_key, shape);
-  writer->WriteObjectKeyValue(dtype_key, dtype);
-  writer->EndObject();
+tvm::ffi::json::Value TensorConfig::SaveToJSON() const {
+  namespace json = ::tvm::ffi::json;
+  json::Object obj;
+  obj.Set(tvm::ffi::String(file_key), tvm::ffi::String(file_name));
+  json::Array shape_arr;
+  for (int v : shape) {
+    shape_arr.push_back(static_cast<int64_t>(v));
+  }
+  obj.Set(tvm::ffi::String(shape_key), std::move(shape_arr));
+  obj.Set(tvm::ffi::String(dtype_key), tvm::ffi::String(dtype));
+  return obj;
 }
 
-void ModelConfig::Load(dmlc::JSONReader* reader) {
-  reader->BeginObject();
-  std::string key;
-  while (!bad && reader->NextObjectItem(&key)) {
+void ModelConfig::Load(tvm::ffi::json::Object obj) {
+  namespace json = ::tvm::ffi::json;
+  for (const auto& kv : obj) {
+    std::string key = std::string(kv.first.cast<tvm::ffi::String>());
     if (key == "model-library") {
-      reader->Read(&model_library);
+      model_library = std::string(kv.second.cast<tvm::ffi::String>());
     } else if (key == "model-json") {
-      reader->Read(&model_json);
+      model_json = std::string(kv.second.cast<tvm::ffi::String>());
     } else if (key == "inputs") {
-      reader->Read(&inputs);
+      auto arr = kv.second.cast<json::Array>();
+      inputs.clear();
+      inputs.reserve(arr.size());
+      for (const auto& elem : arr) {
+        TensorConfig tc;
+        tc.Load(elem.cast<json::Object>());
+        inputs.push_back(std::move(tc));
+      }
       bad = std::any_of(inputs.begin(), inputs.end(), [](auto t) { return t.bad; });
     } else {
       std::cout << "unknown model config key: " << key << '\n';
@@ -93,15 +112,21 @@ void ModelConfig::Load(dmlc::JSONReader* reader) {
   }
 }
 
-void OutputConfig::Save(dmlc::JSONWriter* writer) const {
-  writer->BeginObject(true);
-  writer->WriteObjectKeyValue("pcycles", pcycles);
-  writer->WriteObjectKeyValue("usecs", usecs);
-  writer->WriteObjectKeyValue("outputs", outputs);
-  writer->EndObject();
+tvm::ffi::json::Value OutputConfig::SaveToJSON() const {
+  namespace json = ::tvm::ffi::json;
+  json::Object obj;
+  obj.Set(tvm::ffi::String("pcycles"), static_cast<int64_t>(pcycles));
+  obj.Set(tvm::ffi::String("usecs"), static_cast<int64_t>(usecs));
+  json::Array outputs_arr;
+  for (const auto& tc : outputs) {
+    outputs_arr.push_back(tc.SaveToJSON());
+  }
+  obj.Set(tvm::ffi::String("outputs"), std::move(outputs_arr));
+  return obj;
 }
 
 bool read_model_config(const std::string& file_name, ModelConfig* model_config) {
+  namespace json = ::tvm::ffi::json;
   if (model_config == nullptr) {
     return false;
   }
@@ -109,21 +134,22 @@ bool read_model_config(const std::string& file_name, ModelConfig* model_config)
   if (!mfc.is_open()) {
     return false;
   }
-  dmlc::JSONReader reader(&mfc);
-  model_config->Load(&reader);
-  if (model_config->bad || !mfc) {
+  std::string content((std::istreambuf_iterator<char>(mfc)), std::istreambuf_iterator<char>());
+  auto parsed = json::Parse(content);
+  model_config->Load(parsed.cast<json::Object>());
+  if (model_config->bad) {
     return false;
   }
   return true;
 }
 
 bool write_output_config(const std::string& file_name, OutputConfig* output_config) {
+  namespace json = ::tvm::ffi::json;
   std::ofstream ofc(file_name);
   if (!ofc.is_open()) {
     return false;
   }
-  dmlc::JSONWriter writer(&ofc);
-  output_config->Save(&writer);
+  ofc << std::string(json::Stringify(output_config->SaveToJSON(), 2));
   if (!ofc) {
     return false;
   }
diff --git a/apps/hexagon_launcher/launcher_core.h b/apps/hexagon_launcher/launcher_core.h
index be5a4ee94d..b8c3c6f56b 100644
--- a/apps/hexagon_launcher/launcher_core.h
+++ b/apps/hexagon_launcher/launcher_core.h
@@ -21,7 +21,7 @@
 #define TVM_RUNTIME_HEXAGON_LAUNCHER_LAUNCHER_CORE_H_
 
 #include <dlpack/dlpack.h>
-#include <dmlc/json.h>
+#include <tvm/ffi/extra/json.h>
 #include <tvm/ffi/function.h>
 #include <tvm/runtime/data_type.h>
 #include <tvm/runtime/module.h>
@@ -59,8 +59,8 @@ struct TensorConfig {
   std::string dtype;
   bool bad = false;
 
-  void Load(dmlc::JSONReader* reader);
-  void Save(dmlc::JSONWriter* writer) const;
+  void Load(tvm::ffi::json::Object obj);
+  tvm::ffi::json::Value SaveToJSON() const;
 };
 
 struct ModelConfig {
@@ -69,7 +69,7 @@ struct ModelConfig {
   std::vector<TensorConfig> inputs;
   bool bad = false;
 
-  void Load(dmlc::JSONReader* reader);
+  void Load(tvm::ffi::json::Object obj);
 };
 
 struct OutputConfig {
@@ -77,7 +77,7 @@ struct OutputConfig {
   uint64_t usecs;
   std::vector<TensorConfig> outputs;
 
-  void Save(dmlc::JSONWriter* writer) const;
+  tvm::ffi::json::Value SaveToJSON() const;
 };
 
 struct Model {
diff --git a/apps/ios_rpc/tvmrpc.xcodeproj/project.pbxproj b/apps/ios_rpc/tvmrpc.xcodeproj/project.pbxproj
index ccc61707d3..c2948edcf1 100644
--- a/apps/ios_rpc/tvmrpc.xcodeproj/project.pbxproj
+++ b/apps/ios_rpc/tvmrpc.xcodeproj/project.pbxproj
@@ -284,7 +284,6 @@
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"DEBUG=1",
 					"$(inherited)",
-					"DMLC_USE_LOGGING_LIBRARY=<tvm/runtime/logging.h>",
 					"TVM_USE_LIBBACKTRACE=0",
 					"TVM_LOG_CUSTOMIZE=1",
 				);
@@ -333,7 +332,6 @@
 				GCC_C_LANGUAGE_STANDARD = gnu99;
 				GCC_NO_COMMON_BLOCKS = YES;
 				GCC_PREPROCESSOR_DEFINITIONS = (
-					"DMLC_USE_LOGGING_LIBRARY=<tvm/runtime/logging.h>",
 					"TVM_USE_LIBBACKTRACE=0",
 					"TVM_LOG_CUSTOMIZE=1",
 				);
@@ -366,7 +364,6 @@
 				HEADER_SEARCH_PATHS = (
 					../../include,
 					../../3rdparty/dlpack/include,
-					"../../3rdparty/dmlc-core/include",
 					"${TVM_BUILD_DIR}/apps/ios_rpc/custom_dso_loader/include",
 				);
 				INFOPLIST_FILE = tvmrpc/Info.plist;
@@ -401,7 +398,6 @@
 				HEADER_SEARCH_PATHS = (
 					../../include,
 					../../3rdparty/dlpack/include,
-					"../../3rdparty/dmlc-core/include",
 					"${TVM_BUILD_DIR}/apps/ios_rpc/custom_dso_loader/include",
 				);
 				INFOPLIST_FILE = tvmrpc/Info.plist;
diff --git a/cmake/modules/CUDA.cmake b/cmake/modules/CUDA.cmake
index e9f5854901..3cd07a316f 100644
--- a/cmake/modules/CUDA.cmake
+++ b/cmake/modules/CUDA.cmake
@@ -109,7 +109,6 @@ if(USE_CUDA)
     add_library(tvm_thrust_objs OBJECT ${CONTRIB_THRUST_SRC})
     target_link_libraries(tvm_thrust_objs PRIVATE tvm_ffi_header)
     target_compile_options(tvm_thrust_objs PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:--expt-extended-lambda>)
-    target_compile_definitions(tvm_thrust_objs PUBLIC DMLC_USE_LOGGING_LIBRARY=<tvm/runtime/logging.h>)
     if (NOT USE_THRUST MATCHES ${IS_TRUE_PATTERN})
       find_package(CCCL REQUIRED COMPONENTS Thrust)
       target_link_libraries(tvm_thrust_objs PRIVATE CCCL::Thrust)
diff --git a/cmake/modules/LLVM.cmake b/cmake/modules/LLVM.cmake
index f695149975..fa1cbf6d1a 100644
--- a/cmake/modules/LLVM.cmake
+++ b/cmake/modules/LLVM.cmake
@@ -21,7 +21,7 @@
 #
 # See https://github.com/imageworks/OpenShadingLanguage/issues/1069
 # for more discussion.
-add_definitions(-DDMLC_USE_FOPEN64=0 -DNDEBUG=1)
+add_definitions(-DNDEBUG=1)
 # TODO(@jroesch, @tkonolige): if we actually use targets we can do this.
 # target_compile_definitions(tvm PRIVATE NDEBUG=1)
 
@@ -46,8 +46,6 @@ if(NOT ${USE_LLVM} MATCHES ${IS_FALSE_PATTERN})
   list(APPEND TVM_LINKER_LIBS ${LLVM_LIBS})
   list(APPEND COMPILER_SRCS ${COMPILER_LLVM_SRCS})
   if(NOT MSVC)
-    set_source_files_properties(${COMPILER_LLVM_SRCS}
-      PROPERTIES COMPILE_DEFINITIONS "DMLC_ENABLE_RTTI=0")
     set_source_files_properties(${COMPILER_LLVM_SRCS}
       PROPERTIES COMPILE_FLAGS "-fno-rtti")
   endif()
diff --git a/cmake/modules/LibInfo.cmake b/cmake/modules/LibInfo.cmake
index 5506184133..59a6e6e271 100644
--- a/cmake/modules/LibInfo.cmake
+++ b/cmake/modules/LibInfo.cmake
@@ -44,7 +44,6 @@ function(add_lib_info src_file)
     TVM_INFO_BUILD_DUMMY_LIBTVM="${BUILD_DUMMY_LIBTVM}"
     TVM_INFO_COMPILER_RT_PATH="${COMPILER_RT_PATH}"
     TVM_INFO_CUDA_VERSION="${TVM_INFO_CUDA_VERSION}"
-    TVM_INFO_DMLC_PATH="${DMLC_PATH}"
     TVM_INFO_GIT_COMMIT_HASH="${TVM_GIT_COMMIT_HASH}"
     TVM_INFO_GIT_COMMIT_TIME="${TVM_GIT_COMMIT_TIME}"
     TVM_INFO_HIDE_PRIVATE_SYMBOLS="${HIDE_PRIVATE_SYMBOLS}"
diff --git a/cmake/modules/contrib/CUTLASS.cmake b/cmake/modules/contrib/CUTLASS.cmake
index b74ce4c8df..a3a09f141c 100644
--- a/cmake/modules/contrib/CUTLASS.cmake
+++ b/cmake/modules/contrib/CUTLASS.cmake
@@ -39,7 +39,6 @@ if(USE_CUDA AND USE_CUTLASS)
   list(APPEND CUTLASS_FPA_INTB_RUNTIME_SRCS src/runtime/contrib/cutlass/weight_preprocess.cc)
   add_library(fpA_intB_cutlass_objs OBJECT ${CUTLASS_FPA_INTB_RUNTIME_SRCS})
   target_link_libraries(fpA_intB_cutlass_objs PRIVATE tvm_ffi_header)
-  target_compile_definitions(fpA_intB_cutlass_objs PRIVATE DMLC_USE_LOGGING_LIBRARY=<tvm/runtime/logging.h>)
   target_include_directories(fpA_intB_cutlass_objs PRIVATE
     ${PROJECT_SOURCE_DIR}/3rdparty/cutlass_fpA_intB_gemm
     ${PROJECT_SOURCE_DIR}/3rdparty/cutlass_fpA_intB_gemm/cutlass/include
@@ -76,7 +75,6 @@ if(USE_CUDA AND USE_CUTLASS)
       ${PROJECT_SOURCE_DIR}/3rdparty/cutlass_fpA_intB_gemm/cutlass_extensions/include
     )
     target_link_libraries(tvm_cutlass_objs PRIVATE tvm_ffi_header)
-    target_compile_definitions(tvm_cutlass_objs PRIVATE DMLC_USE_LOGGING_LIBRARY=<tvm/runtime/logging.h>)
     # Note: enable this to get more detailed logs for cutlass kernels
     # target_compile_definitions(tvm_cutlass_objs PRIVATE CUTLASS_DEBUG_TRACE_LEVEL=2)
     list(APPEND CUTLASS_RUNTIME_OBJS "$<${CUTLASS_GEN_COND}:$<TARGET_OBJECTS:tvm_cutlass_objs>>")
diff --git a/cmake/modules/contrib/MSCCLPP.cmake b/cmake/modules/contrib/MSCCLPP.cmake
index b12a5c748b..b63958b69d 100644
--- a/cmake/modules/contrib/MSCCLPP.cmake
+++ b/cmake/modules/contrib/MSCCLPP.cmake
@@ -34,7 +34,6 @@ if(USE_CUDA AND USE_NCCL AND USE_MSCCL)
 
   add_library(msccl SHARED ${MSCCL_SRCS})
   target_link_libraries(msccl PUBLIC mscclpp)
-  target_compile_definitions(msccl PRIVATE DMLC_USE_LOGGING_LIBRARY=<tvm/runtime/logging.h>)
   target_include_directories(msccl PUBLIC
     $<BUILD_INTERFACE:${mscclpp_SOURCE_DIR}/include>
     $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/3rdparty/mscclpp/include>
diff --git a/docs/Doxyfile b/docs/Doxyfile
index d1ea82c842..42d9ea3d3f 100644
--- a/docs/Doxyfile
+++ b/docs/Doxyfile
@@ -1978,7 +1978,7 @@ INCLUDE_FILE_PATTERNS  =
 # recursively expanded use the := operator instead of the = operator.
 # This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
 
-PREDEFINED             = DMLC_USE_CXX11 TVM_DLL= __attribute__(x)=
+PREDEFINED             = TVM_DLL= __attribute__(x)=
 
 # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
 # tag can be used to specify a list of macro names that should be expanded. The
diff --git a/include/tvm/ir/attrs.h b/include/tvm/ir/attrs.h
index e68261602a..29371d2069 100644
--- a/include/tvm/ir/attrs.h
+++ b/include/tvm/ir/attrs.h
@@ -28,7 +28,6 @@
 #ifndef TVM_IR_ATTRS_H_
 #define TVM_IR_ATTRS_H_
 
-#include <dmlc/common.h>
 #include <tvm/ffi/container/map.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/accessor.h>
diff --git a/include/tvm/ir/op.h b/include/tvm/ir/op.h
index 211fc3eecc..2153ce4190 100644
--- a/include/tvm/ir/op.h
+++ b/include/tvm/ir/op.h
@@ -293,7 +293,7 @@ class OpAttrMap : public AttrRegistryMap<Op, ValueType> {
 };
 
 // internal macros to make
-#define TVM_OP_REGISTER_VAR_DEF static DMLC_ATTRIBUTE_UNUSED ::tvm::OpRegEntry& __make_##Op
+#define TVM_OP_REGISTER_VAR_DEF static TVM_ATTRIBUTE_UNUSED ::tvm::OpRegEntry& __make_##Op
 
 /*!
  * \def TVM_REGISTER_OP
diff --git a/include/tvm/runtime/disco/session.h b/include/tvm/runtime/disco/session.h
index 283d75740c..756312777c 100644
--- a/include/tvm/runtime/disco/session.h
+++ b/include/tvm/runtime/disco/session.h
@@ -34,6 +34,7 @@
  * workers' register files could be considered as "identical" (single program) although the values
  * may differ (multiple data).
  *
+ *
  * **DRef.** Following the design above, consider the program in SPMD in a virtual ISA, then each
  * worker is a virtual machine instance to execute the ISA maintaining its own register file.
  * The controler denotes each of their register files with a unique integer "register id",
@@ -77,6 +78,7 @@
 #include <tvm/runtime/object.h>
 #include <tvm/runtime/tensor.h>
 
+#include <mutex>
 #include <queue>
 #include <string>
 #include <utility>
diff --git a/include/tvm/runtime/logging.h b/include/tvm/runtime/logging.h
index b3c5a056f9..6492a20fa3 100644
--- a/include/tvm/runtime/logging.h
+++ b/include/tvm/runtime/logging.h
@@ -29,8 +29,6 @@
 #ifndef TVM_RUNTIME_LOGGING_H_
 #define TVM_RUNTIME_LOGGING_H_
 
-#include <dmlc/common.h>
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/error.h>
 #include <tvm/runtime/base.h>
 
@@ -333,11 +331,8 @@ class LogFatal {
       this->file_ = file;
       this->lineno_ = lineno;
     }
-    [[noreturn]] TVM_NO_INLINE dmlc::Error Finalize() TVM_THROW_EXCEPTION {
+    [[noreturn]] TVM_NO_INLINE Error Finalize() TVM_THROW_EXCEPTION {
       InternalError error(file_, lineno_, stream_.str());
-#if DMLC_LOG_BEFORE_THROW
-      std::cerr << error.what() << std::endl;
-#endif
       throw error;
     }
     std::ostringstream stream_;
@@ -488,8 +483,11 @@ class VLogContext {
   std::vector<std::stringstream*> context_stack_;
 };
 
-/*! \brief Thread local \p VLogContext for tracking a stack of VLOG context messages. */
-using ThreadLocalVLogContext = dmlc::ThreadLocalStore<VLogContext>;
+/*! \brief Get thread local \p VLogContext for tracking a stack of VLOG context messages. */
+inline VLogContext* ThreadLocalVLogContext() {
+  static thread_local VLogContext inst;
+  return &inst;
+}
 
 /*!
  * \brief A RAII class to push/pos a VLOG context message onto the thread-local stack.
@@ -498,8 +496,8 @@ using ThreadLocalVLogContext = dmlc::ThreadLocalStore<VLogContext>;
  */
 class VLogContextEntry {
  public:
-  VLogContextEntry() { ThreadLocalVLogContext::Get()->Push(&sstream_); }
-  ~VLogContextEntry() { ThreadLocalVLogContext::Get()->Pop(); }
+  VLogContextEntry() { ThreadLocalVLogContext()->Push(&sstream_); }
+  ~VLogContextEntry() { ThreadLocalVLogContext()->Pop(); }
   std::ostream& stream() { return sstream_; }
 
  private:
@@ -633,7 +631,7 @@ TVM_CHECK_FUNC(_NE, !=)
  */
 #define VLOG(level)                                                               \
   DLOG_IF(INFO, ::tvm::runtime::detail::VerboseLoggingEnabled(__FILE__, (level))) \
-      << ::tvm::runtime::detail::ThreadLocalVLogContext::Get()->str()
+      << ::tvm::runtime::detail::ThreadLocalVLogContext()->str()
 
 #if TVM_LOG_DEBUG
 #define DCHECK(x) CHECK(x)
diff --git a/include/tvm/runtime/module.h b/include/tvm/runtime/module.h
index 1e0e703944..51fe315903 100644
--- a/include/tvm/runtime/module.h
+++ b/include/tvm/runtime/module.h
@@ -26,13 +26,13 @@
 #ifndef TVM_RUNTIME_MODULE_H_
 #define TVM_RUNTIME_MODULE_H_
 
-#include <dmlc/io.h>
 #include <tvm/ffi/cast.h>
 #include <tvm/ffi/extra/module.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/memory.h>
 #include <tvm/ffi/string.h>
 #include <tvm/runtime/base.h>
+#include <tvm/runtime/logging.h>
 #include <tvm/runtime/object.h>
 
 #include <utility>
diff --git a/include/tvm/runtime/serializer.h b/include/tvm/runtime/serializer.h
index c8e9d3c435..c18b283166 100644
--- a/include/tvm/runtime/serializer.h
+++ b/include/tvm/runtime/serializer.h
@@ -19,51 +19,16 @@
 
 /*!
  * \file tvm/runtime/serializer.h
- * \brief Serializer extension to support TVM data types
- *  Include this file to enable serialization of DLDataType, DLDevice
+ * \brief Serializer extension to support TVM data types.
+ *
+ * This header now redirects to tvm/support/io.h which includes
+ * tvm/support/serializer.h providing Serializer<DLDataType> and
+ * Serializer<DLDevice>.
  */
 #ifndef TVM_RUNTIME_SERIALIZER_H_
 #define TVM_RUNTIME_SERIALIZER_H_
 
-#include <dmlc/io.h>
-#include <dmlc/serializer.h>
-#include <tvm/runtime/base.h>
-#include <tvm/runtime/tensor.h>
-
-namespace dmlc {
-namespace serializer {
-
-template <>
-struct Handler<DLDataType> {
-  inline static void Write(Stream* strm, const DLDataType& dtype) {
-    Handler<uint8_t>::Write(strm, dtype.code);
-    Handler<uint8_t>::Write(strm, dtype.bits);
-    Handler<uint16_t>::Write(strm, dtype.lanes);
-  }
-  inline static bool Read(Stream* strm, DLDataType* dtype) {
-    if (!Handler<uint8_t>::Read(strm, &(dtype->code))) return false;
-    if (!Handler<uint8_t>::Read(strm, &(dtype->bits))) return false;
-    if (!Handler<uint16_t>::Read(strm, &(dtype->lanes))) return false;
-    return true;
-  }
-};
-
-template <>
-struct Handler<DLDevice> {
-  inline static void Write(Stream* strm, const DLDevice& dev) {
-    int32_t device_type = static_cast<int32_t>(dev.device_type);
-    Handler<int32_t>::Write(strm, device_type);
-    Handler<int32_t>::Write(strm, dev.device_id);
-  }
-  inline static bool Read(Stream* strm, DLDevice* dev) {
-    int32_t device_type = 0;
-    if (!Handler<int32_t>::Read(strm, &(device_type))) return false;
-    dev->device_type = static_cast<DLDeviceType>(device_type);
-    if (!Handler<int32_t>::Read(strm, &(dev->device_id))) return false;
-    return true;
-  }
-};
+#include <tvm/support/io.h>
+#include <tvm/support/serializer.h>
 
-}  // namespace serializer
-}  // namespace dmlc
 #endif  // TVM_RUNTIME_SERIALIZER_H_
diff --git a/include/tvm/runtime/tensor.h b/include/tvm/runtime/tensor.h
index 615cfd8ccc..d3d3f367cc 100644
--- a/include/tvm/runtime/tensor.h
+++ b/include/tvm/runtime/tensor.h
@@ -32,7 +32,8 @@
 #include <tvm/runtime/data_type.h>
 #include <tvm/runtime/device_api.h>
 #include <tvm/runtime/object.h>
-#include <tvm/runtime/serializer.h>
+#include <tvm/support/io.h>
+#include <tvm/support/serializer.h>
 
 #include <atomic>
 #include <functional>
@@ -121,12 +122,12 @@ class Tensor : public tvm::ffi::Tensor {
    * \param stream The input data stream
    * \return Whether load is successful
    */
-  inline bool Load(dmlc::Stream* stream);
+  inline bool Load(support::Stream* stream);
   /*!
    * \brief Save Tensor to stream
    * \param stream The output data stream
    */
-  inline void Save(dmlc::Stream* stream) const;
+  inline void Save(support::Stream* stream) const;
 
   /*!
    * \brief Create a Tensor that shares the data memory with the current one.
@@ -195,7 +196,7 @@ class Tensor : public tvm::ffi::Tensor {
  * \param strm The output stream
  * \param tensor The tensor to be saved.
  */
-inline bool SaveDLTensor(dmlc::Stream* strm, const DLTensor* tensor);
+inline bool SaveDLTensor(support::Stream* strm, const DLTensor* tensor);
 
 inline void Tensor::CopyFrom(const DLTensor* other) {
   ICHECK(data_ != nullptr);
@@ -222,7 +223,7 @@ inline void Tensor::CopyTo(const Tensor& other) const {
 /*! \brief Magic number for Tensor file */
 constexpr uint64_t kTVMTensorMagic = 0xDD5E40F096B4A13F;
 
-inline bool SaveDLTensor(dmlc::Stream* strm, const DLTensor* tensor) {
+inline bool SaveDLTensor(support::Stream* strm, const DLTensor* tensor) {
   uint64_t header = kTVMTensorMagic, reserved = 0;
   strm->Write(header);
   strm->Write(reserved);
@@ -251,24 +252,24 @@ inline bool SaveDLTensor(dmlc::Stream* strm, const DLTensor* tensor) {
   int64_t data_byte_size = type_bytes * num_elems;
   strm->Write(data_byte_size);
 
-  if (DMLC_IO_NO_ENDIAN_SWAP && tensor->device.device_type == kDLCPU &&
+  if (TVM_FFI_IO_NO_ENDIAN_SWAP && tensor->device.device_type == kDLCPU &&
       ffi::IsContiguous(*tensor) && tensor->byte_offset == 0) {
     // quick path
     strm->Write(tensor->data, data_byte_size);
   } else {
     std::vector<uint8_t> bytes(data_byte_size);
-    Tensor::CopyToBytes(const_cast<DLTensor*>(tensor), dmlc::BeginPtr(bytes), data_byte_size);
-    if (!DMLC_IO_NO_ENDIAN_SWAP) {
-      dmlc::ByteSwap(dmlc::BeginPtr(bytes), type_bytes, num_elems);
+    Tensor::CopyToBytes(const_cast<DLTensor*>(tensor), bytes.data(), data_byte_size);
+    if (!TVM_FFI_IO_NO_ENDIAN_SWAP) {
+      ffi::ByteSwap(bytes.data(), type_bytes, num_elems);
     }
-    strm->Write(dmlc::BeginPtr(bytes), data_byte_size);
+    strm->Write(bytes.data(), data_byte_size);
   }
   return true;
 }
 
-inline void Tensor::Save(dmlc::Stream* strm) const { SaveDLTensor(strm, operator->()); }
+inline void Tensor::Save(support::Stream* strm) const { SaveDLTensor(strm, operator->()); }
 
-inline bool Tensor::Load(dmlc::Stream* strm) {
+inline bool Tensor::Load(support::Stream* strm) {
   uint64_t header, reserved;
   ICHECK(strm->Read(&header)) << "Invalid DLTensor file format";
   ICHECK(strm->Read(&reserved)) << "Invalid DLTensor file format";
@@ -298,8 +299,8 @@ inline bool Tensor::Load(dmlc::Stream* strm) {
   if (ndim > 0 && shape[0] != 0) {
     ICHECK(read_ret) << "Invalid DLTensor file format";
   }
-  if (!DMLC_IO_NO_ENDIAN_SWAP) {
-    dmlc::ByteSwap(ret->data, elem_bytes, num_elems);
+  if (!TVM_FFI_IO_NO_ENDIAN_SWAP) {
+    ffi::ByteSwap(ret->data, elem_bytes, num_elems);
   }
   *this = ret;
   return true;
diff --git a/include/tvm/runtime/vm/executable.h b/include/tvm/runtime/vm/executable.h
index 7e2380ddf6..5ee47694dd 100644
--- a/include/tvm/runtime/vm/executable.h
+++ b/include/tvm/runtime/vm/executable.h
@@ -26,6 +26,8 @@
 #include <tvm/ffi/function.h>
 #include <tvm/runtime/module.h>
 #include <tvm/runtime/object.h>
+#include <tvm/support/io.h>
+#include <tvm/support/serializer.h>
 
 #include <string>
 #include <unordered_map>
@@ -75,8 +77,8 @@ struct VMFuncInfo {
   std::vector<std::string> param_names;
 
   // defined customized loader save
-  void Save(dmlc::Stream* writer) const;
-  bool Load(dmlc::Stream* reader);
+  void Save(support::Stream* writer) const;
+  bool Load(support::Stream* reader);
 };
 
 /*!
@@ -178,59 +180,66 @@ class VMExecutable : public ffi::ModuleObj {
    * \brief Save the globals.
    * \param strm The input stream.
    */
-  void SaveGlobalSection(dmlc::Stream* strm) const;
+  void SaveGlobalSection(support::Stream* strm) const;
   /*!
    * \brief Save the memory scopes.
    * \param strm The output stream.
    */
-  void SaveMemoryScopeSection(dmlc::Stream* strm) const;
+  void SaveMemoryScopeSection(support::Stream* strm) const;
   /*!
    * \brief Save the constant pool.
    * \param strm The input stream.
    */
-  void SaveConstantSection(dmlc::Stream* strm) const;
+  void SaveConstantSection(support::Stream* strm) const;
   /*!
    * \brief Save the instructions.
    * \param strm The input stream.
    */
-  void SaveCodeSection(dmlc::Stream* strm) const;
+  void SaveCodeSection(support::Stream* strm) const;
   /*!
    * \brief Save the packed functions.
    * \param strm The input stream.
    */
-  void SavePackedFuncNames(dmlc::Stream* strm) const;
+  void SavePackedFuncNames(support::Stream* strm) const;
   /*!
    * \brief Load the globals.
    * \param strm The input stream.
    */
-  void LoadGlobalSection(dmlc::Stream* strm);
+  void LoadGlobalSection(support::Stream* strm);
   /*!
    * \brief Load the memory scopes.
    * \param strm The input stream.
    */
-  void LoadMemoryScopeSection(dmlc::Stream* strm);
+  void LoadMemoryScopeSection(support::Stream* strm);
   /*!
    * \brief Load the constant pool.
    * \param strm The input stream.
    */
-  void LoadConstantSection(dmlc::Stream* strm);
+  void LoadConstantSection(support::Stream* strm);
   /*!
    * \brief Load the instructions.
    * \param strm The input stream.
    */
-  void LoadCodeSection(dmlc::Stream* strm);
+  void LoadCodeSection(support::Stream* strm);
   /*!
    * \brief Save the packed functions.
    * \param strm The input stream.
    */
-  void LoadPackedFuncNames(dmlc::Stream* strm);
+  void LoadPackedFuncNames(support::Stream* strm);
 };
 
 }  // namespace vm
 }  // namespace runtime
 }  // namespace tvm
 
-namespace dmlc {
-DMLC_DECLARE_TRAITS(has_saveload, ::tvm::runtime::vm::VMFuncInfo, true);
-}  // namespace dmlc
+namespace tvm {
+namespace support {
+template <>
+struct Serializer<::tvm::runtime::vm::VMFuncInfo> {
+  static constexpr bool enabled = true;
+  static void Write(Stream* strm, const ::tvm::runtime::vm::VMFuncInfo& data) { data.Save(strm); }
+  static bool Read(Stream* strm, ::tvm::runtime::vm::VMFuncInfo* data) { return data->Load(strm); }
+};
+}  // namespace support
+}  // namespace tvm
 #endif  // TVM_RUNTIME_VM_EXECUTABLE_H_
diff --git a/include/tvm/s_tir/schedule/instruction.h b/include/tvm/s_tir/schedule/instruction.h
index d571b8f286..b422fe9535 100644
--- a/include/tvm/s_tir/schedule/instruction.h
+++ b/include/tvm/s_tir/schedule/instruction.h
@@ -208,7 +208,7 @@ class Instruction : public runtime::ObjectRef {
  * \sa TVM_REGISTER_INST_KIND
  */
 #define TVM_INST_KIND_REGISTER_VAR_DEF \
-  static DMLC_ATTRIBUTE_UNUSED ::tvm::s_tir::InstructionKindRegEntry& __make_##InstructionKind
+  static TVM_ATTRIBUTE_UNUSED ::tvm::s_tir::InstructionKindRegEntry& __make_##InstructionKind
 
 /*!
  * \brief Register an InstructionKind
diff --git a/include/tvm/support/io.h b/include/tvm/support/io.h
new file mode 100644
index 0000000000..8d4c453bdc
--- /dev/null
+++ b/include/tvm/support/io.h
@@ -0,0 +1,136 @@
+/*
+ * 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/support/io.h
+ * \brief Binary stream I/O interface.
+ *
+ * Provides:
+ *   - tvm::support::Stream        (abstract binary stream)
+ *   - tvm::support::Serializer<T> (primary template; specialize for each type)
+ *
+ * Stream::Write<T>() and Stream::Read<T>() delegate to Serializer<T>.
+ * Specializations live in tvm/support/serializer.h.
+ */
+#ifndef TVM_SUPPORT_IO_H_
+#define TVM_SUPPORT_IO_H_
+
+#include <cstddef>
+#include <cstdint>
+
+namespace tvm {
+namespace support {
+
+/*!
+ * \brief Primary Serializer template. Specialize for each serializable type.
+ *
+ * A valid specialization must provide:
+ *   static constexpr bool enabled = true;
+ *   static void Write(Stream* strm, const T& data);
+ *   static bool Read(Stream* strm, T* data);
+ */
+template <typename T, typename = void>
+struct Serializer {
+  static constexpr bool enabled = false;
+};
+
+/*!
+ * \brief Abstract binary stream for serialization.
+ *
+ * Subclasses implement the raw Read/Write byte methods.
+ * The template Write<T>/Read<T> methods delegate to Serializer<T>
+ * for endian-aware, type-safe binary I/O.
+ *
+ * \note Subclasses that override Read(void*, size_t) or Write(const void*, size_t)
+ *       must add `using Stream::Read;` and `using Stream::Write;` to make the
+ *       template overloads visible (C++ name-hiding rule).
+ */
+class Stream {
+ public:
+  /*!
+   * \brief Read raw bytes from the stream.
+   * \param ptr  Destination buffer.
+   * \param size Number of bytes to read.
+   * \return Number of bytes actually read.
+   */
+  virtual size_t Read(void* ptr, size_t size) = 0;
+
+  /*!
+   * \brief Write raw bytes to the stream.
+   * \param ptr  Source buffer.
+   * \param size Number of bytes to write.
+   * \return Number of bytes actually written.
+   */
+  virtual size_t Write(const void* ptr, size_t size) = 0;
+
+  /*! \brief Virtual destructor. */
+  virtual ~Stream() = default;
+
+  /*!
+   * \brief Write a typed value using Serializer<T>.
+   * \tparam T The data type (must have a Serializer<T> specialization).
+   * \param data The value to write.
+   */
+  template <typename T>
+  void Write(const T& data) {
+    static_assert(Serializer<T>::enabled, "No Serializer<T> specialization for this type");
+    Serializer<T>::Write(this, data);
+  }
+
+  /*!
+   * \brief Read a typed value using Serializer<T>.
+   * \tparam T The data type (must have a Serializer<T> specialization).
+   * \param out_data Pointer to receive the value.
+   * \return true on success.
+   */
+  template <typename T>
+  bool Read(T* out_data) {
+    static_assert(Serializer<T>::enabled, "No Serializer<T> specialization for this type");
+    return Serializer<T>::Read(this, out_data);
+  }
+
+  /*!
+   * \brief Write an array of typed values element by element.
+   * \param data Pointer to the first element.
+   * \param num_elems Number of elements.
+   */
+  template <typename T>
+  void WriteArray(const T* data, size_t num_elems) {
+    for (size_t i = 0; i < num_elems; ++i) Write<T>(data[i]);
+  }
+
+  /*!
+   * \brief Read an array of typed values element by element.
+   * \param data Pointer to the first element.
+   * \param num_elems Number of elements.
+   * \return true on success.
+   */
+  template <typename T>
+  bool ReadArray(T* data, size_t num_elems) {
+    for (size_t i = 0; i < num_elems; ++i) {
+      if (!Read<T>(data + i)) return false;
+    }
+    return true;
+  }
+};
+
+}  // namespace support
+}  // namespace tvm
+
+#endif  // TVM_SUPPORT_IO_H_
diff --git a/include/tvm/support/serializer.h b/include/tvm/support/serializer.h
new file mode 100644
index 0000000000..8334f63801
--- /dev/null
+++ b/include/tvm/support/serializer.h
@@ -0,0 +1,232 @@
+/*
+ * 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/support/serializer.h
+ * \brief Serializer<T> specializations for tvm::support::Stream.
+ *
+ * Built-in support for:
+ *   - Arithmetic types (endian-aware)
+ *   - Enum types (via underlying arithmetic type)
+ *   - std::string, std::vector<T>, std::pair<A,B>, std::unordered_map<K,V>
+ *   - DLDataType, DLDevice
+ *
+ * Custom types with Save(Stream*)/Load(Stream*) methods should define
+ * Serializer<Type> specializations directly.
+ */
+#ifndef TVM_SUPPORT_SERIALIZER_H_
+#define TVM_SUPPORT_SERIALIZER_H_
+
+#include <dlpack/dlpack.h>
+#include <tvm/ffi/endian.h>
+#include <tvm/support/io.h>
+
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <type_traits>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+namespace tvm {
+namespace support {
+
+// ---- Arithmetic types ----
+template <typename T>
+struct Serializer<T, std::enable_if_t<std::is_arithmetic_v<T>>> {
+  static constexpr bool enabled = true;
+
+  static void Write(Stream* strm, const T& data) {
+    if constexpr (TVM_FFI_IO_NO_ENDIAN_SWAP) {
+      strm->Write(&data, sizeof(T));
+    } else {
+      T copy = data;
+      ffi::ByteSwap(&copy, sizeof(T), 1);
+      strm->Write(&copy, sizeof(T));
+    }
+  }
+
+  static bool Read(Stream* strm, T* data) {
+    bool ok = strm->Read(data, sizeof(T)) == sizeof(T);
+    if constexpr (!TVM_FFI_IO_NO_ENDIAN_SWAP) {
+      ffi::ByteSwap(data, sizeof(T), 1);
+    }
+    return ok;
+  }
+};
+
+// ---- Enum types (delegate to underlying arithmetic type) ----
+template <typename T>
+struct Serializer<T, std::enable_if_t<std::is_enum_v<T>>> {
+  static constexpr bool enabled = true;
+  using U = std::underlying_type_t<T>;
+
+  static void Write(Stream* strm, const T& data) {
+    Serializer<U>::Write(strm, static_cast<U>(data));
+  }
+
+  static bool Read(Stream* strm, T* data) {
+    U val;
+    if (!Serializer<U>::Read(strm, &val)) return false;
+    *data = static_cast<T>(val);
+    return true;
+  }
+};
+
+// ---- std::string ----
+template <>
+struct Serializer<std::string> {
+  static constexpr bool enabled = true;
+
+  static void Write(Stream* strm, const std::string& data) {
+    uint64_t sz = static_cast<uint64_t>(data.size());
+    Serializer<uint64_t>::Write(strm, sz);
+    if (sz != 0) {
+      strm->Write(data.data(), data.size());
+    }
+  }
+
+  static bool Read(Stream* strm, std::string* data) {
+    uint64_t sz;
+    if (!Serializer<uint64_t>::Read(strm, &sz)) return false;
+    data->resize(static_cast<size_t>(sz));
+    if (sz != 0) {
+      size_t nbytes = static_cast<size_t>(sz);
+      return strm->Read(&(*data)[0], nbytes) == nbytes;
+    }
+    return true;
+  }
+};
+
+// ---- std::vector<T> ----
+template <typename T>
+struct Serializer<std::vector<T>> {
+  static constexpr bool enabled = true;
+
+  static void Write(Stream* strm, const std::vector<T>& vec) {
+    uint64_t sz = static_cast<uint64_t>(vec.size());
+    Serializer<uint64_t>::Write(strm, sz);
+    if constexpr (std::is_arithmetic_v<T> && TVM_FFI_IO_NO_ENDIAN_SWAP) {
+      if (sz != 0) {
+        strm->Write(vec.data(), sizeof(T) * vec.size());
+      }
+    } else {
+      for (const auto& v : vec) {
+        Serializer<T>::Write(strm, v);
+      }
+    }
+  }
+
+  static bool Read(Stream* strm, std::vector<T>* vec) {
+    uint64_t sz;
+    if (!Serializer<uint64_t>::Read(strm, &sz)) return false;
+    vec->resize(static_cast<size_t>(sz));
+    if constexpr (std::is_arithmetic_v<T> && TVM_FFI_IO_NO_ENDIAN_SWAP) {
+      if (sz != 0) {
+        size_t nbytes = sizeof(T) * static_cast<size_t>(sz);
+        return strm->Read(vec->data(), nbytes) == nbytes;
+      }
+      return true;
+    } else {
+      for (size_t i = 0; i < static_cast<size_t>(sz); ++i) {
+        if (!Serializer<T>::Read(strm, &(*vec)[i])) return false;
+      }
+      return true;
+    }
+  }
+};
+
+// ---- std::pair<A, B> ----
+template <typename A, typename B>
+struct Serializer<std::pair<A, B>> {
+  static constexpr bool enabled = true;
+
+  static void Write(Stream* strm, const std::pair<A, B>& data) {
+    Serializer<A>::Write(strm, data.first);
+    Serializer<B>::Write(strm, data.second);
+  }
+
+  static bool Read(Stream* strm, std::pair<A, B>* data) {
+    return Serializer<A>::Read(strm, &data->first) && Serializer<B>::Read(strm, &data->second);
+  }
+};
+
+// ---- std::unordered_map<K, V> ----
+template <typename K, typename V>
+struct Serializer<std::unordered_map<K, V>> {
+  static constexpr bool enabled = true;
+
+  static void Write(Stream* strm, const std::unordered_map<K, V>& data) {
+    std::vector<std::pair<K, V>> vec(data.begin(), data.end());
+    Serializer<std::vector<std::pair<K, V>>>::Write(strm, vec);
+  }
+
+  static bool Read(Stream* strm, std::unordered_map<K, V>* data) {
+    std::vector<std::pair<K, V>> vec;
+    if (!Serializer<std::vector<std::pair<K, V>>>::Read(strm, &vec)) return false;
+    data->clear();
+    data->insert(vec.begin(), vec.end());
+    return true;
+  }
+};
+
+// ---- DLDataType ----
+template <>
+struct Serializer<DLDataType> {
+  static constexpr bool enabled = true;
+
+  static void Write(Stream* strm, const DLDataType& dtype) {
+    Serializer<uint8_t>::Write(strm, dtype.code);
+    Serializer<uint8_t>::Write(strm, dtype.bits);
+    Serializer<uint16_t>::Write(strm, dtype.lanes);
+  }
+
+  static bool Read(Stream* strm, DLDataType* dtype) {
+    if (!Serializer<uint8_t>::Read(strm, &dtype->code)) return false;
+    if (!Serializer<uint8_t>::Read(strm, &dtype->bits)) return false;
+    if (!Serializer<uint16_t>::Read(strm, &dtype->lanes)) return false;
+    return true;
+  }
+};
+
+// ---- DLDevice ----
+template <>
+struct Serializer<DLDevice> {
+  static constexpr bool enabled = true;
+
+  static void Write(Stream* strm, const DLDevice& dev) {
+    int32_t device_type = static_cast<int32_t>(dev.device_type);
+    Serializer<int32_t>::Write(strm, device_type);
+    Serializer<int32_t>::Write(strm, dev.device_id);
+  }
+
+  static bool Read(Stream* strm, DLDevice* dev) {
+    int32_t device_type = 0;
+    if (!Serializer<int32_t>::Read(strm, &device_type)) return false;
+    dev->device_type = static_cast<DLDeviceType>(device_type);
+    if (!Serializer<int32_t>::Read(strm, &dev->device_id)) return false;
+    return true;
+  }
+};
+
+}  // namespace support
+}  // namespace tvm
+
+#endif  // TVM_SUPPORT_SERIALIZER_H_
diff --git a/include/tvm/support/with.h b/include/tvm/support/with.h
index 8333adc9e6..8d4d48038d 100644
--- a/include/tvm/support/with.h
+++ b/include/tvm/support/with.h
@@ -25,8 +25,6 @@
 #ifndef TVM_SUPPORT_WITH_H_
 #define TVM_SUPPORT_WITH_H_
 
-#include <dmlc/common.h>
-
 #include <functional>
 #include <utility>
 
@@ -66,7 +64,7 @@ class With {
     ctx_.EnterWithScope();
   }
   /*! \brief destructor, leaves the scope of the context. */
-  ~With() DMLC_THROW_EXCEPTION { ctx_.ExitWithScope(); }
+  ~With() noexcept(false) { ctx_.ExitWithScope(); }
 
   // Disable copy and move construction.  `With` is intended only for
   // use in nested contexts that are exited in the reverse order of
diff --git a/include/tvm/target/tag.h b/include/tvm/target/tag.h
index 59a13ae572..d3fae83e06 100644
--- a/include/tvm/target/tag.h
+++ b/include/tvm/target/tag.h
@@ -151,7 +151,7 @@ inline TargetTagRegEntry& TargetTagRegEntry::set_name() {
 }
 
 #define TVM_TARGET_TAG_REGISTER_VAR_DEF \
-  static DMLC_ATTRIBUTE_UNUSED ::tvm::TargetTagRegEntry& __make_##TargetTag
+  static TVM_ATTRIBUTE_UNUSED ::tvm::TargetTagRegEntry& __make_##TargetTag
 
 /*!
  * \def TVM_REGISTER_TARGET_TAG
diff --git a/include/tvm/target/target_kind.h b/include/tvm/target/target_kind.h
index 7722211b3e..f2de1d6795 100644
--- a/include/tvm/target/target_kind.h
+++ b/include/tvm/target/target_kind.h
@@ -409,7 +409,7 @@ inline TargetKindRegEntry& TargetKindRegEntry::set_name() {
 }
 
 #define TVM_TARGET_KIND_REGISTER_VAR_DEF \
-  static DMLC_ATTRIBUTE_UNUSED ::tvm::TargetKindRegEntry& __make_##TargetKind
+  static TVM_ATTRIBUTE_UNUSED ::tvm::TargetKindRegEntry& __make_##TargetKind
 
 /*!
  * \def TVM_REGISTER_TARGET_KIND
diff --git a/python/tvm/contrib/hexagon/tools.py b/python/tvm/contrib/hexagon/tools.py
index f010461df0..64bde9316f 100644
--- a/python/tvm/contrib/hexagon/tools.py
+++ b/python/tvm/contrib/hexagon/tools.py
@@ -291,10 +291,8 @@ def create_aot_shared(so_name: Union[str, pathlib.Path], files, hexagon_arch: st
         f"-O3",
         f"-I{tvm_dir / 'include'}",
         f"-I{tvm_dir / '3rdparty' / 'dlpack' / 'include'}",
-        f"-I{tvm_dir / '3rdparty' / 'dmlc-core' / 'include'}",
         f"-I{pathlib.Path(HEXAGON_SDK_ROOT) / 'rtos' / 'qurt' / compute_arch / 'include'/ 'posix'}",
         f"-I{pathlib.Path(HEXAGON_SDK_ROOT) / 'rtos' / 'qurt' / compute_arch / 'include' / 'qurt'}",
-        f"-DDMLC_USE_LOGGING_LIBRARY=<tvm/runtime/logging.h>",
         f"-D_MACH_I32=int",
     ]
 
diff --git a/python/tvm/libinfo.py b/python/tvm/libinfo.py
index f49b483daa..691adf9f98 100644
--- a/python/tvm/libinfo.py
+++ b/python/tvm/libinfo.py
@@ -229,7 +229,6 @@ def find_include_path(name=None, search_path=None, optional=False):
         else:
             tvm_include_path = [os.path.join(p, name) for p in header_path]
         dlpack_include_path = []
-        dmlc_include_path = []
     else:
         tvm_include_path = [os.path.join(p, "include") for p in header_path]
         tvm_ffi_include_path = [
@@ -239,15 +238,11 @@ def find_include_path(name=None, search_path=None, optional=False):
             os.path.join(p, "3rdparty", "tvm-ffi", "3rdparty", "dlpack", "include")
             for p in header_path
         ]
-        dmlc_include_path = [
-            os.path.join(p, "3rdparty", "dmlc-core", "include") for p in header_path
-        ]
 
         # try to find include path
         include_found = [p for p in tvm_include_path if os.path.exists(p) and os.path.isdir(p)]
         include_found += [p for p in tvm_ffi_include_path if os.path.exists(p) and os.path.isdir(p)]
         include_found += [p for p in dlpack_include_path if os.path.exists(p) and os.path.isdir(p)]
-        include_found += [p for p in dmlc_include_path if os.path.exists(p) and os.path.isdir(p)]
 
     if not include_found:
         message = (
diff --git a/python/tvm/relax/frontend/nn/extern.py b/python/tvm/relax/frontend/nn/extern.py
index b35f6e0d22..e38de97df3 100644
--- a/python/tvm/relax/frontend/nn/extern.py
+++ b/python/tvm/relax/frontend/nn/extern.py
@@ -309,7 +309,6 @@ class SourceModule(ExternModule):  # pylint: disable=too-few-public-methods
         tvm_home = SourceModule.tvm_home()
         results = [
             tvm_home / "include",
-            tvm_home / "3rdparty/dmlc-core/include",
             tvm_home / "3rdparty/tvm-ffi/include",
             tvm_home / "3rdparty/tvm-ffi/3rdparty/dlpack/include",
         ]
@@ -352,18 +351,12 @@ class SourceModule(ExternModule):  # pylint: disable=too-few-public-methods
                 "-c",  # generate object file
                 "-O3",
                 "-std=c++17",
-                # DMLC default
-                "-DDMLC_USE_FOPEN64=0",
-                "-DDMLC_USE_LOGGING_LIBRARY=<tvm/runtime/logging.h>",
             ]
         elif source_format == "cu":
             host_flags = [
                 "-c",  # generate object file
                 "-O3",
                 "-std=c++17",
-                # DMLC default
-                "-DDMLC_USE_FOPEN64=0",
-                "-DDMLC_USE_LOGGING_LIBRARY=<tvm/runtime/logging.h>",
                 # Enable `-fPIC` for the host compiler
                 "-Xcompiler=-fPIC",
             ]
diff --git a/src/arith/solve_linear_equation.cc b/src/arith/solve_linear_equation.cc
index 8143892d9a..0afdbb4a58 100644
--- a/src/arith/solve_linear_equation.cc
+++ b/src/arith/solve_linear_equation.cc
@@ -31,6 +31,8 @@
 #include <tvm/tir/op.h>
 #include <tvm/tir/stmt_functor.h>
 
+#include <unordered_set>
+
 #include "int_operator.h"
 
 namespace tvm {
diff --git a/src/contrib/msc/core/codegen/base_codegen.h b/src/contrib/msc/core/codegen/base_codegen.h
index dc2d5d1ef9..80747ceed0 100644
--- a/src/contrib/msc/core/codegen/base_codegen.h
+++ b/src/contrib/msc/core/codegen/base_codegen.h
@@ -24,7 +24,7 @@
 #ifndef TVM_CONTRIB_MSC_CORE_CODEGEN_BASE_CODEGEN_H_
 #define TVM_CONTRIB_MSC_CORE_CODEGEN_BASE_CODEGEN_H_
 
-#include <dmlc/json.h>
+#include <tvm/ffi/extra/json.h>
 #include <tvm/script/printer/doc.h>
 
 #include <memory>
@@ -151,9 +151,8 @@ class BaseCodeGen {
     graph_ = graph;
     config_.reset(new ConfigType());
     if (config.size() > 0) {
-      std::istringstream is(config);
-      dmlc::JSONReader reader(&is);
-      reader.Read(config_.get());
+      namespace json = ::tvm::ffi::json;
+      config_->Load(json::Parse(config).cast<json::Object>());
     }
     while (!scopes_.empty()) {
       scopes_.pop();
diff --git a/src/contrib/msc/core/codegen/codegen_json.cc b/src/contrib/msc/core/codegen/codegen_json.cc
index 6ccec35b78..cc0dd011cc 100644
--- a/src/contrib/msc/core/codegen/codegen_json.cc
+++ b/src/contrib/msc/core/codegen/codegen_json.cc
@@ -56,7 +56,7 @@ std::vector<JSONGraphNodeEntry> MSCJSONSerializer::VisitExpr_(const CallNode* ca
 void MSCJSONSerializer::AddNodeAttr(JSONGraphObjectPtr node, const ffi::String& key,
                                     const ffi::String& value) {
   std::vector<std::string> array_value{std::string(value)};
-  std::vector<dmlc::any> dmlc_value;
+  std::vector<std::any> dmlc_value;
   dmlc_value.emplace_back(array_value);
   node->SetAttr(std::string(key), dmlc_value);
 }
diff --git a/src/contrib/msc/core/codegen/codegen_json.h b/src/contrib/msc/core/codegen/codegen_json.h
index 08a834bdaa..65fdfd0a03 100644
--- a/src/contrib/msc/core/codegen/codegen_json.h
+++ b/src/contrib/msc/core/codegen/codegen_json.h
@@ -24,6 +24,8 @@
 #ifndef TVM_CONTRIB_MSC_CORE_CODEGEN_CODEGEN_JSON_H_
 #define TVM_CONTRIB_MSC_CORE_CODEGEN_CODEGEN_JSON_H_
 
+#include <tvm/ffi/extra/json.h>
+
 #include <string>
 #include <unordered_map>
 #include <vector>
@@ -48,16 +50,14 @@ using JSONSerializer = backend::contrib::JSONSerializer;
 struct MSCCompileConfig {
   std::string graph_json;
   std::unordered_map<std::string, std::string> options;
-  void Load(dmlc::JSONReader* reader) {
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "graph_json") {
-        reader->Read(&graph_json);
-      } else {
-        std::string value;
-        reader->Read(&value);
-        options.insert({key, value});
+  void Load(ffi::json::Object obj) {
+    if (auto it = obj.find(ffi::String("graph_json")); it != obj.end()) {
+      graph_json = std::string((*it).second.cast<ffi::String>());
+    }
+    for (const auto& kv : obj) {
+      std::string k = std::string(kv.first.cast<ffi::String>());
+      if (k != "graph_json") {
+        options[k] = std::string(kv.second.cast<ffi::String>());
       }
     }
   }
@@ -72,10 +72,9 @@ class MSCJSONSerializer : public JSONSerializer {
   explicit MSCJSONSerializer(const ffi::Map<Constant, ffi::String>& constant_names,
                              const std::string& options)
       : JSONSerializer(constant_names) {
+    namespace json = ::tvm::ffi::json;
     MSCCompileConfig config;
-    std::istringstream is(options);
-    dmlc::JSONReader reader(&is);
-    reader.Read(&config);
+    config.Load(json::Parse(options).cast<json::Object>());
     ICHECK(config.graph_json.size() > 0) << "graph_json is needed to init MSCGraph";
     graph_ = MSCGraph(config.graph_json);
     for (const auto& pair : config.options) {
diff --git a/src/contrib/msc/core/codegen/codegen_utils.h b/src/contrib/msc/core/codegen/codegen_utils.h
index 6fbaa96dd6..d6f5b5c375 100644
--- a/src/contrib/msc/core/codegen/codegen_utils.h
+++ b/src/contrib/msc/core/codegen/codegen_utils.h
@@ -24,6 +24,7 @@
 #ifndef TVM_CONTRIB_MSC_CORE_CODEGEN_CODEGEN_UTILS_H_
 #define TVM_CONTRIB_MSC_CORE_CODEGEN_CODEGEN_UTILS_H_
 
+#include <tvm/ffi/extra/json.h>
 #include <tvm/script/printer/doc.h>
 
 #include <memory>
@@ -51,29 +52,42 @@ using namespace tvm::script::printer;
   std::string baseline_folder{"baseline"}; \
   std::vector<size_t> version{0, 0, 0};
 
-#define CODEGEN_CONFIG_PARSE                    \
-  if (key == "training") {                      \
-    reader->Read(&training);                    \
-  } else if (key == "use_tools") {              \
-    reader->Read(&use_tools);                   \
-  } else if (key == "use_plugin") {             \
-    reader->Read(&use_plugin);                  \
-  } else if (key == "need_test") {              \
-    reader->Read(&need_test);                   \
-  } else if (key == "tools_scope") {            \
-    reader->Read(&tools_scope);                 \
-  } else if (key == "tools_tag") {              \
-    reader->Read(&tools_tag);                   \
-  } else if (key == "test_device") {            \
-    reader->Read(&test_device);                 \
-  } else if (key == "prefix") {                 \
-    reader->Read(&prefix);                      \
-  } else if (key == "version") {                \
-    reader->Read(&version);                     \
-  } else if (key == "baseline_folder") {        \
-    reader->Read(&baseline_folder);             \
-  } else {                                      \
-    LOG(FATAL) << "Do not support key " << key; \
+#define CODEGEN_CONFIG_PARSE                                                 \
+  namespace json = ::tvm::ffi::json;                                         \
+  if (auto it = obj.find(ffi::String("training")); it != obj.end()) {        \
+    training = (*it).second.cast<bool>();                                    \
+  }                                                                          \
+  if (auto it = obj.find(ffi::String("use_tools")); it != obj.end()) {       \
+    use_tools = (*it).second.cast<bool>();                                   \
+  }                                                                          \
+  if (auto it = obj.find(ffi::String("use_plugin")); it != obj.end()) {      \
+    use_plugin = (*it).second.cast<bool>();                                  \
+  }                                                                          \
+  if (auto it = obj.find(ffi::String("need_test")); it != obj.end()) {       \
+    need_test = (*it).second.cast<bool>();                                   \
+  }                                                                          \
+  if (auto it = obj.find(ffi::String("tools_scope")); it != obj.end()) {     \
+    tools_scope = std::string((*it).second.cast<ffi::String>());             \
+  }                                                                          \
+  if (auto it = obj.find(ffi::String("tools_tag")); it != obj.end()) {       \
+    tools_tag = std::string((*it).second.cast<ffi::String>());               \
+  }                                                                          \
+  if (auto it = obj.find(ffi::String("test_device")); it != obj.end()) {     \
+    test_device = std::string((*it).second.cast<ffi::String>());             \
+  }                                                                          \
+  if (auto it = obj.find(ffi::String("prefix")); it != obj.end()) {          \
+    prefix = std::string((*it).second.cast<ffi::String>());                  \
+  }                                                                          \
+  if (auto it = obj.find(ffi::String("version")); it != obj.end()) {         \
+    auto arr = (*it).second.cast<json::Array>();                             \
+    version.clear();                                                         \
+    version.reserve(arr.size());                                             \
+    for (const auto& elem : arr) {                                           \
+      version.push_back(static_cast<size_t>(elem.cast<int64_t>()));          \
+    }                                                                        \
+  }                                                                          \
+  if (auto it = obj.find(ffi::String("baseline_folder")); it != obj.end()) { \
+    baseline_folder = std::string((*it).second.cast<ffi::String>());         \
   }
 
 #define DESCRIBE_PRIM_BINARY(OpType, Symbol, AsFunc)                                   \
diff --git a/src/contrib/msc/core/codegen/cpp_codegen.h b/src/contrib/msc/core/codegen/cpp_codegen.h
index 99988d689a..5043da3744 100644
--- a/src/contrib/msc/core/codegen/cpp_codegen.h
+++ b/src/contrib/msc/core/codegen/cpp_codegen.h
@@ -24,7 +24,6 @@
 #ifndef TVM_CONTRIB_MSC_CORE_CODEGEN_CPP_CODEGEN_H_
 #define TVM_CONTRIB_MSC_CORE_CODEGEN_CPP_CODEGEN_H_
 
-#include <dmlc/json.h>
 #include <tvm/script/printer/doc.h>
 
 #include <set>
diff --git a/src/contrib/msc/core/codegen/py_codegen.h b/src/contrib/msc/core/codegen/py_codegen.h
index 460818089f..e51adda8e4 100644
--- a/src/contrib/msc/core/codegen/py_codegen.h
+++ b/src/contrib/msc/core/codegen/py_codegen.h
@@ -24,7 +24,6 @@
 #ifndef TVM_CONTRIB_MSC_CORE_CODEGEN_PY_CODEGEN_H_
 #define TVM_CONTRIB_MSC_CORE_CODEGEN_PY_CODEGEN_H_
 
-#include <dmlc/json.h>
 #include <tvm/script/printer/doc.h>
 
 #include <set>
diff --git a/src/contrib/msc/core/ir/graph.cc b/src/contrib/msc/core/ir/graph.cc
index 6e69e66bca..ada1922f22 100644
--- a/src/contrib/msc/core/ir/graph.cc
+++ b/src/contrib/msc/core/ir/graph.cc
@@ -94,10 +94,10 @@ void MSCTensorNode::FromJson(const JsonMSCTensor& j_tensor) {
 }
 
 void MSCTensorNode::FromJson(const std::string& json_str) {
-  std::istringstream is(json_str);
-  dmlc::JSONReader reader(&is);
+  namespace json = ::tvm::ffi::json;
+  auto parsed = json::Parse(json_str);
   JsonMSCTensor j_tensor;
-  reader.Read(&j_tensor);
+  j_tensor.Load(parsed.cast<json::Object>());
   FromJson(j_tensor);
 }
 
@@ -440,10 +440,10 @@ void MSCJointNode::FromJson(const JsonMSCJoint& j_joint,
 
 void MSCJointNode::FromJson(const std::string& json_str,
                             const ffi::Map<ffi::String, BaseJoint>& nodes) {
-  std::istringstream is(json_str);
-  dmlc::JSONReader reader(&is);
+  namespace json = ::tvm::ffi::json;
+  auto parsed = json::Parse(json_str);
   JsonMSCJoint j_joint;
-  reader.Read(&j_joint);
+  j_joint.Load(parsed.cast<json::Object>());
   FromJson(j_joint, nodes);
 }
 
@@ -579,10 +579,10 @@ void MSCPrimNode::FromJson(const JsonMSCPrim& j_prim,
 
 void MSCPrimNode::FromJson(const std::string& json_str,
                            const ffi::Map<ffi::String, BaseJoint>& prims) {
-  std::istringstream is(json_str);
-  dmlc::JSONReader reader(&is);
+  namespace json = ::tvm::ffi::json;
+  auto parsed = json::Parse(json_str);
   JsonMSCPrim j_prim;
-  reader.Read(&j_prim);
+  j_prim.Load(parsed.cast<json::Object>());
   FromJson(j_prim, prims);
 }
 
@@ -667,10 +667,10 @@ void WeightJointNode::FromJson(const JsonWeightJoint& j_joint,
 
 void WeightJointNode::FromJson(const std::string& json_str,
                                const ffi::Map<ffi::String, BaseJoint>& nodes) {
-  std::istringstream is(json_str);
-  dmlc::JSONReader reader(&is);
+  namespace json = ::tvm::ffi::json;
+  auto parsed = json::Parse(json_str);
   JsonWeightJoint j_joint;
-  reader.Read(&j_joint);
+  j_joint.Load(parsed.cast<json::Object>());
   FromJson(j_joint, nodes);
 }
 
@@ -771,10 +771,10 @@ void MSCGraphNode::FromJson(const JsonMSCGraph& j_graph) {
 }
 
 void MSCGraphNode::FromJson(const std::string& json_str) {
-  std::istringstream is(json_str);
-  dmlc::JSONReader reader(&is);
+  namespace json = ::tvm::ffi::json;
+  auto parsed = json::Parse(json_str);
   JsonMSCGraph j_graph;
-  reader.Read(&j_graph);
+  j_graph.Load(parsed.cast<json::Object>());
   FromJson(j_graph);
 }
 
@@ -1203,10 +1203,10 @@ void WeightGraphNode::FromJson(const JsonWeightGraph& j_graph) {
 }
 
 void WeightGraphNode::FromJson(const std::string& json_str) {
-  std::istringstream is(json_str);
-  dmlc::JSONReader reader(&is);
+  namespace json = ::tvm::ffi::json;
+  auto parsed = json::Parse(json_str);
   JsonWeightGraph j_graph;
-  reader.Read(&j_graph);
+  j_graph.Load(parsed.cast<json::Object>());
   FromJson(j_graph);
 }
 
@@ -1458,10 +1458,10 @@ TVM_FFI_STATIC_INIT_BLOCK() {
            })
       .def("msc.core.MSCTensorToJson",
            [](const MSCTensor& tensor) -> ffi::String {
+             namespace json = ::tvm::ffi::json;
              const auto& tensor_json = tensor->ToJson();
              std::ostringstream os;
-             dmlc::JSONWriter writer(&os);
-             tensor_json.Save(&writer);
+             os << std::string(json::Stringify(tensor_json.SaveToJSON()));
              return os.str();
            })
       .def("msc.core.MSCTensorFromJson",
@@ -1570,10 +1570,10 @@ TVM_FFI_STATIC_INIT_BLOCK() {
            [](const MSCGraph& graph) -> ffi::Array<MSCTensor> { return graph->GetOutputs(); })
       .def("msc.core.MSCGraphToJson",
            [](const MSCGraph& graph) -> ffi::String {
+             namespace json = ::tvm::ffi::json;
              const auto& graph_json = graph->ToJson();
              std::ostringstream os;
-             dmlc::JSONWriter writer(&os);
-             graph_json.Save(&writer);
+             os << std::string(json::Stringify(graph_json.SaveToJSON()));
              return os.str();
            })
       .def("msc.core.MSCGraphFromJson",
@@ -1596,10 +1596,10 @@ TVM_FFI_STATIC_INIT_BLOCK() {
            })
       .def("msc.core.WeightGraphToJson",
            [](const WeightGraph& graph) -> ffi::String {
+             namespace json = ::tvm::ffi::json;
              const auto& graph_json = graph->ToJson();
              std::ostringstream os;
-             dmlc::JSONWriter writer(&os);
-             graph_json.Save(&writer);
+             os << std::string(json::Stringify(graph_json.SaveToJSON()));
              return os.str();
            })
       .def("msc.core.WeightGraphFromJson",
diff --git a/src/contrib/msc/core/ir/graph.h b/src/contrib/msc/core/ir/graph.h
index bb62cb194a..d54a5f75f9 100644
--- a/src/contrib/msc/core/ir/graph.h
+++ b/src/contrib/msc/core/ir/graph.h
@@ -24,7 +24,7 @@
 #ifndef TVM_CONTRIB_MSC_CORE_IR_GRAPH_H_
 #define TVM_CONTRIB_MSC_CORE_IR_GRAPH_H_
 
-#include <dmlc/json.h>
+#include <tvm/ffi/extra/json.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/s_tir/data_layout.h>
 
@@ -51,37 +51,60 @@ struct JsonMSCTensor {
   std::vector<int64_t> shape;
   std::vector<std::string> prims;
 
-  void Save(dmlc::JSONWriter* writer) const {
-    writer->BeginObject();
-    writer->WriteObjectKeyValue("name", name);
-    writer->WriteObjectKeyValue("alias", alias);
-    writer->WriteObjectKeyValue("dtype", dtype);
-    writer->WriteObjectKeyValue("layout", layout);
-    writer->WriteObjectKeyValue("shape", shape);
-    writer->WriteObjectKeyValue("prims", prims);
-    writer->EndObject();
+  ffi::json::Value SaveToJSON() const {
+    ffi::json::Object obj;
+    obj.Set(ffi::String("name"), ffi::String(name));
+    obj.Set(ffi::String("alias"), ffi::String(alias));
+    obj.Set(ffi::String("dtype"), ffi::String(dtype));
+    obj.Set(ffi::String("layout"), ffi::String(layout));
+    {
+      ffi::json::Array arr;
+      for (const auto& v : shape) {
+        arr.push_back(static_cast<int64_t>(v));
+      }
+      obj.Set(ffi::String("shape"), std::move(arr));
+    }
+    {
+      ffi::json::Array arr;
+      for (const auto& s : prims) {
+        arr.push_back(ffi::String(s));
+      }
+      obj.Set(ffi::String("prims"), std::move(arr));
+    }
+    return obj;
   }
 
-  void Load(dmlc::JSONReader* reader) {
+  void Load(ffi::json::Object obj) {
     int bitmask = 0;
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "name") {
-        reader->Read(&name);
-        bitmask |= 1;
-      } else if (key == "alias") {
-        reader->Read(&alias);
-      } else if (key == "dtype") {
-        reader->Read(&dtype);
-        bitmask |= 2;
-      } else if (key == "layout") {
-        reader->Read(&layout);
-      } else if (key == "shape") {
-        reader->Read(&shape);
-        bitmask |= 4;
-      } else if (key == "prims") {
-        reader->Read(&prims);
+    if (auto it = obj.find(ffi::String("name")); it != obj.end()) {
+      name = std::string((*it).second.cast<ffi::String>());
+      bitmask |= 1;
+    }
+    if (auto it = obj.find(ffi::String("alias")); it != obj.end()) {
+      alias = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("dtype")); it != obj.end()) {
+      dtype = std::string((*it).second.cast<ffi::String>());
+      bitmask |= 2;
+    }
+    if (auto it = obj.find(ffi::String("layout")); it != obj.end()) {
+      layout = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("shape")); it != obj.end()) {
+      auto arr = (*it).second.cast<ffi::json::Array>();
+      shape.clear();
+      shape.reserve(arr.size());
+      for (const auto& elem : arr) {
+        shape.push_back(elem.cast<int64_t>());
+      }
+      bitmask |= 4;
+    }
+    if (auto it = obj.find(ffi::String("prims")); it != obj.end()) {
+      auto arr = (*it).second.cast<ffi::json::Array>();
+      prims.clear();
+      prims.reserve(arr.size());
+      for (const auto& elem : arr) {
+        prims.push_back(std::string(elem.cast<ffi::String>()));
       }
     }
     ICHECK_EQ(bitmask, 1 | 2 | 4) << "name, dtype and shape should be given";
@@ -105,47 +128,109 @@ struct JsonMSCJoint {
   std::unordered_map<std::string, std::string> attrs;
   std::unordered_map<std::string, JsonMSCTensor> weights;
 
-  void Save(dmlc::JSONWriter* writer) const {
-    writer->BeginObject();
-    writer->WriteObjectKeyValue("index", index);
-    writer->WriteObjectKeyValue("name", name);
-    writer->WriteObjectKeyValue("shared_ref", shared_ref);
-    writer->WriteObjectKeyValue("optype", optype);
-    writer->WriteObjectKeyValue("parents", parents);
-    writer->WriteObjectKeyValue("inputs", inputs);
-    writer->WriteObjectKeyValue("outputs", outputs);
-    writer->WriteObjectKeyValue("attrs", attrs);
-    writer->WriteObjectKeyValue("weights", weights);
-    writer->EndObject();
+  ffi::json::Value SaveToJSON() const {
+    ffi::json::Object obj;
+    obj.Set(ffi::String("index"), static_cast<int64_t>(index));
+    obj.Set(ffi::String("name"), ffi::String(name));
+    obj.Set(ffi::String("shared_ref"), ffi::String(shared_ref));
+    obj.Set(ffi::String("optype"), ffi::String(optype));
+    {
+      ffi::json::Array arr;
+      for (const auto& s : parents) {
+        arr.push_back(ffi::String(s));
+      }
+      obj.Set(ffi::String("parents"), std::move(arr));
+    }
+    {
+      ffi::json::Array arr;
+      for (const auto& s : inputs) {
+        arr.push_back(ffi::String(s));
+      }
+      obj.Set(ffi::String("inputs"), std::move(arr));
+    }
+    {
+      ffi::json::Array arr;
+      for (const auto& item : outputs) {
+        arr.push_back(item.SaveToJSON());
+      }
+      obj.Set(ffi::String("outputs"), std::move(arr));
+    }
+    {
+      ffi::json::Object inner;
+      for (const auto& kv : attrs) {
+        inner.Set(ffi::String(kv.first), ffi::String(kv.second));
+      }
+      obj.Set(ffi::String("attrs"), std::move(inner));
+    }
+    {
+      ffi::json::Object inner;
+      for (const auto& kv : weights) {
+        inner.Set(ffi::String(kv.first), kv.second.SaveToJSON());
+      }
+      obj.Set(ffi::String("weights"), std::move(inner));
+    }
+    return obj;
   }
 
-  void Load(dmlc::JSONReader* reader) {
+  void Load(ffi::json::Object obj) {
     int bitmask = 0;
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "index") {
-        reader->Read(&index);
-        bitmask |= 1;
-      } else if (key == "name") {
-        reader->Read(&name);
-        bitmask |= 2;
-      } else if (key == "shared_ref") {
-        reader->Read(&shared_ref);
-      } else if (key == "optype") {
-        reader->Read(&optype);
-        bitmask |= 4;
-      } else if (key == "parents") {
-        reader->Read(&parents);
-      } else if (key == "inputs") {
-        reader->Read(&inputs);
-      } else if (key == "outputs") {
-        reader->Read(&outputs);
-        bitmask |= 8;
-      } else if (key == "attrs") {
-        reader->Read(&attrs);
-      } else if (key == "weights") {
-        reader->Read(&weights);
+    if (auto it = obj.find(ffi::String("index")); it != obj.end()) {
+      index = static_cast<size_t>((*it).second.cast<int64_t>());
+      bitmask |= 1;
+    }
+    if (auto it = obj.find(ffi::String("name")); it != obj.end()) {
+      name = std::string((*it).second.cast<ffi::String>());
+      bitmask |= 2;
+    }
+    if (auto it = obj.find(ffi::String("shared_ref")); it != obj.end()) {
+      shared_ref = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("optype")); it != obj.end()) {
+      optype = std::string((*it).second.cast<ffi::String>());
+      bitmask |= 4;
+    }
+    if (auto it = obj.find(ffi::String("parents")); it != obj.end()) {
+      auto arr = (*it).second.cast<ffi::json::Array>();
+      parents.clear();
+      parents.reserve(arr.size());
+      for (const auto& elem : arr) {
+        parents.push_back(std::string(elem.cast<ffi::String>()));
+      }
+    }
+    if (auto it = obj.find(ffi::String("inputs")); it != obj.end()) {
+      auto arr = (*it).second.cast<ffi::json::Array>();
+      inputs.clear();
+      inputs.reserve(arr.size());
+      for (const auto& elem : arr) {
+        inputs.push_back(std::string(elem.cast<ffi::String>()));
+      }
+    }
+    if (auto it = obj.find(ffi::String("outputs")); it != obj.end()) {
+      auto arr = (*it).second.cast<ffi::json::Array>();
+      outputs.clear();
+      outputs.reserve(arr.size());
+      for (const auto& elem : arr) {
+        JsonMSCTensor item;
+        item.Load(elem.cast<ffi::json::Object>());
+        outputs.push_back(std::move(item));
+      }
+      bitmask |= 8;
+    }
+    if (auto it = obj.find(ffi::String("attrs")); it != obj.end()) {
+      auto inner = (*it).second.cast<ffi::json::Object>();
+      attrs.clear();
+      for (const auto& kv : inner) {
+        attrs[std::string(kv.first.cast<ffi::String>())] =
+            std::string(kv.second.cast<ffi::String>());
+      }
+    }
+    if (auto it = obj.find(ffi::String("weights")); it != obj.end()) {
+      auto inner = (*it).second.cast<ffi::json::Object>();
+      weights.clear();
+      for (const auto& kv : inner) {
+        JsonMSCTensor item;
+        item.Load(kv.second.cast<ffi::json::Object>());
+        weights[std::string(kv.first.cast<ffi::String>())] = std::move(item);
       }
     }
     ICHECK_EQ(bitmask, 1 | 2 | 4 | 8) << "index, name, optype and outputs should be given";
@@ -163,34 +248,56 @@ struct JsonMSCPrim {
   std::vector<std::string> parents;
   std::unordered_map<std::string, std::string> attrs;
 
-  void Save(dmlc::JSONWriter* writer) const {
-    writer->BeginObject();
-    writer->WriteObjectKeyValue("index", index);
-    writer->WriteObjectKeyValue("name", name);
-    writer->WriteObjectKeyValue("optype", optype);
-    writer->WriteObjectKeyValue("parents", parents);
-    writer->WriteObjectKeyValue("attrs", attrs);
-    writer->EndObject();
+  ffi::json::Value SaveToJSON() const {
+    ffi::json::Object obj;
+    obj.Set(ffi::String("index"), static_cast<int64_t>(index));
+    obj.Set(ffi::String("name"), ffi::String(name));
+    obj.Set(ffi::String("optype"), ffi::String(optype));
+    {
+      ffi::json::Array arr;
+      for (const auto& s : parents) {
+        arr.push_back(ffi::String(s));
+      }
+      obj.Set(ffi::String("parents"), std::move(arr));
+    }
+    {
+      ffi::json::Object inner;
+      for (const auto& kv : attrs) {
+        inner.Set(ffi::String(kv.first), ffi::String(kv.second));
+      }
+      obj.Set(ffi::String("attrs"), std::move(inner));
+    }
+    return obj;
   }
 
-  void Load(dmlc::JSONReader* reader) {
+  void Load(ffi::json::Object obj) {
     int bitmask = 0;
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "index") {
-        reader->Read(&index);
-        bitmask |= 1;
-      } else if (key == "name") {
-        reader->Read(&name);
-        bitmask |= 2;
-      } else if (key == "optype") {
-        reader->Read(&optype);
-        bitmask |= 4;
-      } else if (key == "parents") {
-        reader->Read(&parents);
-      } else if (key == "attrs") {
-        reader->Read(&attrs);
+    if (auto it = obj.find(ffi::String("index")); it != obj.end()) {
+      index = static_cast<size_t>((*it).second.cast<int64_t>());
+      bitmask |= 1;
+    }
+    if (auto it = obj.find(ffi::String("name")); it != obj.end()) {
+      name = std::string((*it).second.cast<ffi::String>());
+      bitmask |= 2;
+    }
+    if (auto it = obj.find(ffi::String("optype")); it != obj.end()) {
+      optype = std::string((*it).second.cast<ffi::String>());
+      bitmask |= 4;
+    }
+    if (auto it = obj.find(ffi::String("parents")); it != obj.end()) {
+      auto arr = (*it).second.cast<ffi::json::Array>();
+      parents.clear();
+      parents.reserve(arr.size());
+      for (const auto& elem : arr) {
+        parents.push_back(std::string(elem.cast<ffi::String>()));
+      }
+    }
+    if (auto it = obj.find(ffi::String("attrs")); it != obj.end()) {
+      auto inner = (*it).second.cast<ffi::json::Object>();
+      attrs.clear();
+      for (const auto& kv : inner) {
+        attrs[std::string(kv.first.cast<ffi::String>())] =
+            std::string(kv.second.cast<ffi::String>());
       }
     }
     ICHECK_EQ(bitmask, 1 | 2 | 4) << "index, name and optype should be given";
@@ -212,44 +319,80 @@ struct JsonWeightJoint {
   std::vector<std::string> friends;
   std::unordered_map<std::string, std::string> attrs;
 
-  void Save(dmlc::JSONWriter* writer) const {
-    writer->BeginObject();
-    writer->WriteObjectKeyValue("index", index);
-    writer->WriteObjectKeyValue("name", name);
-    writer->WriteObjectKeyValue("shared_ref", shared_ref);
-    writer->WriteObjectKeyValue("weight_type", weight_type);
-    writer->WriteObjectKeyValue("weight", weight);
-    writer->WriteObjectKeyValue("parents", parents);
-    writer->WriteObjectKeyValue("friends", friends);
-    writer->WriteObjectKeyValue("attrs", attrs);
-    writer->EndObject();
+  ffi::json::Value SaveToJSON() const {
+    ffi::json::Object obj;
+    obj.Set(ffi::String("index"), static_cast<int64_t>(index));
+    obj.Set(ffi::String("name"), ffi::String(name));
+    obj.Set(ffi::String("shared_ref"), ffi::String(shared_ref));
+    obj.Set(ffi::String("weight_type"), ffi::String(weight_type));
+    obj.Set(ffi::String("weight"), weight.SaveToJSON());
+    {
+      ffi::json::Array arr;
+      for (const auto& s : parents) {
+        arr.push_back(ffi::String(s));
+      }
+      obj.Set(ffi::String("parents"), std::move(arr));
+    }
+    {
+      ffi::json::Array arr;
+      for (const auto& s : friends) {
+        arr.push_back(ffi::String(s));
+      }
+      obj.Set(ffi::String("friends"), std::move(arr));
+    }
+    {
+      ffi::json::Object inner;
+      for (const auto& kv : attrs) {
+        inner.Set(ffi::String(kv.first), ffi::String(kv.second));
+      }
+      obj.Set(ffi::String("attrs"), std::move(inner));
+    }
+    return obj;
   }
 
-  void Load(dmlc::JSONReader* reader) {
+  void Load(ffi::json::Object obj) {
     int bitmask = 0;
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "index") {
-        reader->Read(&index);
-        bitmask |= 1;
-      } else if (key == "name") {
-        reader->Read(&name);
-        bitmask |= 2;
-      } else if (key == "shared_ref") {
-        reader->Read(&shared_ref);
-      } else if (key == "weight_type") {
-        reader->Read(&weight_type);
-        bitmask |= 4;
-      } else if (key == "weight") {
-        reader->Read(&weight);
-        bitmask |= 8;
-      } else if (key == "parents") {
-        reader->Read(&parents);
-      } else if (key == "friends") {
-        reader->Read(&friends);
-      } else if (key == "attrs") {
-        reader->Read(&attrs);
+    if (auto it = obj.find(ffi::String("index")); it != obj.end()) {
+      index = static_cast<size_t>((*it).second.cast<int64_t>());
+      bitmask |= 1;
+    }
+    if (auto it = obj.find(ffi::String("name")); it != obj.end()) {
+      name = std::string((*it).second.cast<ffi::String>());
+      bitmask |= 2;
+    }
+    if (auto it = obj.find(ffi::String("shared_ref")); it != obj.end()) {
+      shared_ref = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("weight_type")); it != obj.end()) {
+      weight_type = std::string((*it).second.cast<ffi::String>());
+      bitmask |= 4;
+    }
+    if (auto it = obj.find(ffi::String("weight")); it != obj.end()) {
+      weight.Load((*it).second.cast<ffi::json::Object>());
+      bitmask |= 8;
+    }
+    if (auto it = obj.find(ffi::String("parents")); it != obj.end()) {
+      auto arr = (*it).second.cast<ffi::json::Array>();
+      parents.clear();
+      parents.reserve(arr.size());
+      for (const auto& elem : arr) {
+        parents.push_back(std::string(elem.cast<ffi::String>()));
+      }
+    }
+    if (auto it = obj.find(ffi::String("friends")); it != obj.end()) {
+      auto arr = (*it).second.cast<ffi::json::Array>();
+      friends.clear();
+      friends.reserve(arr.size());
+      for (const auto& elem : arr) {
+        friends.push_back(std::string(elem.cast<ffi::String>()));
+      }
+    }
+    if (auto it = obj.find(ffi::String("attrs")); it != obj.end()) {
+      auto inner = (*it).second.cast<ffi::json::Object>();
+      attrs.clear();
+      for (const auto& kv : inner) {
+        attrs[std::string(kv.first.cast<ffi::String>())] =
+            std::string(kv.second.cast<ffi::String>());
       }
     }
     ICHECK_EQ(bitmask, 1 | 2 | 4 | 8) << "index, name, weight_type and weight should be given";
@@ -268,35 +411,83 @@ struct JsonMSCGraph {
   std::vector<JsonMSCJoint> nodes;
   std::vector<JsonMSCPrim> prims;
 
-  void Save(dmlc::JSONWriter* writer) const {
-    writer->BeginObject();
-    writer->WriteObjectKeyValue("name", name);
-    writer->WriteObjectKeyValue("inputs", inputs);
-    writer->WriteObjectKeyValue("outputs", outputs);
-    writer->WriteObjectKeyValue("nodes", nodes);
-    writer->WriteObjectKeyValue("prims", prims);
-    writer->EndObject();
+  ffi::json::Value SaveToJSON() const {
+    ffi::json::Object obj;
+    obj.Set(ffi::String("name"), ffi::String(name));
+    {
+      ffi::json::Array arr;
+      for (const auto& s : inputs) {
+        arr.push_back(ffi::String(s));
+      }
+      obj.Set(ffi::String("inputs"), std::move(arr));
+    }
+    {
+      ffi::json::Array arr;
+      for (const auto& s : outputs) {
+        arr.push_back(ffi::String(s));
+      }
+      obj.Set(ffi::String("outputs"), std::move(arr));
+    }
+    {
+      ffi::json::Array arr;
+      for (const auto& item : nodes) {
+        arr.push_back(item.SaveToJSON());
+      }
+      obj.Set(ffi::String("nodes"), std::move(arr));
+    }
+    {
+      ffi::json::Array arr;
+      for (const auto& item : prims) {
+        arr.push_back(item.SaveToJSON());
+      }
+      obj.Set(ffi::String("prims"), std::move(arr));
+    }
+    return obj;
   }
 
-  void Load(dmlc::JSONReader* reader) {
+  void Load(ffi::json::Object obj) {
     int bitmask = 0;
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "name") {
-        reader->Read(&name);
-        bitmask |= 1;
-      } else if (key == "inputs") {
-        reader->Read(&inputs);
-        bitmask |= 2;
-      } else if (key == "outputs") {
-        reader->Read(&outputs);
-        bitmask |= 4;
-      } else if (key == "nodes") {
-        reader->Read(&nodes);
-        bitmask |= 8;
-      } else if (key == "prims") {
-        reader->Read(&prims);
+    if (auto it = obj.find(ffi::String("name")); it != obj.end()) {
+      name = std::string((*it).second.cast<ffi::String>());
+      bitmask |= 1;
+    }
+    if (auto it = obj.find(ffi::String("inputs")); it != obj.end()) {
+      auto arr = (*it).second.cast<ffi::json::Array>();
+      inputs.clear();
+      inputs.reserve(arr.size());
+      for (const auto& elem : arr) {
+        inputs.push_back(std::string(elem.cast<ffi::String>()));
+      }
+      bitmask |= 2;
+    }
+    if (auto it = obj.find(ffi::String("outputs")); it != obj.end()) {
+      auto arr = (*it).second.cast<ffi::json::Array>();
+      outputs.clear();
+      outputs.reserve(arr.size());
+      for (const auto& elem : arr) {
+        outputs.push_back(std::string(elem.cast<ffi::String>()));
+      }
+      bitmask |= 4;
+    }
+    if (auto it = obj.find(ffi::String("nodes")); it != obj.end()) {
+      auto arr = (*it).second.cast<ffi::json::Array>();
+      nodes.clear();
+      nodes.reserve(arr.size());
+      for (const auto& elem : arr) {
+        JsonMSCJoint item;
+        item.Load(elem.cast<ffi::json::Object>());
+        nodes.push_back(std::move(item));
+      }
+      bitmask |= 8;
+    }
+    if (auto it = obj.find(ffi::String("prims")); it != obj.end()) {
+      auto arr = (*it).second.cast<ffi::json::Array>();
+      prims.clear();
+      prims.reserve(arr.size());
+      for (const auto& elem : arr) {
+        JsonMSCPrim item;
+        item.Load(elem.cast<ffi::json::Object>());
+        prims.push_back(std::move(item));
       }
     }
     ICHECK_EQ(bitmask, 1 | 2 | 4 | 8) << "name, inputs, outputs and nodes should be given";
@@ -312,25 +503,35 @@ struct JsonWeightGraph {
   std::string name;
   std::vector<JsonWeightJoint> nodes;
 
-  void Save(dmlc::JSONWriter* writer) const {
-    writer->BeginObject();
-    writer->WriteObjectKeyValue("name", name);
-    writer->WriteObjectKeyValue("nodes", nodes);
-    writer->EndObject();
+  ffi::json::Value SaveToJSON() const {
+    ffi::json::Object obj;
+    obj.Set(ffi::String("name"), ffi::String(name));
+    {
+      ffi::json::Array arr;
+      for (const auto& item : nodes) {
+        arr.push_back(item.SaveToJSON());
+      }
+      obj.Set(ffi::String("nodes"), std::move(arr));
+    }
+    return obj;
   }
 
-  void Load(dmlc::JSONReader* reader) {
+  void Load(ffi::json::Object obj) {
     int bitmask = 0;
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "name") {
-        reader->Read(&name);
-        bitmask |= 1;
-      } else if (key == "nodes") {
-        reader->Read(&nodes);
-        bitmask |= 2;
+    if (auto it = obj.find(ffi::String("name")); it != obj.end()) {
+      name = std::string((*it).second.cast<ffi::String>());
+      bitmask |= 1;
+    }
+    if (auto it = obj.find(ffi::String("nodes")); it != obj.end()) {
+      auto arr = (*it).second.cast<ffi::json::Array>();
+      nodes.clear();
+      nodes.reserve(arr.size());
+      for (const auto& elem : arr) {
+        JsonWeightJoint item;
+        item.Load(elem.cast<ffi::json::Object>());
+        nodes.push_back(std::move(item));
       }
+      bitmask |= 2;
     }
     ICHECK_EQ(bitmask, 1 | 2) << "name and nodes should be given";
   }
diff --git a/src/contrib/msc/core/ir/graph_builder.h b/src/contrib/msc/core/ir/graph_builder.h
index c86801352f..88e90c6df2 100644
--- a/src/contrib/msc/core/ir/graph_builder.h
+++ b/src/contrib/msc/core/ir/graph_builder.h
@@ -24,7 +24,7 @@
 #ifndef TVM_CONTRIB_MSC_CORE_IR_GRAPH_BUILDER_H_
 #define TVM_CONTRIB_MSC_CORE_IR_GRAPH_BUILDER_H_
 
-#include <dmlc/json.h>
+#include <tvm/ffi/extra/json.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/relax/expr.h>
 #include <tvm/relax/expr_functor.h>
@@ -68,42 +68,61 @@ struct MSCRBuildConfig {
   std::vector<std::string> output_aliases;
   std::unordered_map<std::string, std::vector<std::string>> input_types;
 
-  void LoadInputTypes(dmlc::JSONReader* reader) {
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      std::vector<std::string> types;
-      reader->Read(&types);
-      input_types[key] = types;
+  void LoadInputTypes(ffi::json::Object obj) {
+    namespace json = ::tvm::ffi::json;
+    for (const auto& kv : obj) {
+      auto arr = kv.second.cast<json::Array>();
+      std::vector<std::string> vec;
+      vec.reserve(arr.size());
+      for (const auto& elem : arr) {
+        vec.push_back(std::string(elem.cast<ffi::String>()));
+      }
+      input_types[std::string(kv.first.cast<ffi::String>())] = std::move(vec);
     }
   }
 
-  void Load(dmlc::JSONReader* reader) {
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "prune_graph") {
-        reader->Read(&prune_graph);
-      } else if (key == "use_var_name") {
-        reader->Read(&use_var_name);
-      } else if (key == "float_precision") {
-        reader->Read(&float_precision);
-      } else if (key == "byoc_entry") {
-        reader->Read(&byoc_entry);
-      } else if (key == "sort_by") {
-        reader->Read(&sort_by);
-      } else if (key == "target") {
-        reader->Read(&target);
-      } else if (key == "graph_name") {
-        reader->Read(&graph_name);
-      } else if (key == "input_aliases") {
-        reader->Read(&input_aliases);
-      } else if (key == "output_aliases") {
-        reader->Read(&output_aliases);
-      } else if (key == "input_types") {
-        this->LoadInputTypes(reader);
+  void Load(ffi::json::Object obj) {
+    namespace json = ::tvm::ffi::json;
+    if (auto it = obj.find(ffi::String("prune_graph")); it != obj.end()) {
+      prune_graph = (*it).second.cast<bool>();
+    }
+    if (auto it = obj.find(ffi::String("use_var_name")); it != obj.end()) {
+      use_var_name = (*it).second.cast<bool>();
+    }
+    if (auto it = obj.find(ffi::String("float_precision")); it != obj.end()) {
+      float_precision = static_cast<int>((*it).second.cast<int64_t>());
+    }
+    if (auto it = obj.find(ffi::String("byoc_entry")); it != obj.end()) {
+      byoc_entry = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("sort_by")); it != obj.end()) {
+      sort_by = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("target")); it != obj.end()) {
+      target = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("graph_name")); it != obj.end()) {
+      graph_name = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("input_aliases")); it != obj.end()) {
+      auto arr = (*it).second.cast<json::Array>();
+      input_aliases.clear();
+      input_aliases.reserve(arr.size());
+      for (const auto& elem : arr) {
+        input_aliases.push_back(std::string(elem.cast<ffi::String>()));
       }
     }
+    if (auto it = obj.find(ffi::String("output_aliases")); it != obj.end()) {
+      auto arr = (*it).second.cast<json::Array>();
+      output_aliases.clear();
+      output_aliases.reserve(arr.size());
+      for (const auto& elem : arr) {
+        output_aliases.push_back(std::string(elem.cast<ffi::String>()));
+      }
+    }
+    if (auto it = obj.find(ffi::String("input_types")); it != obj.end()) {
+      LoadInputTypes((*it).second.cast<ffi::json::Object>());
+    }
   }
 };
 
@@ -267,9 +286,8 @@ class GraphBuilder : public ExprVisitor {
       : ExprVisitor() {
     ref_module_ = ref_module;
     if (options.size() > 0) {
-      std::istringstream is(options);
-      dmlc::JSONReader reader(&is);
-      reader.Read(&config_);
+      namespace json = ::tvm::ffi::json;
+      config_.Load(json::Parse(options).cast<json::Object>());
     }
     name_ = config_.graph_name.size() > 0 ? ffi::String(config_.graph_name) : name;
     if (config_.byoc_entry.size() > 0) {
diff --git a/src/contrib/msc/core/ir/plugin.cc b/src/contrib/msc/core/ir/plugin.cc
index 1ff3a8dc8d..e0f816f5ca 100644
--- a/src/contrib/msc/core/ir/plugin.cc
+++ b/src/contrib/msc/core/ir/plugin.cc
@@ -35,6 +35,8 @@ namespace tvm {
 namespace contrib {
 namespace msc {
 
+namespace json = ::tvm::ffi::json;
+
 PluginAttr::PluginAttr(const ffi::String& name, const ffi::String& type,
                        const ffi::String& default_value, const ffi::String& describe) {
   ObjectPtr<PluginAttrNode> n = ffi::make_object<PluginAttrNode>();
@@ -74,10 +76,9 @@ void PluginAttrNode::FromJson(const JsonPluginAttr& j_attr) {
 }
 
 void PluginAttrNode::FromJson(const std::string& json_str) {
-  std::istringstream is(json_str);
-  dmlc::JSONReader reader(&is);
+  auto parsed = json::Parse(json_str);
   JsonPluginAttr j_attr;
-  reader.Read(&j_attr);
+  j_attr.Load(parsed.cast<json::Object>());
   FromJson(j_attr);
 }
 
@@ -123,10 +124,9 @@ void PluginTensorNode::FromJson(const JsonPluginTensor& j_tensor) {
 }
 
 void PluginTensorNode::FromJson(const std::string& json_str) {
-  std::istringstream is(json_str);
-  dmlc::JSONReader reader(&is);
+  auto parsed = json::Parse(json_str);
   JsonPluginTensor j_tensor;
-  reader.Read(&j_tensor);
+  j_tensor.Load(parsed.cast<json::Object>());
   FromJson(j_tensor);
 }
 
@@ -173,10 +173,9 @@ void PluginExternNode::FromJson(const JsonPluginExtern& j_extern) {
 }
 
 void PluginExternNode::FromJson(const std::string& json_str) {
-  std::istringstream is(json_str);
-  dmlc::JSONReader reader(&is);
+  auto parsed = json::Parse(json_str);
   JsonPluginExtern j_extern;
-  reader.Read(&j_extern);
+  j_extern.Load(parsed.cast<json::Object>());
   FromJson(j_extern);
 }
 
@@ -277,10 +276,9 @@ void PluginNode::FromJson(const JsonPlugin& j_plugin) {
 }
 
 void PluginNode::FromJson(const std::string& json_str) {
-  std::istringstream is(json_str);
-  dmlc::JSONReader reader(&is);
+  auto parsed = json::Parse(json_str);
   JsonPlugin j_plugin;
-  reader.Read(&j_plugin);
+  j_plugin.Load(parsed.cast<json::Object>());
   FromJson(j_plugin);
 }
 
diff --git a/src/contrib/msc/core/ir/plugin.h b/src/contrib/msc/core/ir/plugin.h
index 838b3948bf..74ce371125 100644
--- a/src/contrib/msc/core/ir/plugin.h
+++ b/src/contrib/msc/core/ir/plugin.h
@@ -24,7 +24,7 @@
 #ifndef TVM_CONTRIB_MSC_CORE_IR_PLUGIN_H_
 #define TVM_CONTRIB_MSC_CORE_IR_PLUGIN_H_
 
-#include <dmlc/json.h>
+#include <tvm/ffi/extra/json.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/s_tir/data_layout.h>
 
@@ -48,31 +48,30 @@ struct JsonPluginAttr {
   std::string default_value;
   std::string describe;
 
-  void Save(dmlc::JSONWriter* writer) const {
-    writer->BeginObject();
-    writer->WriteObjectKeyValue("name", name);
-    writer->WriteObjectKeyValue("type", type);
-    writer->WriteObjectKeyValue("default_value", default_value);
-    writer->WriteObjectKeyValue("describe", describe);
-    writer->EndObject();
+  ffi::json::Value SaveToJSON() const {
+    ffi::json::Object obj;
+    obj.Set(ffi::String("name"), ffi::String(name));
+    obj.Set(ffi::String("type"), ffi::String(type));
+    obj.Set(ffi::String("default_value"), ffi::String(default_value));
+    obj.Set(ffi::String("describe"), ffi::String(describe));
+    return obj;
   }
 
-  void Load(dmlc::JSONReader* reader) {
+  void Load(ffi::json::Object obj) {
     int bitmask = 0;
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "name") {
-        reader->Read(&name);
-        bitmask |= 1;
-      } else if (key == "type") {
-        reader->Read(&type);
-        bitmask |= 2;
-      } else if (key == "default_value") {
-        reader->Read(&default_value);
-      } else if (key == "describe") {
-        reader->Read(&describe);
-      }
+    if (auto it = obj.find(ffi::String("name")); it != obj.end()) {
+      name = std::string((*it).second.cast<ffi::String>());
+      bitmask |= 1;
+    }
+    if (auto it = obj.find(ffi::String("type")); it != obj.end()) {
+      type = std::string((*it).second.cast<ffi::String>());
+      bitmask |= 2;
+    }
+    if (auto it = obj.find(ffi::String("default_value")); it != obj.end()) {
+      default_value = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("describe")); it != obj.end()) {
+      describe = std::string((*it).second.cast<ffi::String>());
     }
     ICHECK_EQ(bitmask, 1 | 2) << "name and type should be given for plugin attr";
     if (describe.size() == 0) {
@@ -91,33 +90,33 @@ struct JsonPluginTensor {
   std::string device{"default"};
   std::string describe;
 
-  void Save(dmlc::JSONWriter* writer) const {
-    writer->BeginObject();
-    writer->WriteObjectKeyValue("name", name);
-    writer->WriteObjectKeyValue("dtype", dtype);
-    writer->WriteObjectKeyValue("ndim", ndim);
-    writer->WriteObjectKeyValue("device", device);
-    writer->WriteObjectKeyValue("describe", describe);
-    writer->EndObject();
+  ffi::json::Value SaveToJSON() const {
+    ffi::json::Object obj;
+    obj.Set(ffi::String("name"), ffi::String(name));
+    obj.Set(ffi::String("dtype"), ffi::String(dtype));
+    obj.Set(ffi::String("ndim"), ndim);
+    obj.Set(ffi::String("device"), ffi::String(device));
+    obj.Set(ffi::String("describe"), ffi::String(describe));
+    return obj;
   }
 
-  void Load(dmlc::JSONReader* reader) {
+  void Load(ffi::json::Object obj) {
     int bitmask = 0;
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "name") {
-        reader->Read(&name);
-        bitmask |= 1;
-      } else if (key == "dtype") {
-        reader->Read(&dtype);
-      } else if (key == "ndim") {
-        reader->Read(&ndim);
-      } else if (key == "device") {
-        reader->Read(&device);
-      } else if (key == "describe") {
-        reader->Read(&describe);
-      }
+    if (auto it = obj.find(ffi::String("name")); it != obj.end()) {
+      name = std::string((*it).second.cast<ffi::String>());
+      bitmask |= 1;
+    }
+    if (auto it = obj.find(ffi::String("dtype")); it != obj.end()) {
+      dtype = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("ndim")); it != obj.end()) {
+      ndim = (*it).second.cast<int64_t>();
+    }
+    if (auto it = obj.find(ffi::String("device")); it != obj.end()) {
+      device = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("describe")); it != obj.end()) {
+      describe = std::string((*it).second.cast<ffi::String>());
     }
     ICHECK_EQ(bitmask, 1) << "name should be given for plugin tensor";
     if (describe.size() == 0) {
@@ -136,33 +135,33 @@ struct JsonPluginExtern {
   std::string lib;
   std::string describe;
 
-  void Save(dmlc::JSONWriter* writer) const {
-    writer->BeginObject();
-    writer->WriteObjectKeyValue("name", name);
-    writer->WriteObjectKeyValue("header", header);
-    writer->WriteObjectKeyValue("source", source);
-    writer->WriteObjectKeyValue("lib", lib);
-    writer->WriteObjectKeyValue("describe", describe);
-    writer->EndObject();
+  ffi::json::Value SaveToJSON() const {
+    ffi::json::Object obj;
+    obj.Set(ffi::String("name"), ffi::String(name));
+    obj.Set(ffi::String("header"), ffi::String(header));
+    obj.Set(ffi::String("source"), ffi::String(source));
+    obj.Set(ffi::String("lib"), ffi::String(lib));
+    obj.Set(ffi::String("describe"), ffi::String(describe));
+    return obj;
   }
 
-  void Load(dmlc::JSONReader* reader) {
+  void Load(ffi::json::Object obj) {
     int bitmask = 0;
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "name") {
-        reader->Read(&name);
-        bitmask |= 1;
-      } else if (key == "header") {
-        reader->Read(&header);
-      } else if (key == "source") {
-        reader->Read(&source);
-      } else if (key == "lib") {
-        reader->Read(&lib);
-      } else if (key == "describe") {
-        reader->Read(&describe);
-      }
+    if (auto it = obj.find(ffi::String("name")); it != obj.end()) {
+      name = std::string((*it).second.cast<ffi::String>());
+      bitmask |= 1;
+    }
+    if (auto it = obj.find(ffi::String("header")); it != obj.end()) {
+      header = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("source")); it != obj.end()) {
+      source = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("lib")); it != obj.end()) {
+      lib = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("describe")); it != obj.end()) {
+      describe = std::string((*it).second.cast<ffi::String>());
     }
     ICHECK_EQ(bitmask, 1) << "name should be given for plugin extern";
     if (describe.size() == 0) {
@@ -186,49 +185,154 @@ struct JsonPlugin {
   std::unordered_map<std::string, std::vector<std::string>> support_dtypes;
   std::unordered_map<std::string, std::string> options;
 
-  void Save(dmlc::JSONWriter* writer) const {
-    writer->BeginObject();
-    writer->WriteObjectKeyValue("name", name);
-    writer->WriteObjectKeyValue("version", version);
-    writer->WriteObjectKeyValue("describe", describe);
-    writer->WriteObjectKeyValue("attrs", attrs);
-    writer->WriteObjectKeyValue("inputs", inputs);
-    writer->WriteObjectKeyValue("outputs", outputs);
-    writer->WriteObjectKeyValue("buffers", buffers);
-    writer->WriteObjectKeyValue("externs", externs);
-    writer->WriteObjectKeyValue("support_dtypes", support_dtypes);
-    writer->WriteObjectKeyValue("options", options);
-    writer->EndObject();
+  ffi::json::Value SaveToJSON() const {
+    ffi::json::Object obj;
+    obj.Set(ffi::String("name"), ffi::String(name));
+    obj.Set(ffi::String("version"), ffi::String(version));
+    obj.Set(ffi::String("describe"), ffi::String(describe));
+    {
+      ffi::json::Array arr;
+      for (const auto& item : attrs) {
+        arr.push_back(item.SaveToJSON());
+      }
+      obj.Set(ffi::String("attrs"), std::move(arr));
+    }
+    {
+      ffi::json::Array arr;
+      for (const auto& item : inputs) {
+        arr.push_back(item.SaveToJSON());
+      }
+      obj.Set(ffi::String("inputs"), std::move(arr));
+    }
+    {
+      ffi::json::Array arr;
+      for (const auto& item : outputs) {
+        arr.push_back(item.SaveToJSON());
+      }
+      obj.Set(ffi::String("outputs"), std::move(arr));
+    }
+    {
+      ffi::json::Array arr;
+      for (const auto& item : buffers) {
+        arr.push_back(item.SaveToJSON());
+      }
+      obj.Set(ffi::String("buffers"), std::move(arr));
+    }
+    {
+      ffi::json::Object inner;
+      for (const auto& kv : externs) {
+        inner.Set(ffi::String(kv.first), kv.second.SaveToJSON());
+      }
+      obj.Set(ffi::String("externs"), std::move(inner));
+    }
+    // support_dtypes: map<string, vector<string>>
+    {
+      ffi::json::Object sd_obj;
+      for (const auto& kv : support_dtypes) {
+        ffi::json::Array arr;
+        for (const auto& s : kv.second) {
+          arr.push_back(ffi::String(s));
+        }
+        sd_obj.Set(ffi::String(kv.first), std::move(arr));
+      }
+      obj.Set(ffi::String("support_dtypes"), std::move(sd_obj));
+    }
+    {
+      ffi::json::Object inner;
+      for (const auto& kv : options) {
+        inner.Set(ffi::String(kv.first), ffi::String(kv.second));
+      }
+      obj.Set(ffi::String("options"), std::move(inner));
+    }
+    return obj;
   }
 
-  void Load(dmlc::JSONReader* reader) {
+  void Load(ffi::json::Object obj) {
     int bitmask = 0;
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "name") {
-        reader->Read(&name);
-        bitmask |= 1;
-      } else if (key == "version") {
-        reader->Read(&version);
-      } else if (key == "describe") {
-        reader->Read(&describe);
-      } else if (key == "attrs") {
-        reader->Read(&attrs);
-      } else if (key == "inputs") {
-        reader->Read(&inputs);
-        bitmask |= 2;
-      } else if (key == "outputs") {
-        reader->Read(&outputs);
-        bitmask |= 4;
-      } else if (key == "buffers") {
-        reader->Read(&buffers);
-      } else if (key == "externs") {
-        reader->Read(&externs);
-      } else if (key == "support_dtypes") {
-        reader->Read(&support_dtypes);
-      } else if (key == "options") {
-        reader->Read(&options);
+    if (auto it = obj.find(ffi::String("name")); it != obj.end()) {
+      name = std::string((*it).second.cast<ffi::String>());
+      bitmask |= 1;
+    }
+    if (auto it = obj.find(ffi::String("version")); it != obj.end()) {
+      version = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("describe")); it != obj.end()) {
+      describe = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("attrs")); it != obj.end()) {
+      auto arr = (*it).second.cast<ffi::json::Array>();
+      attrs.clear();
+      attrs.reserve(arr.size());
+      for (const auto& elem : arr) {
+        JsonPluginAttr item;
+        item.Load(elem.cast<ffi::json::Object>());
+        attrs.push_back(std::move(item));
+      }
+    }
+    if (auto it = obj.find(ffi::String("inputs")); it != obj.end()) {
+      auto arr = (*it).second.cast<ffi::json::Array>();
+      inputs.clear();
+      inputs.reserve(arr.size());
+      for (const auto& elem : arr) {
+        JsonPluginTensor item;
+        item.Load(elem.cast<ffi::json::Object>());
+        inputs.push_back(std::move(item));
+      }
+      bitmask |= 2;
+    }
+    if (auto it = obj.find(ffi::String("outputs")); it != obj.end()) {
+      auto arr = (*it).second.cast<ffi::json::Array>();
+      outputs.clear();
+      outputs.reserve(arr.size());
+      for (const auto& elem : arr) {
+        JsonPluginTensor item;
+        item.Load(elem.cast<ffi::json::Object>());
+        outputs.push_back(std::move(item));
+      }
+      bitmask |= 4;
+    }
+    if (auto it = obj.find(ffi::String("buffers")); it != obj.end()) {
+      auto arr = (*it).second.cast<ffi::json::Array>();
+      buffers.clear();
+      buffers.reserve(arr.size());
+      for (const auto& elem : arr) {
+        JsonPluginTensor item;
+        item.Load(elem.cast<ffi::json::Object>());
+        buffers.push_back(std::move(item));
+      }
+    }
+    if (auto it = obj.find(ffi::String("externs")); it != obj.end()) {
+      auto inner = (*it).second.cast<ffi::json::Object>();
+      externs.clear();
+      for (const auto& kv : inner) {
+        JsonPluginExtern item;
+        item.Load(kv.second.cast<ffi::json::Object>());
+        externs[std::string(kv.first.cast<ffi::String>())] = std::move(item);
+      }
+    }
+    // support_dtypes: map<string, vector<string>>
+    {
+      auto it = obj.find(ffi::String("support_dtypes"));
+      if (it != obj.end()) {
+        auto inner = (*it).second.cast<ffi::json::Object>();
+        support_dtypes.clear();
+        for (const auto& kv : inner) {
+          auto arr = kv.second.cast<ffi::json::Array>();
+          std::vector<std::string> vec;
+          vec.reserve(arr.size());
+          for (const auto& elem : arr) {
+            vec.push_back(std::string(elem.cast<ffi::String>()));
+          }
+          support_dtypes[std::string(kv.first.cast<ffi::String>())] = std::move(vec);
+        }
+      }
+    }
+    if (auto it = obj.find(ffi::String("options")); it != obj.end()) {
+      auto inner = (*it).second.cast<ffi::json::Object>();
+      options.clear();
+      for (const auto& kv : inner) {
+        options[std::string(kv.first.cast<ffi::String>())] =
+            std::string(kv.second.cast<ffi::String>());
       }
     }
     ICHECK_EQ(bitmask, 1 | 2 | 4) << "name, inputs and outputs should be given for plugin";
diff --git a/src/contrib/msc/core/printer/msc_base_printer.h b/src/contrib/msc/core/printer/msc_base_printer.h
index 10dafb54c2..96981f02b7 100644
--- a/src/contrib/msc/core/printer/msc_base_printer.h
+++ b/src/contrib/msc/core/printer/msc_base_printer.h
@@ -24,7 +24,7 @@
 #ifndef TVM_CONTRIB_MSC_CORE_PRINTER_MSC_BASE_PRINTER_H_
 #define TVM_CONTRIB_MSC_CORE_PRINTER_MSC_BASE_PRINTER_H_
 
-#include <dmlc/json.h>
+#include <tvm/ffi/extra/json.h>
 #include <tvm/script/printer/doc.h>
 
 #include <string>
@@ -47,21 +47,18 @@ struct MSCPrinterConfig {
   size_t float_precision{6};
   std::string indent_space{"  "};
   std::string separator{", "};
-  void Load(dmlc::JSONReader* reader) {
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "indent") {
-        reader->Read(&indent);
-      } else if (key == "float_precision") {
-        reader->Read(&float_precision);
-      } else if (key == "indent_space") {
-        reader->Read(&indent_space);
-      } else if (key == "separator") {
-        reader->Read(&separator);
-      } else {
-        LOG(FATAL) << "Do not support config " << key << " in printer";
-      }
+  void Load(ffi::json::Object obj) {
+    if (auto it = obj.find(ffi::String("indent")); it != obj.end()) {
+      indent = static_cast<size_t>((*it).second.cast<int64_t>());
+    }
+    if (auto it = obj.find(ffi::String("float_precision")); it != obj.end()) {
+      float_precision = static_cast<size_t>((*it).second.cast<int64_t>());
+    }
+    if (auto it = obj.find(ffi::String("indent_space")); it != obj.end()) {
+      indent_space = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("separator")); it != obj.end()) {
+      separator = std::string((*it).second.cast<ffi::String>());
     }
   }
 };
@@ -78,9 +75,8 @@ class MSCBasePrinter {
    */
   explicit MSCBasePrinter(const std::string& options = "") {
     if (options.size() > 0) {
-      std::istringstream is(options);
-      dmlc::JSONReader reader(&is);
-      reader.Read(&config_);
+      namespace json = ::tvm::ffi::json;
+      config_.Load(json::Parse(options).cast<json::Object>());
     }
     indent_ = config_.indent;
   }
diff --git a/src/contrib/msc/core/transform/set_expr_name.cc b/src/contrib/msc/core/transform/set_expr_name.cc
index d0231afedb..73c8f9d8e8 100644
--- a/src/contrib/msc/core/transform/set_expr_name.cc
+++ b/src/contrib/msc/core/transform/set_expr_name.cc
@@ -27,6 +27,8 @@
 #include <tvm/relax/expr_functor.h>
 #include <tvm/relax/transform.h>
 
+#include <set>
+
 #include "../utils.h"
 
 namespace tvm {
diff --git a/src/contrib/msc/framework/tensorflow/codegen_utils.h b/src/contrib/msc/framework/tensorflow/codegen_utils.h
index 4c250f1060..bdce8dc0e3 100644
--- a/src/contrib/msc/framework/tensorflow/codegen_utils.h
+++ b/src/contrib/msc/framework/tensorflow/codegen_utils.h
@@ -43,13 +43,7 @@ class TFV1CodeGenHelper : public BaseCodeGenHelper {};
  */
 struct TensorflowCodeGenConfig {
   CODEGEN_CONFIG_MEMBERS
-  void Load(dmlc::JSONReader* reader) {
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      CODEGEN_CONFIG_PARSE
-    }
-  }
+  void Load(ffi::json::Object obj) { CODEGEN_CONFIG_PARSE }
 };
 
 }  // namespace msc
diff --git a/src/contrib/msc/framework/tensorflow/config.h b/src/contrib/msc/framework/tensorflow/config.h
index 2b6e3dc966..0a1497a1d1 100644
--- a/src/contrib/msc/framework/tensorflow/config.h
+++ b/src/contrib/msc/framework/tensorflow/config.h
@@ -38,16 +38,11 @@ namespace msc {
 struct TensorflowCodeGenConfig {
   bool is_training{false};
   CODEGEN_CONFIG_MEMBERS
-  void Load(dmlc::JSONReader* reader) {
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "is_training") {
-        reader->Read(&is_training);
-      } else {
-        CODEGEN_CONFIG_PARSE
-      }
+  void Load(ffi::json::Object obj) {
+    if (auto it = obj.find(ffi::String("is_training")); it != obj.end()) {
+      is_training = (*it).second.cast<bool>();
     }
+    CODEGEN_CONFIG_PARSE
   }
 };
 
diff --git a/src/contrib/msc/framework/tensorrt/codegen_utils.h b/src/contrib/msc/framework/tensorrt/codegen_utils.h
index 3a16e668fe..b06d92dcd8 100644
--- a/src/contrib/msc/framework/tensorrt/codegen_utils.h
+++ b/src/contrib/msc/framework/tensorrt/codegen_utils.h
@@ -92,36 +92,46 @@ struct TensorRTCodeGenConfig {
   std::string tensorrt_root{"/usr/local/cuda"};
   std::vector<std::string> extern_libs;
   CODEGEN_CONFIG_MEMBERS
-  void Load(dmlc::JSONReader* reader) {
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "log_level") {
-        reader->Read(&log_level);
-      } else if (key == "profile_level") {
-        reader->Read(&profile_level);
-      } else if (key == "test_iter") {
-        reader->Read(&test_iter);
-      } else if (key == "max_workspace") {
-        reader->Read(&max_workspace);
-      } else if (key == "cmake_version") {
-        reader->Read(&cmake_version);
-      } else if (key == "dataset") {
-        reader->Read(&dataset);
-      } else if (key == "range_file") {
-        reader->Read(&range_file);
-      } else if (key == "precision") {
-        reader->Read(&precision);
-      } else if (key == "precision_mode") {
-        reader->Read(&precision_mode);
-      } else if (key == "tensorrt_root") {
-        reader->Read(&tensorrt_root);
-      } else if (key == "extern_libs") {
-        reader->Read(&extern_libs);
-      } else {
-        CODEGEN_CONFIG_PARSE
+  void Load(ffi::json::Object obj) {
+    if (auto it = obj.find(ffi::String("log_level")); it != obj.end()) {
+      log_level = static_cast<int>((*it).second.cast<int64_t>());
+    }
+    if (auto it = obj.find(ffi::String("profile_level")); it != obj.end()) {
+      profile_level = static_cast<int>((*it).second.cast<int64_t>());
+    }
+    if (auto it = obj.find(ffi::String("test_iter")); it != obj.end()) {
+      test_iter = static_cast<int>((*it).second.cast<int64_t>());
+    }
+    if (auto it = obj.find(ffi::String("max_workspace")); it != obj.end()) {
+      max_workspace = static_cast<size_t>((*it).second.cast<int64_t>());
+    }
+    if (auto it = obj.find(ffi::String("cmake_version")); it != obj.end()) {
+      cmake_version = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("dataset")); it != obj.end()) {
+      dataset = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("range_file")); it != obj.end()) {
+      range_file = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("precision")); it != obj.end()) {
+      precision = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("precision_mode")); it != obj.end()) {
+      precision_mode = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("tensorrt_root")); it != obj.end()) {
+      tensorrt_root = std::string((*it).second.cast<ffi::String>());
+    }
+    if (auto it = obj.find(ffi::String("extern_libs")); it != obj.end()) {
+      auto arr = (*it).second.cast<::tvm::ffi::json::Array>();
+      extern_libs.clear();
+      extern_libs.reserve(arr.size());
+      for (const auto& elem : arr) {
+        extern_libs.push_back(std::string(elem.cast<ffi::String>()));
       }
     }
+    CODEGEN_CONFIG_PARSE
   }
 };
 
diff --git a/src/contrib/msc/framework/tensorrt/transform_tensorrt.cc b/src/contrib/msc/framework/tensorrt/transform_tensorrt.cc
index 8b20d59574..ef58e31972 100644
--- a/src/contrib/msc/framework/tensorrt/transform_tensorrt.cc
+++ b/src/contrib/msc/framework/tensorrt/transform_tensorrt.cc
@@ -22,7 +22,7 @@
  * \brief Pass for transform the function to tensorrt.
  */
 
-#include <dmlc/json.h>
+#include <tvm/ffi/extra/json.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/relax/attrs/sorting.h>
 #include <tvm/relax/expr.h>
@@ -43,16 +43,17 @@ struct TensorRTTransConfig {
   bool linear_to_conv{true};
   std::vector<size_t> version{0, 0, 0};
 
-  void Load(dmlc::JSONReader* reader) {
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "linear_to_conv") {
-        reader->Read(&linear_to_conv);
-      } else if (key == "version") {
-        reader->Read(&version);
-      } else {
-        LOG(FATAL) << "Do not support key " << key;
+  void Load(ffi::json::Object obj) {
+    namespace json = ::tvm::ffi::json;
+    if (auto it = obj.find(ffi::String("linear_to_conv")); it != obj.end()) {
+      linear_to_conv = (*it).second.cast<bool>();
+    }
+    if (auto it = obj.find(ffi::String("version")); it != obj.end()) {
+      auto arr = (*it).second.cast<json::Array>();
+      version.clear();
+      version.reserve(arr.size());
+      for (const auto& elem : arr) {
+        version.push_back(static_cast<size_t>(elem.cast<int64_t>()));
       }
     }
   }
@@ -61,9 +62,8 @@ struct TensorRTTransConfig {
 const TensorRTTransConfig ParseConfig(const ffi::String& config_str) {
   TensorRTTransConfig config;
   if (config_str.size() > 0) {
-    std::istringstream is(config_str);
-    dmlc::JSONReader reader(&is);
-    reader.Read(&config);
+    namespace json = ::tvm::ffi::json;
+    config.Load(json::Parse(std::string(config_str)).cast<json::Object>());
   }
   return config;
 }
diff --git a/src/contrib/msc/framework/torch/codegen_utils.h b/src/contrib/msc/framework/torch/codegen_utils.h
index 13dee306e9..8a76729dbc 100644
--- a/src/contrib/msc/framework/torch/codegen_utils.h
+++ b/src/contrib/msc/framework/torch/codegen_utils.h
@@ -54,13 +54,7 @@ class TorchCodeGenHelper : public BaseCodeGenHelper {
  */
 struct TorchCodeGenConfig {
   CODEGEN_CONFIG_MEMBERS
-  void Load(dmlc::JSONReader* reader) {
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      CODEGEN_CONFIG_PARSE
-    }
-  }
+  void Load(ffi::json::Object obj) { CODEGEN_CONFIG_PARSE }
 };
 
 }  // namespace msc
diff --git a/src/contrib/msc/framework/torch/config.h b/src/contrib/msc/framework/torch/config.h
index 3258b1bed2..b5a18e123a 100644
--- a/src/contrib/msc/framework/torch/config.h
+++ b/src/contrib/msc/framework/torch/config.h
@@ -38,16 +38,11 @@ namespace msc {
 struct TorchCodeGenConfig {
   bool is_training{false};
   CODEGEN_CONFIG_MEMBERS
-  void Load(dmlc::JSONReader* reader) {
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "is_training") {
-        reader->Read(&is_training);
-      } else {
-        CODEGEN_CONFIG_PARSE
-      }
+  void Load(ffi::json::Object obj) {
+    if (auto it = obj.find(ffi::String("is_training")); it != obj.end()) {
+      is_training = (*it).second.cast<bool>();
     }
+    CODEGEN_CONFIG_PARSE
   }
 };
 
diff --git a/src/contrib/msc/framework/tvm/codegen_utils.h b/src/contrib/msc/framework/tvm/codegen_utils.h
index ab502c7fd5..9e822cc97c 100644
--- a/src/contrib/msc/framework/tvm/codegen_utils.h
+++ b/src/contrib/msc/framework/tvm/codegen_utils.h
@@ -45,18 +45,14 @@ struct RelaxCodeGenConfig {
   bool explicit_name{true};
   bool from_relay{false};
   CODEGEN_CONFIG_MEMBERS
-  void Load(dmlc::JSONReader* reader) {
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "explicit_name") {
-        reader->Read(&explicit_name);
-      } else if (key == "from_relay") {
-        reader->Read(&from_relay);
-      } else {
-        CODEGEN_CONFIG_PARSE
-      }
+  void Load(ffi::json::Object obj) {
+    if (auto it = obj.find(ffi::String("explicit_name")); it != obj.end()) {
+      explicit_name = (*it).second.cast<bool>();
     }
+    if (auto it = obj.find(ffi::String("from_relay")); it != obj.end()) {
+      from_relay = (*it).second.cast<bool>();
+    }
+    CODEGEN_CONFIG_PARSE
   }
 };
 
diff --git a/src/contrib/msc/framework/tvm/config.h b/src/contrib/msc/framework/tvm/config.h
index 5f91f1c3f5..3426730f68 100644
--- a/src/contrib/msc/framework/tvm/config.h
+++ b/src/contrib/msc/framework/tvm/config.h
@@ -39,18 +39,14 @@ struct RelaxCodeGenConfig {
   bool explicit_name{true};
   bool from_relay{false};
   CODEGEN_CONFIG_MEMBERS
-  void Load(dmlc::JSONReader* reader) {
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "explicit_name") {
-        reader->Read(&explicit_name);
-      } else if (key == "from_relay") {
-        reader->Read(&from_relay);
-      } else {
-        CODEGEN_CONFIG_PARSE
-      }
+  void Load(ffi::json::Object obj) {
+    if (auto it = obj.find(ffi::String("explicit_name")); it != obj.end()) {
+      explicit_name = (*it).second.cast<bool>();
     }
+    if (auto it = obj.find(ffi::String("from_relay")); it != obj.end()) {
+      from_relay = (*it).second.cast<bool>();
+    }
+    CODEGEN_CONFIG_PARSE
   }
 };
 
diff --git a/src/contrib/msc/plugin/base_codegen.h b/src/contrib/msc/plugin/base_codegen.h
index fcb1f3982f..971719d04c 100644
--- a/src/contrib/msc/plugin/base_codegen.h
+++ b/src/contrib/msc/plugin/base_codegen.h
@@ -24,7 +24,7 @@
 #ifndef TVM_CONTRIB_MSC_PLUGIN_BASE_CODEGEN_H_
 #define TVM_CONTRIB_MSC_PLUGIN_BASE_CODEGEN_H_
 
-#include <dmlc/json.h>
+#include <tvm/ffi/extra/json.h>
 #include <tvm/script/printer/doc.h>
 
 #include <memory>
@@ -57,9 +57,8 @@ class BasePluginCodeGen {
   explicit BasePluginCodeGen(const std::string& config = "") {
     config_.reset(new ConfigType());
     if (config.size() > 0) {
-      std::istringstream is(config);
-      dmlc::JSONReader reader(&is);
-      reader.Read(config_.get());
+      namespace json = ::tvm::ffi::json;
+      config_->Load(json::Parse(config).cast<json::Object>());
     }
   }
 
diff --git a/src/contrib/msc/plugin/codegen_utils.h b/src/contrib/msc/plugin/codegen_utils.h
index e61a0944ae..9fd9a0e941 100644
--- a/src/contrib/msc/plugin/codegen_utils.h
+++ b/src/contrib/msc/plugin/codegen_utils.h
@@ -24,6 +24,8 @@
 #ifndef TVM_CONTRIB_MSC_PLUGIN_CODEGEN_UTILS_H_
 #define TVM_CONTRIB_MSC_PLUGIN_CODEGEN_UTILS_H_
 
+#include <tvm/ffi/extra/json.h>
+
 #include <string>
 #include <unordered_map>
 #include <vector>
@@ -44,29 +46,62 @@ namespace msc {
   std::unordered_map<std::string, std::string> flags; \
   std::unordered_map<std::string, std::string> ops_info;
 
-#define PLUGIN_CODEGEN_CONFIG_PARSE             \
-  if (key == "need_convert") {                  \
-    reader->Read(&need_convert);                \
-  } else if (key == "with_runtime") {           \
-    reader->Read(&with_runtime);                \
-  } else if (key == "cmake_version") {          \
-    reader->Read(&cmake_version);               \
-  } else if (key == "project_name") {           \
-    reader->Read(&project_name);                \
-  } else if (key == "install_dir") {            \
-    reader->Read(&install_dir);                 \
-  } else if (key == "version") {                \
-    reader->Read(&version);                     \
-  } else if (key == "includes") {               \
-    reader->Read(&includes);                    \
-  } else if (key == "libs") {                   \
-    reader->Read(&libs);                        \
-  } else if (key == "flags") {                  \
-    reader->Read(&flags);                       \
-  } else if (key == "ops_info") {               \
-    reader->Read(&ops_info);                    \
-  } else {                                      \
-    LOG(FATAL) << "Do not support key " << key; \
+#define PLUGIN_CODEGEN_CONFIG_PARSE                                        \
+  namespace json = ::tvm::ffi::json;                                       \
+  if (auto it = obj.find(ffi::String("need_convert")); it != obj.end()) {  \
+    need_convert = (*it).second.cast<bool>();                              \
+  }                                                                        \
+  if (auto it = obj.find(ffi::String("with_runtime")); it != obj.end()) {  \
+    with_runtime = (*it).second.cast<bool>();                              \
+  }                                                                        \
+  if (auto it = obj.find(ffi::String("cmake_version")); it != obj.end()) { \
+    cmake_version = std::string((*it).second.cast<ffi::String>());         \
+  }                                                                        \
+  if (auto it = obj.find(ffi::String("project_name")); it != obj.end()) {  \
+    project_name = std::string((*it).second.cast<ffi::String>());          \
+  }                                                                        \
+  if (auto it = obj.find(ffi::String("install_dir")); it != obj.end()) {   \
+    install_dir = std::string((*it).second.cast<ffi::String>());           \
+  }                                                                        \
+  if (auto it = obj.find(ffi::String("version")); it != obj.end()) {       \
+    auto arr = (*it).second.cast<json::Array>();                           \
+    version.clear();                                                       \
+    version.reserve(arr.size());                                           \
+    for (const auto& elem : arr) {                                         \
+      version.push_back(static_cast<size_t>(elem.cast<int64_t>()));        \
+    }                                                                      \
+  }                                                                        \
+  if (auto it = obj.find(ffi::String("includes")); it != obj.end()) {      \
+    auto arr = (*it).second.cast<json::Array>();                           \
+    includes.clear();                                                      \
+    includes.reserve(arr.size());                                          \
+    for (const auto& elem : arr) {                                         \
+      includes.push_back(std::string(elem.cast<ffi::String>()));           \
+    }                                                                      \
+  }                                                                        \
+  if (auto it = obj.find(ffi::String("libs")); it != obj.end()) {          \
+    auto arr = (*it).second.cast<json::Array>();                           \
+    libs.clear();                                                          \
+    libs.reserve(arr.size());                                              \
+    for (const auto& elem : arr) {                                         \
+      libs.push_back(std::string(elem.cast<ffi::String>()));               \
+    }                                                                      \
+  }                                                                        \
+  if (auto it = obj.find(ffi::String("flags")); it != obj.end()) {         \
+    auto inner = (*it).second.cast<json::Object>();                        \
+    flags.clear();                                                         \
+    for (const auto& kv : inner) {                                         \
+      flags[std::string(kv.first.cast<ffi::String>())] =                   \
+          std::string(kv.second.cast<ffi::String>());                      \
+    }                                                                      \
+  }                                                                        \
+  if (auto it = obj.find(ffi::String("ops_info")); it != obj.end()) {      \
+    auto inner = (*it).second.cast<json::Object>();                        \
+    ops_info.clear();                                                      \
+    for (const auto& kv : inner) {                                         \
+      ops_info[std::string(kv.first.cast<ffi::String>())] =                \
+          std::string(kv.second.cast<ffi::String>());                      \
+    }                                                                      \
   }
 
 }  // namespace msc
diff --git a/src/contrib/msc/plugin/tensorrt_codegen.h b/src/contrib/msc/plugin/tensorrt_codegen.h
index c5b0e585a1..579d9a5422 100644
--- a/src/contrib/msc/plugin/tensorrt_codegen.h
+++ b/src/contrib/msc/plugin/tensorrt_codegen.h
@@ -40,16 +40,11 @@ namespace msc {
 struct TensorRTPluginCodeGenConfig {
   std::string tensorrt_root{"/usr/local/cuda"};
   PLUGIN_CODEGEN_CONFIG_MEMBERS
-  void Load(dmlc::JSONReader* reader) {
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "tensorrt_root") {
-        reader->Read(&tensorrt_root);
-      } else {
-        PLUGIN_CODEGEN_CONFIG_PARSE
-      }
+  void Load(ffi::json::Object obj) {
+    if (auto it = obj.find(ffi::String("tensorrt_root")); it != obj.end()) {
+      tensorrt_root = std::string((*it).second.cast<ffi::String>());
     }
+    PLUGIN_CODEGEN_CONFIG_PARSE
   }
 };
 
diff --git a/src/contrib/msc/plugin/torch_codegen.h b/src/contrib/msc/plugin/torch_codegen.h
index 1dae9134e7..2a5042392e 100644
--- a/src/contrib/msc/plugin/torch_codegen.h
+++ b/src/contrib/msc/plugin/torch_codegen.h
@@ -41,18 +41,14 @@ struct TorchPluginCodeGenConfig {
   bool is_training{false};
   std::string torch_prefix{"torch"};
   PLUGIN_CODEGEN_CONFIG_MEMBERS
-  void Load(dmlc::JSONReader* reader) {
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "is_training") {
-        reader->Read(&is_training);
-      } else if (key == "torch_prefix") {
-        reader->Read(&torch_prefix);
-      } else {
-        PLUGIN_CODEGEN_CONFIG_PARSE
-      }
+  void Load(ffi::json::Object obj) {
+    if (auto it = obj.find(ffi::String("is_training")); it != obj.end()) {
+      is_training = (*it).second.cast<bool>();
+    }
+    if (auto it = obj.find(ffi::String("torch_prefix")); it != obj.end()) {
+      torch_prefix = std::string((*it).second.cast<ffi::String>());
     }
+    PLUGIN_CODEGEN_CONFIG_PARSE
   }
 };
 
diff --git a/src/contrib/msc/plugin/tvm_codegen.cc b/src/contrib/msc/plugin/tvm_codegen.cc
index 7a109a1472..44f690334b 100644
--- a/src/contrib/msc/plugin/tvm_codegen.cc
+++ b/src/contrib/msc/plugin/tvm_codegen.cc
@@ -278,7 +278,6 @@ void TVMPluginCodeGen::CodeGenCmake(const std::set<ffi::String>& devices) {
       .line("find_library(TVM_LIB NAMES tvm HINTS ${TVM_ROOT}/build NO_DEFAULT_PATH)");
   ffi::Array<ffi::String> includes, libs;
   includes.push_back("${TVM_ROOT}/include");
-  includes.push_back("${TVM_ROOT}/3rdparty/dmlc-core/include");
   includes.push_back("${TVM_ROOT}/3rdparty/dlpack/include");
   includes.push_back("${TVM_ROOT}/3rdparty/compiler-rt");
   libs.push_back("${TVM_LIB}");
diff --git a/src/contrib/msc/plugin/tvm_codegen.h b/src/contrib/msc/plugin/tvm_codegen.h
index 926c516200..d2f6774296 100644
--- a/src/contrib/msc/plugin/tvm_codegen.h
+++ b/src/contrib/msc/plugin/tvm_codegen.h
@@ -41,18 +41,14 @@ struct TVMPluginCodeGenConfig {
   bool as_relay{false};
   std::string tvm_root{"tvm"};
   PLUGIN_CODEGEN_CONFIG_MEMBERS
-  void Load(dmlc::JSONReader* reader) {
-    std::string key;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
-      if (key == "as_relay") {
-        reader->Read(&as_relay);
-      } else if (key == "tvm_root") {
-        reader->Read(&tvm_root);
-      } else {
-        PLUGIN_CODEGEN_CONFIG_PARSE
-      }
+  void Load(ffi::json::Object obj) {
+    if (auto it = obj.find(ffi::String("as_relay")); it != obj.end()) {
+      as_relay = (*it).second.cast<bool>();
     }
+    if (auto it = obj.find(ffi::String("tvm_root")); it != obj.end()) {
+      tvm_root = std::string((*it).second.cast<ffi::String>());
+    }
+    PLUGIN_CODEGEN_CONFIG_PARSE
   }
 };
 
diff --git a/src/ir/instrument.cc b/src/ir/instrument.cc
index 011968d105..b969470caa 100644
--- a/src/ir/instrument.cc
+++ b/src/ir/instrument.cc
@@ -21,13 +21,13 @@
  * \file src/ir/instrument.cc
  * \brief Infrastructure for instrumentation.
  */
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/ir/instrument.h>
 #include <tvm/ir/transform.h>
 #include <tvm/node/repr_printer.h>
 
+#include <chrono>
 #include <stack>
 
 namespace tvm {
@@ -235,12 +235,15 @@ struct PassProfileThreadLocalEntry {
 };
 
 /*! \brief Thread local store to hold the pass profiling data. */
-typedef dmlc::ThreadLocalStore<PassProfileThreadLocalEntry> PassProfileThreadLocalStore;
+static PassProfileThreadLocalEntry* PassProfileThreadLocalStoreGet() {
+  static thread_local PassProfileThreadLocalEntry inst;
+  return &inst;
+}
 
 void PassProfile::EnterPass(ffi::String name) {
   PassProfile* cur = PassProfile::Current();
   cur->children.emplace_back(name);
-  PassProfileThreadLocalStore::Get()->profile_stack.push(&cur->children.back());
+  PassProfileThreadLocalStoreGet()->profile_stack.push(&cur->children.back());
 }
 
 void PassProfile::ExitPass() {
@@ -248,11 +251,11 @@ void PassProfile::ExitPass() {
   ICHECK_NE(cur->name, "root") << "mismatched enter/exit for pass profiling";
   cur->end = PassProfile::Clock::now();
   cur->duration = std::chrono::duration_cast<PassProfile::Duration>(cur->end - cur->start);
-  PassProfileThreadLocalStore::Get()->profile_stack.pop();
+  PassProfileThreadLocalStoreGet()->profile_stack.pop();
 }
 
 PassProfile* PassProfile::Current() {
-  PassProfileThreadLocalEntry* entry = PassProfileThreadLocalStore::Get();
+  PassProfileThreadLocalEntry* entry = PassProfileThreadLocalStoreGet();
   if (!entry->profile_stack.empty()) {
     return entry->profile_stack.top();
   } else {
@@ -261,7 +264,7 @@ PassProfile* PassProfile::Current() {
 }
 
 ffi::String RenderPassProfiles() {
-  PassProfileThreadLocalEntry* entry = PassProfileThreadLocalStore::Get();
+  PassProfileThreadLocalEntry* entry = PassProfileThreadLocalStoreGet();
   CHECK(entry->profile_stack.empty()) << "cannot print pass profile while still in a pass!";
 
   if (entry->root.children.empty()) {
@@ -326,7 +329,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
           PassProfile::ExitPass();
         };
 
-        auto exit_pass_ctx = []() { PassProfileThreadLocalStore::Get()->root.children.clear(); };
+        auto exit_pass_ctx = []() { PassProfileThreadLocalStoreGet()->root.children.clear(); };
 
         return BasePassInstrument("PassTimingInstrument",
                                   /* enter_pass_ctx */ nullptr, exit_pass_ctx,
diff --git a/src/ir/transform.cc b/src/ir/transform.cc
index 3cbf8a629f..c647706544 100644
--- a/src/ir/transform.cc
+++ b/src/ir/transform.cc
@@ -21,7 +21,6 @@
  * \file src/ir/transform.cc
  * \brief Infrastructure for transformation passes.
  */
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/ffi/rvalue_ref.h>
@@ -54,17 +53,20 @@ struct PassContextThreadLocalEntry {
 };
 
 /*! \brief Thread local store to hold the pass context. */
-typedef dmlc::ThreadLocalStore<PassContextThreadLocalEntry> PassContextThreadLocalStore;
+static PassContextThreadLocalEntry* PassContextThreadLocalStoreGet() {
+  static thread_local PassContextThreadLocalEntry inst;
+  return &inst;
+}
 
 void PassContext::EnterWithScope() {
   InstrumentEnterPassContext();
 
-  PassContextThreadLocalEntry* entry = PassContextThreadLocalStore::Get();
+  PassContextThreadLocalEntry* entry = PassContextThreadLocalStoreGet();
   entry->context_stack.push(*this);
 }
 
 void PassContext::ExitWithScope() {
-  PassContextThreadLocalEntry* entry = PassContextThreadLocalStore::Get();
+  PassContextThreadLocalEntry* entry = PassContextThreadLocalStoreGet();
   ICHECK(!entry->context_stack.empty());
   ICHECK(entry->context_stack.top().same_as(*this));
   entry->context_stack.pop();
@@ -73,7 +75,7 @@ void PassContext::ExitWithScope() {
 }
 
 PassContext PassContext::Current() {
-  PassContextThreadLocalEntry* entry = PassContextThreadLocalStore::Get();
+  PassContextThreadLocalEntry* entry = PassContextThreadLocalStoreGet();
   if (!entry->context_stack.empty()) {
     return entry->context_stack.top();
   } else {
diff --git a/src/node/structural_hash.cc b/src/node/structural_hash.cc
index aa02d097e9..6f7bca7cc5 100644
--- a/src/node/structural_hash.cc
+++ b/src/node/structural_hash.cc
@@ -19,7 +19,6 @@
 /*!
  * \file src/node/structural_hash.cc
  */
-#include <dmlc/memory_io.h>
 #include <tvm/ffi/extra/base64.h>
 #include <tvm/ffi/extra/module.h>
 #include <tvm/ffi/extra/structural_hash.h>
@@ -30,12 +29,14 @@
 #include <tvm/node/node.h>
 #include <tvm/node/structural_hash.h>
 #include <tvm/runtime/profiling.h>
+#include <tvm/support/io.h>
 #include <tvm/target/codegen.h>
 
 #include <algorithm>
 #include <unordered_map>
 
 #include "../support/base64.h"
+#include "../support/bytes_io.h"
 #include "../support/str_escape.h"
 #include "../support/utils.h"
 
@@ -63,15 +64,15 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   refl::TypeAttrDef<runtime::Tensor::Container>()
       .def("__data_to_json__",
            [](const runtime::Tensor::Container* node) {
-             std::string blob;
-             dmlc::MemoryStringStream mstrm(&blob);
+             std::string result;
+             support::BytesOutStream mstrm(&result);
              support::Base64OutStream b64strm(&mstrm);
              runtime::SaveDLTensor(&b64strm, node);
              b64strm.Finish();
-             return ffi::String(blob);
+             return ffi::String(std::move(result));
            })
       .def("__data_from_json__", [](const std::string& blob) {
-        dmlc::MemoryStringStream mstrm(const_cast<std::string*>(&blob));
+        support::BytesInStream mstrm(blob);
         support::Base64InStream b64strm(&mstrm);
         b64strm.InitPosition();
         runtime::Tensor temp;
diff --git a/src/relax/analysis/var2value.cc b/src/relax/analysis/var2value.cc
index 17a439b408..e71b14c96c 100644
--- a/src/relax/analysis/var2value.cc
+++ b/src/relax/analysis/var2value.cc
@@ -22,6 +22,8 @@
 #include <tvm/relax/expr.h>
 #include <tvm/relax/expr_functor.h>
 
+#include <map>
+
 namespace tvm {
 namespace relax {
 class Var2ValAnalysis : public relax::ExprVisitor {
diff --git a/src/relax/backend/contrib/clml/codegen.cc b/src/relax/backend/contrib/clml/codegen.cc
index 362621f423..5a113ae3b1 100644
--- a/src/relax/backend/contrib/clml/codegen.cc
+++ b/src/relax/backend/contrib/clml/codegen.cc
@@ -85,7 +85,7 @@ class CollectCLMLFromCompositeFunctionBody : public ExprVisitor {
   void SetGenericAttributes(const CallNode* call_node) {
     if (backend::IsOp(call_node, "relax.nn.relu")) {
       std::vector<std::string> activation_type = {"relu"};
-      std::vector<dmlc::any> act_attr;
+      std::vector<std::any> act_attr;
       act_attr.emplace_back(activation_type);
       node_->SetAttr("activation_type", act_attr);
     }
@@ -245,7 +245,7 @@ class OpenCLMLJSONSerializer : public JSONSerializer {
 
     if (nodes.bn) {
       const auto* bn_attr = nodes.bn->attrs.as<BatchNormAttrs>();
-      std::vector<dmlc::any> bn_any_attr;
+      std::vector<std::any> bn_any_attr;
       std::vector<std::string> bn_args = {
           std::to_string(bn_attr->axis), std::to_string(bn_attr->epsilon),
           std::to_string(bn_attr->center), std::to_string(bn_attr->scale)};
@@ -264,14 +264,14 @@ class OpenCLMLJSONSerializer : public JSONSerializer {
                                           std::to_string(p[6].as<IntImmNode>()->value),
                                           std::to_string(p[5].as<IntImmNode>()->value),
                                           std::to_string(p[7].as<IntImmNode>()->value)};
-      std::vector<dmlc::any> padding_attr;
+      std::vector<std::any> padding_attr;
       padding_attr.emplace_back(padding);
       json_node->SetAttr("padding", padding_attr);
     }
 
     if (nodes.activation) {
       std::vector<std::string> activation_type = {nodes.act_type};
-      std::vector<dmlc::any> act_attr;
+      std::vector<std::any> act_attr;
       act_attr.emplace_back(activation_type);
       json_node->SetAttr("activation_type", act_attr);
     }
@@ -285,7 +285,7 @@ class OpenCLMLJSONSerializer : public JSONSerializer {
       cfg = AttrsWithDefaultValues<OpenCLMLCompilerConfig>();
     }
     std::vector<std::string> clml_version = {std::to_string(cfg.value()->clml_version.IntValue())};
-    std::vector<dmlc::any> clml_version_attr;
+    std::vector<std::any> clml_version_attr;
     clml_version_attr.emplace_back(clml_version);
     node->SetAttr("clml_version", clml_version_attr);
   }
diff --git a/src/relax/backend/contrib/codegen_json/codegen_json.h b/src/relax/backend/contrib/codegen_json/codegen_json.h
index 6076de75bc..9dd211e00d 100644
--- a/src/relax/backend/contrib/codegen_json/codegen_json.h
+++ b/src/relax/backend/contrib/codegen_json/codegen_json.h
@@ -24,8 +24,6 @@
 #ifndef TVM_RELAX_BACKEND_CONTRIB_CODEGEN_JSON_CODEGEN_JSON_H_
 #define TVM_RELAX_BACKEND_CONTRIB_CODEGEN_JSON_CODEGEN_JSON_H_
 
-#include <dmlc/any.h>
-#include <dmlc/json.h>
 #include <tvm/ffi/reflection/accessor.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/relax/struct_info.h>
@@ -70,7 +68,7 @@ class OpAttrExtractor {
   }
 
   void SetNodeAttr(const char* key, const std::vector<std::string>& value) {
-    std::vector<dmlc::any> attr;
+    std::vector<std::any> attr;
     attr.emplace_back(value);
     node_->SetAttr(key, attr);
   }
@@ -222,10 +220,8 @@ class JSONSerializer : public relax::MemoizedExprTranslator<NodeEntries> {
 
   /*!\brief Return the generated json. */
   std::string GetJSON() {
-    std::ostringstream os;
-    dmlc::JSONWriter writer(&os);
-    Save(&writer);
-    return os.str();
+    namespace json = ::tvm::ffi::json;
+    return std::string(json::Stringify(SaveToJSON()));
   }
 
  protected:
@@ -271,11 +267,11 @@ class JSONSerializer : public relax::MemoizedExprTranslator<NodeEntries> {
       dtype.emplace_back(DType2String(tensor_sinfo->dtype));
       ret.push_back(JSONGraphNodeEntry(node_id, 0));
     }
-    std::vector<dmlc::any> shape_attrs;
+    std::vector<std::any> shape_attrs;
     shape_attrs.emplace_back(shape);
     node->SetAttr("shape", shape_attrs);
 
-    std::vector<dmlc::any> type_attrs;
+    std::vector<std::any> type_attrs;
     type_attrs.emplace_back(dtype);
     node->SetAttr("dtype", type_attrs);
     return ret;
@@ -292,7 +288,7 @@ class JSONSerializer : public relax::MemoizedExprTranslator<NodeEntries> {
       ICHECK(pattern.has_value());
       std::vector<std::string> values;
       values.push_back(pattern.value());
-      std::vector<dmlc::any> attr;
+      std::vector<std::any> attr;
       attr.emplace_back(values);
       node->SetAttr("PartitionedFromPattern", attr);
     }
@@ -424,12 +420,8 @@ class JSONSerializer : public relax::MemoizedExprTranslator<NodeEntries> {
     return {};
   }
 
-  /*!
-   * \brief Save to JSON graph
-   *
-   * \param writer A json writer
-   */
-  void Save(dmlc::JSONWriter* writer) {
+  ffi::json::Value SaveToJSON() {
+    namespace json = ::tvm::ffi::json;
     std::vector<size_t> arg_nodes;
     for (size_t i = 0; i < nodes_.size(); ++i) {
       auto node = nodes_[i];
@@ -443,12 +435,28 @@ class JSONSerializer : public relax::MemoizedExprTranslator<NodeEntries> {
       num_entry += node->GetNumOutput();
       node_row_ptr.push_back(num_entry);
     }
-    writer->BeginObject();
-    writer->WriteObjectKeyValue("nodes", nodes_);
-    writer->WriteObjectKeyValue("arg_nodes", arg_nodes);
-    writer->WriteObjectKeyValue("heads", heads_);
-    writer->WriteObjectKeyValue("node_row_ptr", node_row_ptr);
-    writer->EndObject();
+    json::Object obj;
+    // nodes
+    json::Array nodes_arr;
+    for (auto& n : nodes_) {
+      nodes_arr.push_back(n->SaveToJSON());
+    }
+    obj.Set(ffi::String("nodes"), std::move(nodes_arr));
+    // arg_nodes
+    json::Array arg_arr;
+    for (auto x : arg_nodes) arg_arr.push_back(static_cast<int64_t>(x));
+    obj.Set(ffi::String("arg_nodes"), std::move(arg_arr));
+    // heads
+    json::Array heads_arr;
+    for (const auto& h : heads_) {
+      heads_arr.push_back(h.SaveToJSON());
+    }
+    obj.Set(ffi::String("heads"), std::move(heads_arr));
+    // node_row_ptr
+    json::Array nrp_arr;
+    for (auto x : node_row_ptr) nrp_arr.push_back(static_cast<int64_t>(x));
+    obj.Set(ffi::String("node_row_ptr"), std::move(nrp_arr));
+    return obj;
   }
 
  private:
diff --git a/src/relax/backend/contrib/cublas/codegen.cc b/src/relax/backend/contrib/cublas/codegen.cc
index ab8336bfd5..10cf01bdc9 100644
--- a/src/relax/backend/contrib/cublas/codegen.cc
+++ b/src/relax/backend/contrib/cublas/codegen.cc
@@ -93,7 +93,7 @@ class CublasJSONSerializer : public JSONSerializer {
         }
 
         std::vector<std::string> dq_scale = {backend::to_str(alpha)};
-        std::vector<dmlc::any> dq_scale_attr;
+        std::vector<std::any> dq_scale_attr;
         dq_scale_attr.emplace_back(dq_scale);
         node->SetAttr("dq_scale", dq_scale_attr);
       }
diff --git a/src/relax/backend/contrib/cudnn/codegen.cc b/src/relax/backend/contrib/cudnn/codegen.cc
index 0773a627ac..d70f076bfa 100644
--- a/src/relax/backend/contrib/cudnn/codegen.cc
+++ b/src/relax/backend/contrib/cudnn/codegen.cc
@@ -118,13 +118,13 @@ class cuDNNJSONSerializer : public JSONSerializer {
     SetCallNodeAttribute(node, root_call);
 
     auto to_str_array = [](int val) {
-      return std::vector<dmlc::any>{std::vector<std::string>{std::to_string(val)}};
+      return std::vector<std::any>{std::vector<std::string>{std::to_string(val)}};
     };
     node->SetAttr("num_heads", to_str_array(num_heads));
     node->SetAttr("num_kv_heads", to_str_array(num_kv_heads));
     node->SetAttr("head_size", to_str_array(head_size));
     node->SetAttr("head_size_v", to_str_array(head_size_v));
-    node->SetAttr("layout", std::vector<dmlc::any>{std::vector<std::string>{layout}});
+    node->SetAttr("layout", std::vector<std::any>{std::vector<std::string>{layout}});
     return AddNode(node, ffi::GetRef<Expr>(call_node));
   }
 
diff --git a/src/relax/backend/contrib/nnapi/codegen.cc b/src/relax/backend/contrib/nnapi/codegen.cc
index 0ea05b9863..cbd54cfe06 100644
--- a/src/relax/backend/contrib/nnapi/codegen.cc
+++ b/src/relax/backend/contrib/nnapi/codegen.cc
@@ -60,7 +60,7 @@ class CollectFromCompositeFunctionBody : public ExprVisitor {
         axes.push_back(std::to_string(axis.IntValue()));
       }
 
-      std::vector<dmlc::any> axes_attr;
+      std::vector<std::any> axes_attr;
       axes_attr.emplace_back(axes);
       node_->SetAttr("axes", axes_attr);
     }
@@ -70,7 +70,7 @@ class CollectFromCompositeFunctionBody : public ExprVisitor {
     const auto* astype_attrs = call_node->attrs.as<AstypeAttrs>();
     ICHECK(astype_attrs);
 
-    std::vector<dmlc::any> dtype_attr;
+    std::vector<std::any> dtype_attr;
     auto dtype_str = runtime::DLDataTypeToString(astype_attrs->dtype);
     dtype_attr.emplace_back(std::vector<std::string>{dtype_str});
     node_->SetAttr("astype_dtype", dtype_attr);
@@ -87,14 +87,14 @@ class CollectFromCompositeFunctionBody : public ExprVisitor {
         axis.push_back(std::to_string(dim->value));
       }
 
-      std::vector<dmlc::any> axis_attr;
+      std::vector<std::any> axis_attr;
       axis_attr.emplace_back(axis);
       node_->SetAttr("axis", axis_attr);
     }
 
     {
       const std::vector<std::string> keepdims{mean_attrs->keepdims ? "1" : "0"};
-      std::vector<dmlc::any> keepdims_attr;
+      std::vector<std::any> keepdims_attr;
       keepdims_attr.emplace_back(keepdims);
       node_->SetAttr("keepdims", keepdims_attr);
     }
@@ -122,15 +122,15 @@ class CollectFromCompositeFunctionBody : public ExprVisitor {
     const int group_val = conv2d_attr->groups;
     groups.push_back(std::to_string(group_val));
 
-    std::vector<dmlc::any> strides_attr;
+    std::vector<std::any> strides_attr;
     strides_attr.emplace_back(strides);
     node_->SetAttr("strides", strides_attr);
 
-    std::vector<dmlc::any> padding_attr;
+    std::vector<std::any> padding_attr;
     padding_attr.emplace_back(padding);
     node_->SetAttr("padding", padding_attr);
 
-    std::vector<dmlc::any> group_attr;
+    std::vector<std::any> group_attr;
     group_attr.emplace_back(groups);
     node_->SetAttr("group", group_attr);
   }
@@ -159,15 +159,15 @@ class CollectFromCompositeFunctionBody : public ExprVisitor {
       pool_size.push_back(std::to_string(size));
     }
 
-    std::vector<dmlc::any> strides_attr;
+    std::vector<std::any> strides_attr;
     strides_attr.emplace_back(strides);
     node_->SetAttr("strides", strides_attr);
 
-    std::vector<dmlc::any> padding_attr;
+    std::vector<std::any> padding_attr;
     padding_attr.emplace_back(padding);
     node_->SetAttr("padding", padding_attr);
 
-    std::vector<dmlc::any> pooling_attr;
+    std::vector<std::any> pooling_attr;
     pooling_attr.emplace_back(pool_size);
     node_->SetAttr("pool_size", pooling_attr);
   }
diff --git a/src/relax/backend/contrib/tensorrt/codegen.cc b/src/relax/backend/contrib/tensorrt/codegen.cc
index 02d9a61a80..ed3d2adfcf 100644
--- a/src/relax/backend/contrib/tensorrt/codegen.cc
+++ b/src/relax/backend/contrib/tensorrt/codegen.cc
@@ -189,7 +189,7 @@ class TensorRTJSONSerializer : public JSONSerializer {
     std::vector<std::string> max_workspace_size = {std::to_string(cfg.value()->max_workspace_size)};
     std::vector<std::string> use_fp16 = {std::to_string(cfg.value()->use_fp16)};
     std::vector<std::string> use_uint8 = {std::to_string(cfg.value()->use_uint8)};
-    std::vector<dmlc::any> tensorrt_version_attr, use_implicit_batch_attr, max_workspace_size_attr,
+    std::vector<std::any> tensorrt_version_attr, use_implicit_batch_attr, max_workspace_size_attr,
         use_fp16_attr, use_uint8_attr;
     tensorrt_version_attr.emplace_back(tensorrt_version);
     use_implicit_batch_attr.emplace_back(use_implicit_batch);
diff --git a/src/relax/backend/vm/exec_builder.cc b/src/relax/backend/vm/exec_builder.cc
index a2fef22609..bcbff2ad84 100644
--- a/src/relax/backend/vm/exec_builder.cc
+++ b/src/relax/backend/vm/exec_builder.cc
@@ -24,6 +24,7 @@
 #include <tvm/relax/exec_builder.h>
 
 #include <sstream>
+#include <unordered_set>
 
 namespace tvm {
 namespace relax {
diff --git a/src/relax/ir/transform.cc b/src/relax/ir/transform.cc
index 39c7543613..281a60375b 100644
--- a/src/relax/ir/transform.cc
+++ b/src/relax/ir/transform.cc
@@ -21,7 +21,6 @@
  * \file relax/ir/transform.cc
  * \brief Relax specific transformation passes.
  */
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/ffi/rvalue_ref.h>
diff --git a/src/runtime/const_loader_module.cc b/src/runtime/const_loader_module.cc
index 918d551077..f372715d58 100644
--- a/src/runtime/const_loader_module.cc
+++ b/src/runtime/const_loader_module.cc
@@ -27,7 +27,6 @@
  * code and constants significantly reduces the efforts for handling external
  * codegen and runtimes.
  */
-#include <dmlc/memory_io.h>
 #include <tvm/ffi/container/array.h>
 #include <tvm/ffi/container/map.h>
 #include <tvm/ffi/extra/module.h>
@@ -35,9 +34,12 @@
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/ffi/string.h>
 #include <tvm/runtime/tensor.h>
+#include <tvm/support/io.h>
 
 #include <cstdint>
 
+#include "../support/bytes_io.h"
+
 namespace tvm {
 namespace runtime {
 
@@ -152,9 +154,8 @@ class ConstLoaderModuleObj : public ffi::ModuleObj {
   }
 
   ffi::Bytes SaveToBytes() const final {
-    std::string bytes_buffer;
-    dmlc::MemoryStringStream ms(&bytes_buffer);
-    dmlc::Stream* stream = &ms;
+    std::string result;
+    support::BytesOutStream stream(&result);
 
     std::vector<std::string> variables;
     std::vector<Tensor> const_var_tensor;
@@ -165,12 +166,12 @@ class ConstLoaderModuleObj : public ffi::ModuleObj {
     }
 
     // Save all variables in the function.
-    stream->Write(variables);
+    stream.Write(variables);
     // Save all constant data.
     uint64_t sz = static_cast<uint64_t>(const_var_tensor.size());
-    stream->Write(sz);
+    stream.Write(sz);
     for (uint64_t i = 0; i < sz; i++) {
-      const_var_tensor[i].Save(stream);
+      const_var_tensor[i].Save(&stream);
     }
 
     // Save the symbol to list of required constant variables mapping
@@ -181,31 +182,30 @@ class ConstLoaderModuleObj : public ffi::ModuleObj {
       const_vars.push_back(it.second);
     }
 
-    stream->Write(symbols);
+    stream.Write(symbols);
     sz = static_cast<uint64_t>(const_vars_by_symbol_.size());
-    stream->Write(sz);
+    stream.Write(sz);
     for (uint64_t i = 0; i < sz; i++) {
-      stream->Write(const_vars[i]);
+      stream.Write(const_vars[i]);
     }
-    return ffi::Bytes(bytes_buffer);
+    return ffi::Bytes(std::move(result));
   }
 
   static ffi::Module LoadFromBytes(const ffi::Bytes& bytes) {
-    dmlc::MemoryFixedSizeStream ms(const_cast<char*>(bytes.data()), bytes.size());
-    dmlc::Stream* stream = &ms;
+    support::BytesInStream stream(bytes);
 
     // Load the variables.
     std::vector<std::string> variables;
-    ICHECK(stream->Read(&variables)) << "Loading variable names failed";
+    ICHECK(stream.Read(&variables)) << "Loading variable names failed";
     uint64_t sz;
-    ICHECK(stream->Read(&sz, sizeof(sz))) << "Loading number of vars failed";
+    ICHECK(stream.Read(&sz, sizeof(sz))) << "Loading number of vars failed";
     ICHECK_EQ(static_cast<size_t>(sz), variables.size())
         << "The number of variables and ndarray counts must match";
     // Load the list of ndarray.
     std::vector<Tensor> arrays;
     for (uint64_t i = 0; i < sz; i++) {
       Tensor temp;
-      temp.Load(stream);
+      temp.Load(&stream);
       arrays.push_back(temp);
     }
 
@@ -217,13 +217,13 @@ class ConstLoaderModuleObj : public ffi::ModuleObj {
 
     // Load the symbol to list of required constant variables mapping
     std::vector<std::string> symbols;
-    ICHECK(stream->Read(&symbols)) << "Loading symbols failed";
-    ICHECK(stream->Read(&sz, sizeof(sz))) << "Loading number of symbols failed";
+    ICHECK(stream.Read(&symbols)) << "Loading symbols failed";
+    ICHECK(stream.Read(&sz, sizeof(sz))) << "Loading number of symbols failed";
     ICHECK_EQ(static_cast<size_t>(sz), symbols.size());
     std::vector<std::vector<std::string>> const_vars;
     for (uint64_t i = 0; i < sz; i++) {
       std::vector<std::string> vars;
-      ICHECK(stream->Read(&vars)) << "Loading const variables failed";
+      ICHECK(stream.Read(&vars)) << "Loading const variables failed";
       const_vars.push_back(vars);
     }
 
diff --git a/src/runtime/contrib/clml/clml_runtime.cc b/src/runtime/contrib/clml/clml_runtime.cc
index d1cf6b2808..cdcd8a6b04 100644
--- a/src/runtime/contrib/clml/clml_runtime.cc
+++ b/src/runtime/contrib/clml/clml_runtime.cc
@@ -23,10 +23,14 @@
  */
 #include "clml_runtime.h"
 
+#include <tvm/ffi/extra/json.h>
 #include <tvm/ffi/reflection/registry.h>
+#include <tvm/support/io.h>
 
 #include <unordered_map>
 
+#include "../../../support/bytes_io.h"
+
 #ifdef TVM_GRAPH_EXECUTOR_CLML
 #include "clml_memory_planner.h"
 #include "clml_utils.h"
@@ -133,9 +137,10 @@ CLMLWorkspace::CLMLWorkspace() {
   if (!(tuning_file = getenv("CLML_TUNING_CACHE"))) this->is_tuning_run = 0;
 }
 
-typedef dmlc::ThreadLocalStore<CLMLThreadEntry> CLMLThreadStore;
-
-CLMLThreadEntry* CLMLThreadEntry::ThreadLocal() { return CLMLThreadStore::Get(); }
+CLMLThreadEntry* CLMLThreadEntry::ThreadLocal() {
+  static thread_local CLMLThreadEntry inst;
+  return &inst;
+}
 #endif
 
 class CLMLRuntime : public JSONRuntimeBase {
@@ -234,21 +239,20 @@ class CLMLRuntime : public JSONRuntimeBase {
       std::vector<unsigned char> tune_buffer;
       std::string tune_blob;
       LoadBinaryFromFile(cws->tuning_file, &tune_blob);
-      dmlc::MemoryStringStream mstrm(const_cast<std::string*>(&tune_blob));
-      dmlc::Stream* strm = &mstrm;
+      support::BytesInStream strm(tune_blob);
 
       uint64_t header, reserve;
       std::string tune_symbol;
-      while (strm->Read(&header)) {
+      while (strm.Read(&header)) {
         if (header != kTVMCLMLTuningCacheMagic) break;
-        if (!strm->Read(&reserve)) break;
-        if (!strm->Read(&tune_symbol)) break;
+        if (!strm.Read(&reserve)) break;
+        if (!strm.Read(&tune_symbol)) break;
         if (tune_symbol == clml_symbol) {
-          strm->Read(&tune_buffer);
+          strm.Read(&tune_buffer);
           break;
         } else {
           std::vector<unsigned char> tmp_buf;
-          if (!strm->Read(&tmp_buf)) break;
+          if (!strm.Read(&tmp_buf)) break;
         }
       }
 
@@ -269,13 +273,12 @@ class CLMLRuntime : public JSONRuntimeBase {
       LOG(FATAL) << "Debugging over recordable queues is not supported yet. You may disable the "
                     "same by exporting CLML_DISABLE_RECORDABLE_QUEUE at runtime.";
     }
+    namespace json = ::tvm::ffi::json;
     cl_command_queue queue = CLML_QUEUE;
     ffi::Map<ffi::String, Tensor> dump_tensors;
-    std::ostringstream os;
-    dmlc::JSONWriter writer(&os);
-    writer.BeginObject();
+    json::Object obj;
 
-    writer.WriteObjectKeyValue("graph", graph_json_);
+    obj.Set(ffi::String("graph"), ffi::String(graph_json_));
 
     int op_index = 0;
     for (auto it = this->layer_.storage_map.begin(); it != this->layer_.storage_map.end(); it++) {
@@ -321,11 +324,10 @@ class CLMLRuntime : public JSONRuntimeBase {
       for (size_t i = 0; i < dump_bytes.size(); ++i) {
         oss << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(dump_bytes[i]);
       }
-      writer.WriteObjectKeyValue("tensors", oss.str());
+      obj.Set(ffi::String("tensors"), ffi::String(oss.str()));
     }
 
-    writer.EndObject();
-    return os.str();
+    return std::string(json::Stringify(obj));
   }
 
   void RunProfile(profiling::Profiler* prof) override {
@@ -944,18 +946,16 @@ class CLMLRuntime : public JSONRuntimeBase {
                 saved_cache.data(), &len_ret);
 
       std::string tune_str;
-      dmlc::MemoryStringStream mstrm(&tune_str);
-      dmlc::Stream* strm = &mstrm;
+      support::BytesOutStream strm(&tune_str);
       uint64_t header = kTVMCLMLTuningCacheMagic;
       uint64_t reserved = 0x0;
-      strm->Write(header);
-      strm->Write(reserved);
-      strm->Write(clml_symbol);
-      strm->Write(saved_cache);
-
+      strm.Write(header);
+      strm.Write(reserved);
+      strm.Write(clml_symbol);
+      strm.Write(saved_cache);
       std::ofstream fs(cws->tuning_file, std::ios::app | std::ios::binary);
       ICHECK(!fs.fail()) << "Cannot open " << cws->tuning_file;
-      fs.write(&tune_str[0], tune_str.length());
+      fs.write(tune_str.data(), tune_str.size());
       LOG_CLML << "CLML: Tuning cache dumped to:" << cws->tuning_file << " size"
                << tune_str.length() << " with tuning blob len " << saved_cache.size();
     }
diff --git a/src/runtime/contrib/coreml/coreml_runtime.mm b/src/runtime/contrib/coreml/coreml_runtime.mm
index c3ac6185d9..045ac482a6 100644
--- a/src/runtime/contrib/coreml/coreml_runtime.mm
+++ b/src/runtime/contrib/coreml/coreml_runtime.mm
@@ -23,6 +23,7 @@
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 
+#include "../../../support/bytes_io.h"
 #include "coreml_runtime.h"
 
 namespace tvm {
@@ -199,18 +200,17 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 }
 
 ffi::Bytes CoreMLRuntime::SaveToBytes() const {
-  std::string buffer;
-  dmlc::MemoryStringStream ms(&buffer);
-  dmlc::Stream* stream = &ms;
+  std::string result;
+  support::BytesOutStream stream(&result);
   NSURL* url = model_->url_;
   NSFileWrapper* dirWrapper = [[[NSFileWrapper alloc] initWithURL:url options:0
                                                             error:nil] autorelease];
   NSData* dirData = [dirWrapper serializedRepresentation];
-  stream->Write(symbol_);
-  stream->Write((uint64_t)[dirData length]);
-  stream->Write([dirData bytes], [dirData length]);
+  stream.Write(symbol_);
+  stream.Write((uint64_t)[dirData length]);
+  stream.Write([dirData bytes], [dirData length]);
   DLOG(INFO) << "Save " << symbol_ << " (" << [dirData length] << " bytes)";
-  return ffi::Bytes(buffer);
+  return ffi::Bytes(std::move(result));
 }
 
 /*!
@@ -221,8 +221,7 @@ ffi::Bytes CoreMLRuntime::SaveToBytes() const {
  * \return The created CoreML module.
  */
 ffi::Module CoreMLRuntimeLoadFromBytes(const ffi::Bytes& bytes) {
-  dmlc::MemoryFixedSizeStream ms(const_cast<char*>(bytes.data()), bytes.size());
-  dmlc::Stream* stream = &ms;
+  support::BytesInStream stream(bytes);
 
   NSString* tempBaseDir = NSTemporaryDirectory();
   if (tempBaseDir == nil) tempBaseDir = @"/tmp";
@@ -236,11 +235,11 @@ ffi::Module CoreMLRuntimeLoadFromBytes(const ffi::Bytes& bytes) {
   NSString* tempDir = [NSString stringWithUTF8String:result];
 
   std::string symbol;
-  stream->Read(&symbol);
+  stream.Read(&symbol);
   uint64_t length;
-  stream->Read(&length);
+  stream.Read(&length);
   void* ptr = new char[length];
-  stream->Read(ptr, length);
+  stream.Read(ptr, length);
   NSData* data = [[NSData alloc] initWithBytesNoCopy:ptr length:length];
   NSFileWrapper* dirWrapper =
       [[[NSFileWrapper alloc] initWithSerializedRepresentation:data] autorelease];
diff --git a/src/runtime/contrib/cublas/cublas_utils.cc b/src/runtime/contrib/cublas/cublas_utils.cc
index f5248fde7e..b18f7a14ef 100644
--- a/src/runtime/contrib/cublas/cublas_utils.cc
+++ b/src/runtime/contrib/cublas/cublas_utils.cc
@@ -22,7 +22,6 @@
  */
 #include "cublas_utils.h"
 
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/extra/c_env_api.h>
 #include <tvm/ffi/function.h>
 
@@ -40,10 +39,9 @@ CuBlasThreadEntry::~CuBlasThreadEntry() {
   }
 }
 
-typedef dmlc::ThreadLocalStore<CuBlasThreadEntry> CuBlasThreadStore;
-
 CuBlasThreadEntry* CuBlasThreadEntry::ThreadLocal(DLDevice curr_device) {
-  CuBlasThreadEntry* retval = CuBlasThreadStore::Get();
+  static thread_local CuBlasThreadEntry inst;
+  CuBlasThreadEntry* retval = &inst;
   cudaStream_t stream =
       static_cast<cudaStream_t>(TVMFFIEnvGetStream(curr_device.device_type, curr_device.device_id));
   CHECK_CUBLAS_ERROR(cublasSetStream(retval->handle, stream));
@@ -71,10 +69,9 @@ CuBlasLtThreadEntry::~CuBlasLtThreadEntry() {
   }
 }
 
-typedef dmlc::ThreadLocalStore<CuBlasLtThreadEntry> CuBlasLtThreadStore;
-
 CuBlasLtThreadEntry* CuBlasLtThreadEntry::ThreadLocal(DLDevice curr_device) {
-  return CuBlasLtThreadStore::Get();
+  static thread_local CuBlasLtThreadEntry inst;
+  return &inst;
 }
 
 }  // namespace contrib
diff --git a/src/runtime/contrib/cudnn/cudnn_json_runtime.cc b/src/runtime/contrib/cudnn/cudnn_json_runtime.cc
index cefa2957b6..48f2b912f8 100644
--- a/src/runtime/contrib/cudnn/cudnn_json_runtime.cc
+++ b/src/runtime/contrib/cudnn/cudnn_json_runtime.cc
@@ -231,6 +231,7 @@ class cuDNNJSONRuntime : public JSONRuntimeBase {
     };
 #else
     LOG(FATAL) << "Please build with CUDNN frontend to use attention op";
+    return nullptr;
 #endif
   }
 
diff --git a/src/runtime/contrib/cudnn/cudnn_utils.cc b/src/runtime/contrib/cudnn/cudnn_utils.cc
index b0e3af9efb..4a9581f259 100644
--- a/src/runtime/contrib/cudnn/cudnn_utils.cc
+++ b/src/runtime/contrib/cudnn/cudnn_utils.cc
@@ -23,7 +23,6 @@
 
 #include "cudnn_utils.h"
 
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/extra/c_env_api.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
@@ -121,10 +120,9 @@ CuDNNThreadEntry::CuDNNThreadEntry() {
 
 CuDNNThreadEntry::~CuDNNThreadEntry() {}
 
-typedef dmlc::ThreadLocalStore<CuDNNThreadEntry> CuDNNThreadStore;
-
 CuDNNThreadEntry* CuDNNThreadEntry::ThreadLocal(Device curr_device, bool check_exists) {
-  auto* res = CuDNNThreadStore::Get();
+  static thread_local CuDNNThreadEntry inst;
+  auto* res = &inst;
   if (check_exists) {
     ICHECK(res->exists()) << "CUDNN_STATUS_NOT_INITIALIZED";
   }
diff --git a/src/runtime/contrib/hipblas/hipblas_utils.cc b/src/runtime/contrib/hipblas/hipblas_utils.cc
index 17ed9a0d93..a7de7310ba 100644
--- a/src/runtime/contrib/hipblas/hipblas_utils.cc
+++ b/src/runtime/contrib/hipblas/hipblas_utils.cc
@@ -22,7 +22,6 @@
  */
 #include "hipblas_utils.h"
 
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/extra/c_env_api.h>
 #include <tvm/ffi/function.h>
 
@@ -40,10 +39,9 @@ HipBlasThreadEntry::~HipBlasThreadEntry() {
   }
 }
 
-typedef dmlc::ThreadLocalStore<HipBlasThreadEntry> HipBlasThreadStore;
-
 HipBlasThreadEntry* HipBlasThreadEntry::ThreadLocal(DLDevice curr_device) {
-  HipBlasThreadEntry* retval = HipBlasThreadStore::Get();
+  static thread_local HipBlasThreadEntry inst;
+  HipBlasThreadEntry* retval = &inst;
   TVMFFIStreamHandle stream = TVMFFIEnvGetStream(curr_device.device_type, curr_device.device_id);
   CHECK_HIPBLAS_ERROR(hipblasSetStream(retval->handle, static_cast<hipStream_t>(stream)));
   return retval;
@@ -70,10 +68,9 @@ HipBlasLtThreadEntry::~HipBlasLtThreadEntry() {
   }
 }
 
-typedef dmlc::ThreadLocalStore<HipBlasLtThreadEntry> HipBlasLtThreadStore;
-
 HipBlasLtThreadEntry* HipBlasLtThreadEntry::ThreadLocal(DLDevice curr_device) {
-  return HipBlasLtThreadStore::Get();
+  static thread_local HipBlasLtThreadEntry inst;
+  return &inst;
 }
 
 }  // namespace contrib
diff --git a/src/runtime/contrib/json/json_node.h b/src/runtime/contrib/json/json_node.h
index b0f2bb5821..7f36ee6620 100644
--- a/src/runtime/contrib/json/json_node.h
+++ b/src/runtime/contrib/json/json_node.h
@@ -26,11 +26,10 @@
 #define TVM_RUNTIME_CONTRIB_JSON_JSON_NODE_H_
 
 #include <dlpack/dlpack.h>
-#include <dmlc/any.h>
-#include <dmlc/json.h>
-#include <dmlc/memory_io.h>
+#include <tvm/ffi/extra/json.h>
 #include <tvm/runtime/data_type.h>
 
+#include <any>
 #include <cstdint>
 #include <cstdio>
 #include <memory>
@@ -43,7 +42,7 @@ namespace tvm {
 namespace runtime {
 namespace json {
 
-using JSONGraphAttrs = std::unordered_map<std::string, dmlc::any>;
+using JSONGraphAttrs = std::unordered_map<std::string, std::any>;
 
 /*!
  * \brief The node entry in the serialized json graph.
@@ -57,29 +56,24 @@ class JSONGraphNodeEntry {
 
   /*!
    * \brief Serialize a node entry.
-   * \param writer The json writer.
    */
-  void Save(dmlc::JSONWriter* writer) const {
-    writer->BeginArray();
-    writer->WriteArrayItem(id_);
-    writer->WriteArrayItem(index_);
-    writer->WriteArrayItem(version_);
-    writer->EndArray();
+  ffi::json::Value SaveToJSON() const {
+    ffi::json::Array arr;
+    arr.push_back(static_cast<int64_t>(id_));
+    arr.push_back(static_cast<int64_t>(index_));
+    arr.push_back(static_cast<int64_t>(version_));
+    return arr;
   }
 
   /*!
-   * \brief Deserialize the json string into a node entry.
-   * \param reader The json reader.
+   * \brief Deserialize the json array into a node entry.
    */
-  void Load(dmlc::JSONReader* reader) {
-    reader->BeginArray();
-    ICHECK(reader->NextArrayItem()) << "invalid json format";
-    reader->Read(&id_);
-    ICHECK(reader->NextArrayItem()) << "invalid json format";
-    reader->Read(&index_);
-    if (reader->NextArrayItem()) {
-      reader->Read(&version_);
-      ICHECK(!reader->NextArrayItem()) << "invalid json format";
+  void Load(ffi::json::Array arr) {
+    ICHECK_GE(arr.size(), 2) << "invalid json format";
+    id_ = static_cast<uint32_t>(arr[0].cast<int64_t>());
+    index_ = static_cast<uint32_t>(arr[1].cast<int64_t>());
+    if (arr.size() > 2) {
+      version_ = static_cast<uint32_t>(arr[2].cast<int64_t>());
     } else {
       version_ = 0;
     }
@@ -92,6 +86,85 @@ class JSONGraphNodeEntry {
   uint32_t version_;
 };
 
+namespace {
+
+template <typename T>
+bool SameType(const std::any& data) {
+  return data.type() == typeid(T);
+}
+
+ffi::json::Value AnyVecToJson(const std::vector<std::any>& data) {
+  ffi::json::Array arr;
+  for (const auto& v : data) {
+    if (SameType<std::string>(v)) {
+      arr.push_back(ffi::String(std::any_cast<std::string>(v)));
+    } else if (SameType<int>(v)) {
+      arr.push_back(static_cast<int64_t>(std::any_cast<int>(v)));
+    } else if (SameType<std::vector<size_t>>(v)) {
+      const auto& sv = std::any_cast<const std::vector<size_t>&>(v);
+      ffi::json::Array inner;
+      for (auto x : sv) inner.push_back(static_cast<int64_t>(x));
+      arr.push_back(std::move(inner));
+    } else if (SameType<std::vector<std::vector<int64_t>>>(v)) {
+      const auto& vv = std::any_cast<const std::vector<std::vector<int64_t>>&>(v);
+      ffi::json::Array inner;
+      for (const auto& row : vv) {
+        ffi::json::Array r;
+        for (auto x : row) r.push_back(x);
+        inner.push_back(std::move(r));
+      }
+      arr.push_back(std::move(inner));
+    } else if (SameType<std::vector<std::string>>(v)) {
+      const auto& sv = std::any_cast<const std::vector<std::string>&>(v);
+      ffi::json::Array inner;
+      for (const auto& s : sv) inner.push_back(ffi::String(s));
+      arr.push_back(std::move(inner));
+    } else {
+      LOG(FATAL) << "Not supported type in std::any vector";
+    }
+  }
+  return arr;
+}
+
+ffi::json::Value AttrsToJson(const std::unordered_map<std::string, std::any>& data) {
+  ffi::json::Object obj;
+  for (const auto& kv : data) {
+    const auto& k = kv.first;
+    const auto& v = kv.second;
+    if (SameType<std::string>(v)) {
+      obj.Set(ffi::String(k), ffi::String(std::any_cast<std::string>(v)));
+    } else if (SameType<int>(v)) {
+      obj.Set(ffi::String(k), static_cast<int64_t>(std::any_cast<int>(v)));
+    } else if (SameType<std::vector<size_t>>(v)) {
+      const auto& sv = std::any_cast<const std::vector<size_t>&>(v);
+      ffi::json::Array arr;
+      for (auto x : sv) arr.push_back(static_cast<int64_t>(x));
+      obj.Set(ffi::String(k), std::move(arr));
+    } else if (SameType<std::vector<std::vector<int64_t>>>(v)) {
+      const auto& vv = std::any_cast<const std::vector<std::vector<int64_t>>&>(v);
+      ffi::json::Array arr;
+      for (const auto& row : vv) {
+        ffi::json::Array r;
+        for (auto x : row) r.push_back(x);
+        arr.push_back(std::move(r));
+      }
+      obj.Set(ffi::String(k), std::move(arr));
+    } else if (SameType<std::vector<std::string>>(v)) {
+      const auto& sv = std::any_cast<const std::vector<std::string>&>(v);
+      ffi::json::Array arr;
+      for (const auto& s : sv) arr.push_back(ffi::String(s));
+      obj.Set(ffi::String(k), std::move(arr));
+    } else if (SameType<std::vector<std::any>>(v)) {
+      obj.Set(ffi::String(k), AnyVecToJson(std::any_cast<const std::vector<std::any>&>(v)));
+    } else {
+      LOG(FATAL) << "Not supported type in attrs";
+    }
+  }
+  return obj;
+}
+
+}  // namespace
+
 /*!
  * \brief The node of the serialized json graph. It includes an array of
  * entries.
@@ -111,79 +184,90 @@ class JSONGraphNode {
 
   /*!
    * \brief Serialize a node so that it can be saved to disk.
-   * \param writer The json writer.
    */
-  void Save(dmlc::JSONWriter* writer) {
-    writer->BeginObject();
-    writer->WriteObjectKeyValue("op", op_type_);
-    writer->WriteObjectKeyValue("name", name_);
+  ffi::json::Value SaveToJSON() {
     if (!inputs_.empty()) {
       SetAttr("num_inputs", std::to_string(inputs_.size()));
       SetAttr("num_outputs", std::to_string(num_outputs_));
-      writer->WriteObjectKeyValue("inputs", this->inputs_);
+    }
+    ffi::json::Object obj;
+    obj.Set(ffi::String("op"), ffi::String(op_type_));
+    obj.Set(ffi::String("name"), ffi::String(name_));
+    if (!inputs_.empty()) {
+      ffi::json::Array inputs_arr;
+      for (const auto& e : inputs_) {
+        inputs_arr.push_back(e.SaveToJSON());
+      }
+      obj.Set(ffi::String("inputs"), std::move(inputs_arr));
     }
     if (!attrs_.empty()) {
-      writer->WriteObjectKeyValue("attrs", attrs_);
+      obj.Set(ffi::String("attrs"), AttrsToJson(attrs_));
     }
-    writer->EndObject();
+    return obj;
   }
 
   /*!
-   * \brief Load the attribute of a node in the json string.
-   * \param reader The json reader.
+   * \brief Load the attribute of a node from a json object.
    */
-  void LoadAttrs(dmlc::JSONReader* reader) {
-    std::string key, value;
-    reader->BeginObject();
-    while (reader->NextObjectItem(&key)) {
+  void LoadAttrs(ffi::json::Object obj) {
+    for (const auto& kv : obj) {
+      std::string key = std::string(kv.first.cast<ffi::String>());
       if (key == "num_inputs") {
-        reader->Read(&value);
+        std::string value = std::string(kv.second.cast<ffi::String>());
         num_inputs_ = strtoul(value.c_str(), nullptr, 10);
       } else if (key == "num_outputs") {
-        reader->Read(&value);
+        std::string value = std::string(kv.second.cast<ffi::String>());
         num_outputs_ = strtoul(value.c_str(), nullptr, 10);
       } else if (key == "dtype") {
-        std::vector<std::string> tmp;
-        reader->BeginArray();
-        ICHECK(reader->NextArrayItem());
-        reader->Read(&tmp);
-        ICHECK(!reader->NextArrayItem());
-        for (const auto& it : tmp) {
-          dtype_.push_back(ffi::StringToDLDataType(it));
+        auto outer = kv.second.cast<ffi::json::Array>();
+        ICHECK_EQ(outer.size(), 1);
+        auto inner = outer[0].cast<ffi::json::Array>();
+        for (const auto& it : inner) {
+          dtype_.push_back(ffi::StringToDLDataType(std::string(it.cast<ffi::String>())));
         }
       } else if (key == "shape") {
-        reader->BeginArray();
-        ICHECK(reader->NextArrayItem());
-        reader->Read(&shape_);
-        ICHECK(!reader->NextArrayItem());
+        auto outer = kv.second.cast<ffi::json::Array>();
+        ICHECK_EQ(outer.size(), 1);
+        auto shapes_arr = outer[0].cast<ffi::json::Array>();
+        shape_.clear();
+        for (const auto& s : shapes_arr) {
+          auto row = s.cast<ffi::json::Array>();
+          std::vector<int64_t> shape_vec;
+          for (const auto& v : row) shape_vec.push_back(v.cast<int64_t>());
+          shape_.push_back(shape_vec);
+        }
       } else {
-        reader->BeginArray();
-        ICHECK(reader->NextArrayItem());
+        auto outer = kv.second.cast<ffi::json::Array>();
+        ICHECK_EQ(outer.size(), 1);
+        auto inner = outer[0].cast<ffi::json::Array>();
         std::vector<std::string> tmp;
-        reader->Read(&tmp);
+        for (const auto& v : inner) tmp.push_back(std::string(v.cast<ffi::String>()));
         attrs_[key] = tmp;
-        ICHECK(!reader->NextArrayItem());
       }
     }
     ICHECK_EQ(shape_.size(), dtype_.size());
   }
 
   /*!
-   * \brief Load a node in the json string.
-   * \param reader The json reader.
+   * \brief Load a node from a json object.
    */
-  void Load(dmlc::JSONReader* reader) {
-    reader->BeginObject();
-    std::string key;
-    while (reader->NextObjectItem(&key)) {
+  void Load(ffi::json::Object obj) {
+    for (const auto& kv : obj) {
+      std::string key = std::string(kv.first.cast<ffi::String>());
       if (key == "op") {
-        reader->Read(&op_type_);
+        op_type_ = std::string(kv.second.cast<ffi::String>());
       } else if (key == "name") {
-        reader->Read(&name_);
+        name_ = std::string(kv.second.cast<ffi::String>());
       } else if (key == "inputs") {
-        reader->Read(&inputs_);
+        auto arr = kv.second.cast<ffi::json::Array>();
+        inputs_.clear();
+        for (const auto& e : arr) {
+          JSONGraphNodeEntry entry;
+          entry.Load(e.cast<ffi::json::Array>());
+          inputs_.push_back(entry);
+        }
       } else if (key == "attr" || key == "attrs") {
-        this->LoadAttrs(reader);
+        this->LoadAttrs(kv.second.cast<ffi::json::Object>());
       } else {
         LOG(FATAL) << "Unknown key: " << key;
       }
@@ -257,7 +341,7 @@ class JSONGraphNode {
   template <typename T>
   T GetAttr(const std::string& key) const {
     ICHECK_GT(attrs_.count(key), 0U) << "Key: " << key << " is not found";
-    return dmlc::get<T>(attrs_.at(key));
+    return std::any_cast<T>(attrs_.at(key));
   }
 
   /*!
@@ -327,84 +411,4 @@ class JSONGraphNode {
 }  // namespace runtime
 }  // namespace tvm
 
-namespace dmlc {
-namespace json {
-template <typename T>
-inline bool SameType(const dmlc::any& data) {
-  return std::type_index(data.type()) == std::type_index(typeid(T));
-}
-
-template <>
-struct Handler<std::shared_ptr<tvm::runtime::json::JSONGraphNode>> {
-  inline static void Write(dmlc::JSONWriter* writer,
-                           const std::shared_ptr<tvm::runtime::json::JSONGraphNode>& data) {
-    data->Save(writer);
-  }
-
-  inline static void Read(dmlc::JSONReader* reader,
-                          std::shared_ptr<tvm::runtime::json::JSONGraphNode>* data) {
-    (*data)->Load(reader);
-  }
-};
-
-template <>
-struct Handler<std::unordered_map<std::string, dmlc::any>> {
-  inline static void Write(dmlc::JSONWriter* writer,
-                           const std::unordered_map<std::string, dmlc::any>& data) {
-    writer->BeginObject();
-    for (const auto& kv : data) {
-      auto k = kv.first;
-      const dmlc::any& v = kv.second;
-      if (SameType<std::string>(v)) {
-        writer->WriteObjectKeyValue(k, dmlc::get<std::string>(v));
-      } else if (SameType<int>(v)) {
-        writer->WriteObjectKeyValue(k, dmlc::get<int>(v));
-      } else if (SameType<std::vector<size_t>>(v)) {
-        writer->WriteObjectKeyValue(k, dmlc::get<std::vector<size_t>>(v));
-      } else if (SameType<std::vector<std::vector<int64_t>>>(v)) {
-        writer->WriteObjectKeyValue(k, dmlc::get<std::vector<std::vector<int64_t>>>(v));
-      } else if (SameType<std::vector<std::string>>(v)) {
-        writer->WriteObjectKeyValue(k, dmlc::get<std::vector<std::string>>(v));
-      } else if (SameType<std::vector<dmlc::any>>(v)) {
-        writer->WriteObjectKeyValue(k, dmlc::get<std::vector<dmlc::any>>(v));
-      } else {
-        LOG(FATAL) << "Not supported";
-      }
-    }
-    writer->EndObject();
-  }
-  inline static void Read(dmlc::JSONReader* reader,
-                          std::unordered_map<std::string, dmlc::any>* data) {
-    LOG(FATAL) << "Not implemented.";
-  }
-};
-
-template <>
-struct Handler<std::vector<dmlc::any>> {
-  inline static void Write(dmlc::JSONWriter* writer, const std::vector<dmlc::any>& data) {
-    writer->BeginArray();
-    for (const auto& v : data) {
-      if (SameType<std::string>(v)) {
-        writer->WriteArrayItem(dmlc::get<std::string>(v));
-      } else if (SameType<int>(v)) {
-        writer->WriteArrayItem(dmlc::get<int>(v));
-      } else if (SameType<std::vector<size_t>>(v)) {
-        writer->WriteArrayItem(dmlc::get<std::vector<size_t>>(v));
-      } else if (SameType<std::vector<std::vector<int64_t>>>(v)) {
-        writer->WriteArrayItem(dmlc::get<std::vector<std::vector<int64_t>>>(v));
-      } else if (SameType<std::vector<std::string>>(v)) {
-        writer->WriteArrayItem(dmlc::get<std::vector<std::string>>(v));
-      } else {
-        LOG(FATAL) << "Not supported";
-      }
-    }
-    writer->EndArray();
-  }
-  inline static void Read(dmlc::JSONReader* reader, std::vector<dmlc::any>* data) {
-    LOG(FATAL) << "Not implemented.";
-  }
-};
-}  // namespace json
-}  // namespace dmlc
-
 #endif  // TVM_RUNTIME_CONTRIB_JSON_JSON_NODE_H_
diff --git a/src/runtime/contrib/json/json_runtime.h b/src/runtime/contrib/json/json_runtime.h
index a8bb6c2608..ef41144fb9 100644
--- a/src/runtime/contrib/json/json_runtime.h
+++ b/src/runtime/contrib/json/json_runtime.h
@@ -25,11 +25,14 @@
 #ifndef TVM_RUNTIME_CONTRIB_JSON_JSON_RUNTIME_H_
 #define TVM_RUNTIME_CONTRIB_JSON_JSON_RUNTIME_H_
 
+#include <tvm/ffi/extra/json.h>
 #include <tvm/ffi/extra/module.h>
 #include <tvm/runtime/profiling.h>
 #include <tvm/runtime/tensor.h>
+#include <tvm/support/io.h>
 
 #include <cstddef>
+#include <mutex>
 #include <string>
 #include <tuple>
 #include <type_traits>
@@ -37,6 +40,7 @@
 #include <utility>
 #include <vector>
 
+#include "../../../support/bytes_io.h"
 #include "json_node.h"
 
 namespace tvm {
@@ -85,6 +89,7 @@ class JSONRuntimeBase : public ffi::ModuleObj {
    */
   virtual std::string DebugDump(void) {
     LOG(FATAL) << "Not expected to be here : Debug dump w/o support ?";
+    return "";
   }
 
   /*!
@@ -152,34 +157,32 @@ class JSONRuntimeBase : public ffi::ModuleObj {
   }
 
   ffi::Bytes SaveToBytes() const override {
-    std::string buffer;
-    dmlc::MemoryStringStream ms(&buffer);
-    dmlc::Stream* stream = &ms;
+    std::string result;
+    support::BytesOutStream stream(&result);
     // Save the symbol
-    stream->Write(symbol_name_);
+    stream.Write(symbol_name_);
     // Save the graph
-    stream->Write(graph_json_);
+    stream.Write(graph_json_);
     // Save the required const names
     std::vector<std::string> consts;
     for (const auto& it : const_names_) {
       consts.push_back(it);
     }
-    stream->Write(consts);
-    return ffi::Bytes(buffer);
+    stream.Write(consts);
+    return ffi::Bytes(std::move(result));
   }
 
   template <typename T,
             typename = typename std::enable_if<std::is_base_of<JSONRuntimeBase, T>::value>::type>
   static ffi::Module LoadFromBytes(const ffi::Bytes& bytes) {
-    dmlc::MemoryFixedSizeStream ms(const_cast<char*>(bytes.data()), bytes.size());
-    dmlc::Stream* stream = &ms;
+    support::BytesInStream stream(bytes);
     std::string symbol;
     std::string graph_json;
     std::vector<std::string> consts;
     // Load the symbol
-    ICHECK(stream->Read(&symbol)) << "Loading symbol name failed";
-    ICHECK(stream->Read(&graph_json)) << "Loading graph json failed";
-    ICHECK(stream->Read(&consts)) << "Loading the const name list failed";
+    ICHECK(stream.Read(&symbol)) << "Loading symbol name failed";
+    ICHECK(stream.Read(&graph_json)) << "Loading graph json failed";
+    ICHECK(stream.Read(&consts)) << "Loading the const name list failed";
     ffi::Array<ffi::String> const_names;
     for (const auto& it : consts) {
       const_names.push_back(it);
@@ -231,9 +234,9 @@ class JSONRuntimeBase : public ffi::ModuleObj {
    * \param graph_json The graph in the json format.
    */
   void LoadGraph(const std::string& graph_json) {
-    std::istringstream is(graph_json);
-    dmlc::JSONReader reader(&is);
-    this->Load(&reader);
+    namespace json = ::tvm::ffi::json;
+    auto root = json::Parse(graph_json).cast<json::Object>();
+    this->Load(root);
     std::vector<std::string> consts;
     for (size_t i = 0; i < input_nodes_.size(); i++) {
       uint32_t nid = input_nodes_[i];
@@ -277,21 +280,40 @@ class JSONRuntimeBase : public ffi::ModuleObj {
   }
 
   // Load the graph.
-  void Load(dmlc::JSONReader* reader) {
-    reader->BeginObject();
-    std::string key;
-    std::string symbol_;
-    while (reader->NextObjectItem(&key)) {
+  void Load(ffi::json::Object root) {
+    namespace json = ::tvm::ffi::json;
+    for (const auto& kv : root) {
+      std::string key = std::string(kv.first.cast<ffi::String>());
       if (key == "nodes") {
-        reader->Read(&nodes_);
+        auto arr = kv.second.cast<json::Array>();
+        nodes_.clear();
+        for (const auto& n : arr) {
+          JSONGraphNode node;
+          node.Load(n.cast<json::Object>());
+          nodes_.push_back(std::move(node));
+        }
       } else if (key == "arg_nodes") {
-        reader->Read(&input_nodes_);
+        auto arr = kv.second.cast<json::Array>();
+        input_nodes_.clear();
+        for (const auto& v : arr) {
+          input_nodes_.push_back(static_cast<uint32_t>(v.cast<int64_t>()));
+        }
       } else if (key == "node_row_ptr") {
-        reader->Read(&node_row_ptr_);
+        auto arr = kv.second.cast<json::Array>();
+        node_row_ptr_.clear();
+        for (const auto& v : arr) {
+          node_row_ptr_.push_back(static_cast<size_t>(v.cast<int64_t>()));
+        }
       } else if (key == "heads") {
-        reader->Read(&outputs_);
+        auto arr = kv.second.cast<json::Array>();
+        outputs_.clear();
+        for (const auto& e : arr) {
+          JSONGraphNodeEntry entry;
+          entry.Load(e.cast<ffi::json::Array>());
+          outputs_.push_back(entry);
+        }
       } else if (key == "symbol") {
-        reader->Read(&symbol_);
+        // ignored
       } else {
         LOG(FATAL) << "Unknown key: " << key;
       }
diff --git a/src/runtime/contrib/miopen/miopen_utils.cc b/src/runtime/contrib/miopen/miopen_utils.cc
index 617ea5aaf0..701350c82b 100644
--- a/src/runtime/contrib/miopen/miopen_utils.cc
+++ b/src/runtime/contrib/miopen/miopen_utils.cc
@@ -22,7 +22,6 @@
  */
 #include "miopen_utils.h"
 
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/function.h>
 
 #include <string>
@@ -51,11 +50,10 @@ MIOpenThreadEntry::MIOpenThreadEntry() {
 
 MIOpenThreadEntry::~MIOpenThreadEntry() { MIOPEN_CALL(miopenDestroy(handle)); }
 
-typedef dmlc::ThreadLocalStore<MIOpenThreadEntry> MIOpenThreadStore;
-
 MIOpenThreadEntry* MIOpenThreadEntry::ThreadLocal(Device curr_device) {
   // Need to update stream per fetch to avoid stream switching
-  MIOpenThreadEntry* res = MIOpenThreadStore::Get();
+  static thread_local MIOpenThreadEntry inst;
+  MIOpenThreadEntry* res = &inst;
   TVMFFIStreamHandle stream = TVMFFIEnvGetStream(curr_device.device_type, curr_device.device_id);
   MIOPEN_CALL(miopenSetStream(res->handle, stream));
   return res;
diff --git a/src/runtime/contrib/mps/mps_utils.h b/src/runtime/contrib/mps/mps_utils.h
index 1dd1a2c1e3..4d942bb267 100644
--- a/src/runtime/contrib/mps/mps_utils.h
+++ b/src/runtime/contrib/mps/mps_utils.h
@@ -25,7 +25,6 @@
 #define TVM_RUNTIME_CONTRIB_MPS_MPS_UTILS_H_
 
 #import <MetalPerformanceShaders/MetalPerformanceShaders.h>
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/function.h>
 #include <tvm/runtime/data_type.h>
 #include <tvm/runtime/device_api.h>
diff --git a/src/runtime/contrib/mps/mps_utils.mm b/src/runtime/contrib/mps/mps_utils.mm
index f5dfbdd530..e825bb93c7 100644
--- a/src/runtime/contrib/mps/mps_utils.mm
+++ b/src/runtime/contrib/mps/mps_utils.mm
@@ -83,9 +83,10 @@ MetalThreadEntry::~MetalThreadEntry() {
   }
 }
 
-typedef dmlc::ThreadLocalStore<MetalThreadEntry> MetalThreadStore;
-
-MetalThreadEntry* MetalThreadEntry::ThreadLocal() { return MetalThreadStore::Get(); }
+MetalThreadEntry* MetalThreadEntry::ThreadLocal() {
+  static thread_local MetalThreadEntry inst;
+  return &inst;
+}
 
 }  // namespace contrib
 }  // namespace tvm
diff --git a/src/runtime/contrib/mrvl/mrvl_hw_runtime.cc b/src/runtime/contrib/mrvl/mrvl_hw_runtime.cc
index bfa2e1889b..26a603c430 100644
--- a/src/runtime/contrib/mrvl/mrvl_hw_runtime.cc
+++ b/src/runtime/contrib/mrvl/mrvl_hw_runtime.cc
@@ -33,6 +33,7 @@
 #include <vector>
 
 #include "../../../../src/support/base64.h"
+#include "../../../support/bytes_io.h"
 #include "mrvl_base64.h"
 
 #define MRVL_LIBMLDPC_DEFAULT_PATH "/usr/lib/libmldpc.so"
@@ -246,35 +247,33 @@ class MarvellHardwareModuleNode : public ffi::ModuleObj {
   }
 
   virtual ffi::Bytes SaveToBytes() const {
-    std::string buffer;
-    dmlc::MemoryStringStream ms(&buffer);
-    dmlc::Stream* stream = &ms;
+    std::string result;
+    support::BytesOutStream stream(&result);
     // Save the symbol name and other data and serialize them to
     // binary format.
-    stream->Write(symbol_name_);
-    stream->Write(nodes_json_);
-    stream->Write(bin_code_);
-    stream->Write(num_inputs_);
-    stream->Write(num_outputs_);
-    stream->Write(run_arg.num_batches);
-    return ffi::Bytes(buffer);
+    stream.Write(symbol_name_);
+    stream.Write(nodes_json_);
+    stream.Write(bin_code_);
+    stream.Write(num_inputs_);
+    stream.Write(num_outputs_);
+    stream.Write(run_arg.num_batches);
+    return ffi::Bytes(std::move(result));
   }
 
   static ffi::Module LoadFromBytes(const ffi::Bytes& bytes) {
-    dmlc::MemoryFixedSizeStream ms(const_cast<char*>(bytes.data()), bytes.size());
-    dmlc::Stream* stream = &ms;
+    support::BytesInStream stream(bytes);
     std::string symbol_name;
     std::string nodes_json;
     std::string bin_code;
     int num_inputs, num_outputs, batch_size;
 
     // Load the symbol_name and other data to construct the module
-    ICHECK(stream->Read(&symbol_name)) << "Loading symbol name failed";
-    ICHECK(stream->Read(&nodes_json)) << "Loading nodes json failed";
-    ICHECK(stream->Read(&bin_code)) << "Loading binary code failed";
-    ICHECK(stream->Read(&num_inputs)) << "Loading num_inputs failed";
-    ICHECK(stream->Read(&num_outputs)) << "Loading num_outputs failed";
-    ICHECK(stream->Read(&batch_size)) << "Loading batch_size failed";
+    ICHECK(stream.Read(&symbol_name)) << "Loading symbol name failed";
+    ICHECK(stream.Read(&nodes_json)) << "Loading nodes json failed";
+    ICHECK(stream.Read(&bin_code)) << "Loading binary code failed";
+    ICHECK(stream.Read(&num_inputs)) << "Loading num_inputs failed";
+    ICHECK(stream.Read(&num_outputs)) << "Loading num_outputs failed";
+    ICHECK(stream.Read(&batch_size)) << "Loading batch_size failed";
     auto n = ffi::make_object<MarvellHardwareModuleNode>(symbol_name, nodes_json, bin_code,
                                                          num_inputs, num_outputs, batch_size);
     return ffi::Module(n);
diff --git a/src/runtime/contrib/mrvl/mrvl_runtime.cc b/src/runtime/contrib/mrvl/mrvl_runtime.cc
index 1a9ad8c478..bcc4bcc2e2 100644
--- a/src/runtime/contrib/mrvl/mrvl_runtime.cc
+++ b/src/runtime/contrib/mrvl/mrvl_runtime.cc
@@ -34,6 +34,7 @@
 #include <string>
 #include <vector>
 
+#include "../../../support/bytes_io.h"
 #include "../json/json_node.h"
 #include "mrvl_sw_runtime_lib.h"
 
@@ -89,29 +90,27 @@ class MarvellSimulatorModuleNode : public ffi::ModuleObj {
   }
 
   virtual ffi::Bytes SaveToBytes() const {
-    std::string buffer;
-    dmlc::MemoryStringStream ms(&buffer);
-    dmlc::Stream* stream = &ms;
+    std::string result;
+    support::BytesOutStream stream(&result);
     // Save the symbol name and other data and serialize them to
     // binary format.
-    stream->Write(symbol_name_);
-    stream->Write(nodes_json_);
-    stream->Write(bin_code_);
-    return ffi::Bytes(buffer);
+    stream.Write(symbol_name_);
+    stream.Write(nodes_json_);
+    stream.Write(bin_code_);
+    return ffi::Bytes(std::move(result));
   }
 
   static ffi::Module LoadFromBytes(const ffi::Bytes& bytes) {
-    dmlc::MemoryFixedSizeStream ms(const_cast<char*>(bytes.data()), bytes.size());
-    dmlc::Stream* stream = &ms;
+    support::BytesInStream stream(bytes);
     std::string symbol_name;
     std::string nodes_json;
     std::string bin_code;
     // Load the symbol_name and other data to construct the module
-    ICHECK(stream->Read(&symbol_name))
+    ICHECK(stream.Read(&symbol_name))
         << "Marvell-Compiler-ERROR-Internal::Loading symbol name failed";
-    ICHECK(stream->Read(&nodes_json))
+    ICHECK(stream.Read(&nodes_json))
         << "Marvell-Compiler-ERROR-Internal::Loading nodes json failed";
-    ICHECK(stream->Read(&bin_code)) << "Marvell-Compiler-ERROR-Internal::Loading bin code failed";
+    ICHECK(stream.Read(&bin_code)) << "Marvell-Compiler-ERROR-Internal::Loading bin code failed";
     auto n = ffi::make_object<MarvellSimulatorModuleNode>(symbol_name, nodes_json, bin_code);
     return ffi::Module(n);
   }
diff --git a/src/runtime/contrib/msc/tensorrt_runtime.cc b/src/runtime/contrib/msc/tensorrt_runtime.cc
index 91e291ce30..4fd2870d0d 100644
--- a/src/runtime/contrib/msc/tensorrt_runtime.cc
+++ b/src/runtime/contrib/msc/tensorrt_runtime.cc
@@ -22,7 +22,6 @@
  * \brief JSON runtime implementation for TensorRT.
  */
 
-#include <dmlc/parameter.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/tensor.h>
diff --git a/src/runtime/contrib/random/random.cc b/src/runtime/contrib/random/random.cc
index f444ab0740..1c75a8152a 100644
--- a/src/runtime/contrib/random/random.cc
+++ b/src/runtime/contrib/random/random.cc
@@ -20,7 +20,6 @@
 /*!
  * \file External random functions for tensor.
  */
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/data_type.h>
@@ -64,10 +63,9 @@ struct RandomThreadLocalEntry {
   static RandomThreadLocalEntry* ThreadLocal();
 };
 
-typedef dmlc::ThreadLocalStore<RandomThreadLocalEntry> RandomThreadLocalStore;
-
 RandomThreadLocalEntry* RandomThreadLocalEntry::ThreadLocal() {
-  return RandomThreadLocalStore::Get();
+  static thread_local RandomThreadLocalEntry inst;
+  return &inst;
 }
 
 TVM_FFI_STATIC_INIT_BLOCK() {
diff --git a/src/runtime/contrib/rocblas/rocblas.cc b/src/runtime/contrib/rocblas/rocblas.cc
index 73ec8c1b0f..62ae5b27a3 100644
--- a/src/runtime/contrib/rocblas/rocblas.cc
+++ b/src/runtime/contrib/rocblas/rocblas.cc
@@ -22,7 +22,6 @@
  */
 #include "rocblas.h"
 
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/data_type.h>
@@ -63,7 +62,10 @@ struct RocBlasThreadEntry {
   rocblas_handle handle;
 };  // RocBlasThreadEntry
 
-typedef dmlc::ThreadLocalStore<RocBlasThreadEntry> RocBlasThreadStore;
+static RocBlasThreadEntry* RocBlasThreadStoreGet() {
+  static thread_local RocBlasThreadEntry inst;
+  return &inst;
+}
 
 // matrix multiplication for row major
 TVM_FFI_STATIC_INIT_BLOCK() {
@@ -105,7 +107,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
             size_t ldb = transb ? K : N;
             size_t ldc = N;
 
-            CHECK_ROCBLAS_ERROR(rocblas_sgemm(RocBlasThreadStore::Get()->handle, roc_trans_B,
+            CHECK_ROCBLAS_ERROR(rocblas_sgemm(RocBlasThreadStoreGet()->handle, roc_trans_B,
                                               roc_trans_A, N, M, K, &alpha, B_ptr, ldb, A_ptr, lda,
                                               &beta, C_ptr, ldc));
           })
@@ -142,8 +144,8 @@ TVM_FFI_STATIC_INIT_BLOCK() {
         size_t ldc = N;
 
         CHECK_ROCBLAS_ERROR(rocblas_sgemm_strided_batched(
-            RocBlasThreadStore::Get()->handle, roc_trans_B, roc_trans_A, N, M, K, &alpha, B_ptr,
-            ldb, K * N, A_ptr, lda, M * K, &beta, C_ptr, ldc, M * N, batch_size));
+            RocBlasThreadStoreGet()->handle, roc_trans_B, roc_trans_A, N, M, K, &alpha, B_ptr, ldb,
+            K * N, A_ptr, lda, M * K, &beta, C_ptr, ldc, M * N, batch_size));
       });
 }
 }  // namespace contrib
diff --git a/src/runtime/contrib/tensorrt/tensorrt_runtime.cc b/src/runtime/contrib/tensorrt/tensorrt_runtime.cc
index f89f0abe2a..dc250a6f74 100644
--- a/src/runtime/contrib/tensorrt/tensorrt_runtime.cc
+++ b/src/runtime/contrib/tensorrt/tensorrt_runtime.cc
@@ -22,7 +22,7 @@
  * \brief JSON runtime implementation for TensorRT.
  */
 
-#include <dmlc/parameter.h>
+#include <tvm/ffi/extra/json.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/tensor.h>
@@ -33,6 +33,7 @@
 #include <unordered_map>
 #include <vector>
 
+#include "../../../support/env.h"
 #include "../../file_utils.h"
 #include "../json/json_node.h"
 #include "../json/json_runtime.h"
@@ -75,9 +76,9 @@ class TensorRTRuntime : public JSONRuntimeBase {
         max_batch_size_(-1),
         multi_engine_mode_(false),
         use_fp16_(false) {
-    const bool use_int8 = dmlc::GetEnv("TVM_TENSORRT_USE_INT8", false);
-    multi_engine_mode_ = dmlc::GetEnv("TVM_TENSORRT_MULTI_ENGINE", false);
-    num_calibration_batches_remaining_ = dmlc::GetEnv("TENSORRT_NUM_CALI_INT8", 0);
+    const bool use_int8 = support::GetEnv("TVM_TENSORRT_USE_INT8", false);
+    multi_engine_mode_ = support::GetEnv("TVM_TENSORRT_MULTI_ENGINE", false);
+    num_calibration_batches_remaining_ = support::GetEnv("TENSORRT_NUM_CALI_INT8", 0);
     if (use_int8) {
       ICHECK(num_calibration_batches_remaining_ != 0)
           << "When using INT8 mode, "
@@ -126,7 +127,7 @@ class TensorRTRuntime : public JSONRuntimeBase {
             std::stoi(nodes_[i].GetAttr<std::vector<std::string>>("use_implicit_batch")[0]);
         // Allow max_workspace_size to be overridden at runtime.
         size_t runtime_max_workspace_size =
-            dmlc::GetEnv("TVM_TENSORRT_MAX_WORKSPACE_SIZE", size_t(0));
+            support::GetEnv("TVM_TENSORRT_MAX_WORKSPACE_SIZE", size_t(0));
         if (runtime_max_workspace_size != 0) {
           max_workspace_size_ = runtime_max_workspace_size;
         } else {
@@ -284,7 +285,7 @@ class TensorRTRuntime : public JSONRuntimeBase {
     int batch_size = GetBatchSize();
     int compatible_engine_batch_size = -1;
     bool find_engine_flag = FindCompatibleEngine(batch_size, &compatible_engine_batch_size);
-    const bool use_int8 = (dmlc::GetEnv("TVM_TENSORRT_USE_INT8", 0) != 0);
+    const bool use_int8 = (support::GetEnv("TVM_TENSORRT_USE_INT8", 0) != 0);
     const bool int8_calibration_not_used_or_not_complete =
         (calibrator_ != nullptr && num_calibration_batches_remaining_ != 0);
     if (find_engine_flag &&
@@ -323,7 +324,7 @@ class TensorRTRuntime : public JSONRuntimeBase {
   }
 
   void BuildEngineFromJson(int batch_size) {
-    const bool use_fp16 = dmlc::GetEnv("TVM_TENSORRT_USE_FP16", false) || use_fp16_;
+    const bool use_fp16 = support::GetEnv("TVM_TENSORRT_USE_FP16", false) || use_fp16_;
     TensorRTBuilder builder(&logger_, data_entry_, max_workspace_size_, use_implicit_batch_,
                             use_fp16, batch_size, calibrator_.get());
     for (size_t i = 0; i < input_nodes_.size(); ++i) {
@@ -360,7 +361,7 @@ class TensorRTRuntime : public JSONRuntimeBase {
    * have to be built at first inference.
    */
   bool GetCachedEnginesFromDisk() {
-    std::string cache_dir = dmlc::GetEnv("TVM_TENSORRT_CACHE_DIR", std::string(""));
+    std::string cache_dir = support::GetEnv("TVM_TENSORRT_CACHE_DIR", std::string(""));
     if (cache_dir.empty()) return false;
     std::string key = GetSubgraphKey();
     std::string path = cache_dir + "/" + key + ".plan";
@@ -378,17 +379,30 @@ class TensorRTRuntime : public JSONRuntimeBase {
         runtime->deserializeCudaEngine(&serialized_engine[0], serialized_engine.size(), nullptr);
     engine_and_context.context = engine_and_context.engine->createExecutionContext();
     // Load metadata
+    namespace json = ::tvm::ffi::json;
     std::string meta_path = cache_dir + "/" + key + ".meta";
     std::string serialized_meta;
     LoadBinaryFromFile(meta_path, &serialized_meta);
-    std::istringstream is(serialized_meta);
-    dmlc::JSONReader reader(&is);
-    dmlc::JSONObjectReadHelper helper;
+    auto meta_obj = json::Parse(serialized_meta).cast<json::Object>();
     int batch_size;
-    helper.DeclareField("inputs", &engine_and_context.inputs);
-    helper.DeclareField("outputs", &engine_and_context.outputs);
-    helper.DeclareField("batch_size", &batch_size);
-    helper.ReadAllFields(&reader);
+    // Read inputs
+    {
+      auto arr = meta_obj.at(ffi::String("inputs")).cast<json::Array>();
+      engine_and_context.inputs.clear();
+      for (const auto& v : arr) {
+        engine_and_context.inputs.push_back(std::string(v.cast<ffi::String>()));
+      }
+    }
+    // Read outputs
+    {
+      auto arr = meta_obj.at(ffi::String("outputs")).cast<json::Array>();
+      engine_and_context.outputs.clear();
+      for (const auto& v : arr) {
+        engine_and_context.outputs.push_back(std::string(v.cast<ffi::String>()));
+      }
+    }
+    // Read batch_size
+    batch_size = static_cast<int>(meta_obj.at(ffi::String("batch_size")).cast<int64_t>());
     trt_engine_cache_[std::make_pair(symbol_name_, batch_size)] = engine_and_context;
     max_batch_size_ = batch_size;
     LOG(INFO) << "finished loading engine and context ... ";
@@ -400,7 +414,7 @@ class TensorRTRuntime : public JSONRuntimeBase {
    */
   void CacheEngineToDisk() {
     int batch_size = GetBatchSize();
-    std::string cache_dir = dmlc::GetEnv("TVM_TENSORRT_CACHE_DIR", std::string(""));
+    std::string cache_dir = support::GetEnv("TVM_TENSORRT_CACHE_DIR", std::string(""));
     if (cache_dir.empty()) return;
     std::string key = GetSubgraphKey();
     std::string path = cache_dir + "/" + key + ".plan";
@@ -412,24 +426,32 @@ class TensorRTRuntime : public JSONRuntimeBase {
                                        serialized_engine->size()));
     serialized_engine->destroy();
     // Serialize metadata
-    std::ostringstream os;
-    dmlc::JSONWriter writer(&os);
-    writer.BeginObject();
-    writer.WriteObjectKeyValue("inputs",
-                               trt_engine_cache_[std::make_pair(symbol_name_, batch_size)].inputs);
-    writer.WriteObjectKeyValue("outputs",
-                               trt_engine_cache_[std::make_pair(symbol_name_, batch_size)].outputs);
-    writer.WriteObjectKeyValue("batch_size", batch_size);
-    writer.EndObject();
+    namespace json = ::tvm::ffi::json;
+    json::Object meta_obj;
+    {
+      json::Array inputs_arr;
+      for (const auto& s : trt_engine_cache_[std::make_pair(symbol_name_, batch_size)].inputs) {
+        inputs_arr.push_back(ffi::String(s));
+      }
+      meta_obj.Set(ffi::String("inputs"), std::move(inputs_arr));
+    }
+    {
+      json::Array outputs_arr;
+      for (const auto& s : trt_engine_cache_[std::make_pair(symbol_name_, batch_size)].outputs) {
+        outputs_arr.push_back(ffi::String(s));
+      }
+      meta_obj.Set(ffi::String("outputs"), std::move(outputs_arr));
+    }
+    meta_obj.Set(ffi::String("batch_size"), static_cast<int64_t>(batch_size));
     std::string meta_path = cache_dir + "/" + key + ".meta";
-    SaveBinaryToFile(meta_path, os.str());
+    SaveBinaryToFile(meta_path, std::string(json::Stringify(meta_obj)));
   }
 
   std::string GetSubgraphKey() {
     // Using this key will only allow a single model per TVM_TENSORRT_CACHE_DIR directory. We could
     // instead use a hash of graph_json and all weights to allow many models in the same directory,
     // but the cost of computing the hash is high.
-    return symbol_name_ + (dmlc::GetEnv("TVM_TENSORRT_USE_FP16", false) ? "_fp16" : "_fp32");
+    return symbol_name_ + (support::GetEnv("TVM_TENSORRT_USE_FP16", false) ? "_fp16" : "_fp32");
   }
 
   /*! \brief Retreive a GPU buffer for input or output or allocate if needed. */
diff --git a/src/runtime/cpu_device_api.cc b/src/runtime/cpu_device_api.cc
index d9299832dd..9762d2c3b4 100644
--- a/src/runtime/cpu_device_api.cc
+++ b/src/runtime/cpu_device_api.cc
@@ -20,7 +20,6 @@
 /*!
  * \file cpu_device_api.cc
  */
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/device_api.h>
@@ -143,12 +142,17 @@ 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 dmlc::ThreadLocalStore<CPUWorkspacePool>::Get()->AllocWorkspace(dev, size);
+  return CPUWorkspacePoolThreadLocal()->AllocWorkspace(dev, size);
 }
 
 void CPUDeviceAPI::FreeWorkspace(Device dev, void* data) {
-  dmlc::ThreadLocalStore<CPUWorkspacePool>::Get()->FreeWorkspace(dev, data);
+  CPUWorkspacePoolThreadLocal()->FreeWorkspace(dev, data);
 }
 
 TVM_FFI_STATIC_INIT_BLOCK() {
diff --git a/src/runtime/cuda/cuda_device_api.cc b/src/runtime/cuda/cuda_device_api.cc
index bfd5f7cca9..665db68b26 100644
--- a/src/runtime/cuda/cuda_device_api.cc
+++ b/src/runtime/cuda/cuda_device_api.cc
@@ -23,7 +23,6 @@
  */
 #include <cuda.h>
 #include <cuda_runtime.h>
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/extra/c_env_api.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
@@ -274,11 +273,12 @@ class CUDADeviceAPI final : public DeviceAPI {
   }
 };
 
-typedef dmlc::ThreadLocalStore<CUDAThreadEntry> CUDAThreadStore;
-
 CUDAThreadEntry::CUDAThreadEntry() : pool(kDLCUDA, CUDADeviceAPI::Global()) {}
 
-CUDAThreadEntry* CUDAThreadEntry::ThreadLocal() { return CUDAThreadStore::Get(); }
+CUDAThreadEntry* CUDAThreadEntry::ThreadLocal() {
+  static thread_local CUDAThreadEntry inst;
+  return &inst;
+}
 
 TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
diff --git a/src/runtime/cuda/cuda_module.cc b/src/runtime/cuda/cuda_module.cc
index f5219ae98a..ef5e1e1bf5 100644
--- a/src/runtime/cuda/cuda_module.cc
+++ b/src/runtime/cuda/cuda_module.cc
@@ -24,10 +24,10 @@
 
 #include <cuda.h>
 #include <cuda_runtime.h>
-#include <dmlc/memory_io.h>
 #include <tvm/ffi/extra/c_env_api.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
+#include <tvm/support/io.h>
 
 #include <array>
 #include <mutex>
@@ -35,6 +35,7 @@
 #include <unordered_map>
 #include <vector>
 
+#include "../../support/bytes_io.h"
 #include "../file_utils.h"
 #include "../meta_data.h"
 #include "../pack_args.h"
@@ -95,13 +96,12 @@ class CUDAModuleNode : public ffi::ModuleObj {
   }
 
   ffi::Bytes SaveToBytes() const final {
-    std::string buffer;
-    dmlc::MemoryStringStream ms(&buffer);
-    dmlc::Stream* stream = &ms;
-    stream->Write(fmt_);
-    stream->Write(fmap_);
-    stream->Write(data_);
-    return ffi::Bytes(buffer);
+    std::string result;
+    support::BytesOutStream stream(&result);
+    stream.Write(fmt_);
+    stream.Write(fmap_);
+    stream.Write(data_);
+    return ffi::Bytes(std::move(result));
   }
 
   ffi::String InspectSource(const ffi::String& format) const final {
@@ -326,14 +326,13 @@ ffi::Module CUDAModuleLoadFile(const std::string& file_name, const ffi::String&
 }
 
 ffi::Module CUDAModuleLoadFromBytes(const ffi::Bytes& bytes) {
-  dmlc::MemoryFixedSizeStream ms(const_cast<char*>(bytes.data()), bytes.size());
-  dmlc::Stream* stream = &ms;
+  support::BytesInStream stream(bytes);
   std::string data;
   std::unordered_map<std::string, FunctionInfo> fmap;
   std::string fmt;
-  stream->Read(&fmt);
-  stream->Read(&fmap);
-  stream->Read(&data);
+  stream.Read(&fmt);
+  stream.Read(&fmap);
+  stream.Read(&data);
   return CUDAModuleCreate(data, fmt, fmap, std::string());
 }
 
diff --git a/src/runtime/cuda/l2_cache_flush.cc b/src/runtime/cuda/l2_cache_flush.cc
index b69ecc7188..90003df506 100644
--- a/src/runtime/cuda/l2_cache_flush.cc
+++ b/src/runtime/cuda/l2_cache_flush.cc
@@ -18,7 +18,6 @@
  */
 #include "../../../3rdparty/nvbench/l2_cache_flush.h"
 
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/extra/c_env_api.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
@@ -30,9 +29,10 @@ namespace tvm {
 
 namespace runtime {
 
-typedef dmlc::ThreadLocalStore<L2Flush> L2FlushStore;
-
-L2Flush* L2Flush::ThreadLocal() { return L2FlushStore::Get(); }
+L2Flush* L2Flush::ThreadLocal() {
+  static thread_local L2Flush inst;
+  return &inst;
+}
 
 TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
diff --git a/src/runtime/device_api.cc b/src/runtime/device_api.cc
index f8910f6e88..d2d553f138 100644
--- a/src/runtime/device_api.cc
+++ b/src/runtime/device_api.cc
@@ -37,6 +37,7 @@
 #include <array>
 #include <cctype>
 #include <cstdlib>
+#include <mutex>
 #include <sstream>
 #include <string>
 #include <tuple>
diff --git a/src/runtime/disco/loader.cc b/src/runtime/disco/loader.cc
index 2a92703925..d80cf101a0 100644
--- a/src/runtime/disco/loader.cc
+++ b/src/runtime/disco/loader.cc
@@ -220,6 +220,7 @@ std::string GetSiblingPath(const std::string& path, const std::string& filename)
     return path.substr(0, found + 1) + filename;
   }
   LOG(FATAL) << "ValueError: Cannot find the parent directory: " << path;
+  return "";
 }
 
 Tensor ShardLoaderObj::LoadParamOnWorker0(int weight_index) const {
diff --git a/src/runtime/disco/message_queue.h b/src/runtime/disco/message_queue.h
index e8286384ff..2f065f131c 100644
--- a/src/runtime/disco/message_queue.h
+++ b/src/runtime/disco/message_queue.h
@@ -19,7 +19,7 @@
 #ifndef TVM_RUNTIME_DISCO_MESSAGE_QUEUE_H_
 #define TVM_RUNTIME_DISCO_MESSAGE_QUEUE_H_
 
-#include <dmlc/io.h>
+#include <tvm/support/io.h>
 
 #include <string>
 #include <vector>
@@ -29,10 +29,10 @@
 namespace tvm {
 namespace runtime {
 
-class DiscoStreamMessageQueue : private dmlc::Stream,
+class DiscoStreamMessageQueue : private support::Stream,
                                 private DiscoProtocol<DiscoStreamMessageQueue> {
  public:
-  explicit DiscoStreamMessageQueue(Stream* stream) : stream_(stream) {}
+  explicit DiscoStreamMessageQueue(support::Stream* stream) : stream_(stream) {}
 
   ~DiscoStreamMessageQueue() = default;
 
@@ -113,10 +113,10 @@ class DiscoStreamMessageQueue : private dmlc::Stream,
     return size;
   }
 
-  using dmlc::Stream::Read;
-  using dmlc::Stream::ReadArray;
-  using dmlc::Stream::Write;
-  using dmlc::Stream::WriteArray;
+  using support::Stream::Read;
+  using support::Stream::ReadArray;
+  using support::Stream::Write;
+  using support::Stream::WriteArray;
   friend struct RPCReference;
   friend struct DiscoProtocol<DiscoStreamMessageQueue>;
 
@@ -124,7 +124,7 @@ class DiscoStreamMessageQueue : private dmlc::Stream,
   std::string write_buffer_;
   std::string read_buffer_;
   size_t read_offset_ = 0;
-  dmlc::Stream* stream_;
+  support::Stream* stream_;
 };
 
 }  // namespace runtime
diff --git a/src/runtime/disco/protocol.h b/src/runtime/disco/protocol.h
index 067a4f0d4a..e84b9df04d 100644
--- a/src/runtime/disco/protocol.h
+++ b/src/runtime/disco/protocol.h
@@ -19,11 +19,11 @@
 #ifndef TVM_RUNTIME_DISCO_PROTOCOL_H_
 #define TVM_RUNTIME_DISCO_PROTOCOL_H_
 
-#include <dmlc/io.h>
-#include <dmlc/memory_io.h>
 #include <tvm/ffi/function.h>
 #include <tvm/runtime/base.h>
 #include <tvm/runtime/disco/session.h>
+#include <tvm/support/io.h>
+#include <tvm/support/serializer.h>
 
 #include <memory>
 #include <string>
@@ -32,6 +32,7 @@
 
 #include "../../support/arena.h"
 #include "../../support/base64.h"
+#include "../../support/bytes_io.h"
 #include "../minrpc/rpc_reference.h"
 
 namespace tvm {
@@ -139,6 +140,7 @@ inline uint64_t DiscoProtocol<SubClassType>::GetFFIAnyProtocolBytes(const TVMFFI
     LOG(FATAL) << "ValueError: Object type is not supported in Disco calling convention: "
                << any_view_ptr->GetTypeKey() << " (type_index = " << any_view_ptr->type_index()
                << ")";
+    return 0;
   }
 }
 template <class SubClassType>
@@ -221,7 +223,7 @@ inline std::string DiscoDebugObject::SaveToStr() const {
     Tensor array = opt_nd.value();
     std::string result;
     {
-      dmlc::MemoryStringStream mstrm(&result);
+      support::BytesOutStream mstrm(&result);
       support::Base64OutStream b64strm(&mstrm);
       runtime::SaveDLTensor(&b64strm, array.operator->());
       b64strm.Finish();
@@ -239,6 +241,7 @@ inline std::string DiscoDebugObject::SaveToStr() const {
   }
   LOG(FATAL) << "ValueError: Cannot serialize the following type code in non-debugging mode: "
              << this->data.GetTypeKey();
+  return "";
 }
 
 inline ObjectPtr<DiscoDebugObject> DiscoDebugObject::LoadFromStr(std::string json_str) {
@@ -251,7 +254,7 @@ inline ObjectPtr<DiscoDebugObject> DiscoDebugObject::LoadFromStr(std::string jso
     CHECK(f.has_value()) << "ValueError: Cannot deserialize object in non-debugging mode";
     result->data = (*f)(json_str);
   } else if (control_bit == '1') {
-    dmlc::MemoryStringStream mstrm(&json_str);
+    support::BytesInStream mstrm(json_str);
     support::Base64InStream b64strm(&mstrm);
     b64strm.InitPosition();
     runtime::Tensor array;
diff --git a/src/runtime/disco/threaded_session.cc b/src/runtime/disco/threaded_session.cc
index 029038625f..398c5885e8 100644
--- a/src/runtime/disco/threaded_session.cc
+++ b/src/runtime/disco/threaded_session.cc
@@ -16,11 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-#include <dmlc/io.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/base.h>
 #include <tvm/runtime/disco/disco_worker.h>
 #include <tvm/runtime/object.h>
+#include <tvm/support/io.h>
 
 #include <condition_variable>
 #include <cstdint>
@@ -38,7 +38,7 @@
 namespace tvm {
 namespace runtime {
 
-class DiscoThreadedMessageQueue : private dmlc::Stream,
+class DiscoThreadedMessageQueue : private support::Stream,
                                   private DiscoProtocol<DiscoThreadedMessageQueue> {
  public:
   void Send(const ffi::PackedArgs& args) {
@@ -104,10 +104,10 @@ class DiscoThreadedMessageQueue : private dmlc::Stream,
     return size;
   }
 
-  using dmlc::Stream::Read;
-  using dmlc::Stream::ReadArray;
-  using dmlc::Stream::Write;
-  using dmlc::Stream::WriteArray;
+  using support::Stream::Read;
+  using support::Stream::ReadArray;
+  using support::Stream::Write;
+  using support::Stream::WriteArray;
   friend struct RPCReference;
   friend struct DiscoProtocol<DiscoThreadedMessageQueue>;
 
diff --git a/src/runtime/file_utils.cc b/src/runtime/file_utils.cc
index b3733ee6fd..8fce34a294 100644
--- a/src/runtime/file_utils.cc
+++ b/src/runtime/file_utils.cc
@@ -22,67 +22,89 @@
  */
 #include "file_utils.h"
 
-#include <dmlc/json.h>
-#include <dmlc/memory_io.h>
+#include <tvm/ffi/extra/json.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/logging.h>
-#include <tvm/runtime/serializer.h>
+#include <tvm/support/io.h>
 
 #include <fstream>
 #include <unordered_map>
 #include <utility>
 #include <vector>
 
+#include "../support/bytes_io.h"
+
 namespace tvm {
 namespace runtime {
 
-void FunctionInfo::Save(dmlc::JSONWriter* writer) const {
-  std::vector<std::string> sarg_types(arg_types.size());
-  for (size_t i = 0; i < arg_types.size(); ++i) {
-    sarg_types[i] = DLDataTypeToString(arg_types[i]);
+ffi::json::Value FunctionInfo::SaveToJSON() const {
+  namespace json = ::tvm::ffi::json;
+  json::Object obj;
+  obj.Set(ffi::String("name"), ffi::String(name));
+  // arg_types: convert DLDataType to string
+  json::Array sarg_types;
+  for (const auto& t : arg_types) {
+    sarg_types.push_back(ffi::String(DLDataTypeToString(t)));
+  }
+  obj.Set(ffi::String("arg_types"), std::move(sarg_types));
+  {
+    json::Array tags;
+    for (const auto& s : launch_param_tags) tags.push_back(ffi::String(s));
+    obj.Set(ffi::String("launch_param_tags"), std::move(tags));
   }
-  writer->BeginObject();
-  writer->WriteObjectKeyValue("name", name);
-  writer->WriteObjectKeyValue("arg_types", sarg_types);
-  writer->WriteObjectKeyValue("launch_param_tags", launch_param_tags);
-  std::vector<int> iarg_extra_tags(arg_extra_tags.size());
-  for (size_t i = 0; i < arg_extra_tags.size(); ++i) {
-    iarg_extra_tags[i] = static_cast<int>(arg_extra_tags[i]);
+  // arg_extra_tags: convert enum to int
+  json::Array iarg_extra_tags;
+  for (const auto& t : arg_extra_tags) {
+    iarg_extra_tags.push_back(static_cast<int64_t>(t));
   }
-  writer->WriteObjectKeyValue("arg_extra_tags", iarg_extra_tags);
-  writer->EndObject();
+  obj.Set(ffi::String("arg_extra_tags"), std::move(iarg_extra_tags));
+  return obj;
 }
 
-void FunctionInfo::Load(dmlc::JSONReader* reader) {
-  dmlc::JSONObjectReadHelper helper;
-  std::vector<std::string> sarg_types;
-  helper.DeclareField("name", &name);
-  helper.DeclareField("arg_types", &sarg_types);
-  helper.DeclareOptionalField("launch_param_tags", &launch_param_tags);
-  helper.DeclareOptionalField("thread_axis_tags",
-                              &launch_param_tags);  // for backward compatibility
-  std::vector<int> iarg_extra_tags;
-  helper.DeclareOptionalField("arg_extra_tags", &iarg_extra_tags);
-  arg_extra_tags.resize(iarg_extra_tags.size());
-  for (size_t i = 0; i < arg_extra_tags.size(); ++i) {
-    arg_extra_tags[i] = static_cast<ArgExtraTags>(iarg_extra_tags[i]);
+void FunctionInfo::LoadFromJSON(ffi::json::Object src) {
+  namespace json = ::tvm::ffi::json;
+  name = std::string(src.at(ffi::String("name")).cast<ffi::String>());
+  // arg_types
+  auto sarg_types_arr = src.at(ffi::String("arg_types")).cast<json::Array>();
+  arg_types.resize(sarg_types_arr.size());
+  for (size_t i = 0; i < sarg_types_arr.size(); ++i) {
+    arg_types[i] = StringToDLDataType(std::string(sarg_types_arr[i].cast<ffi::String>()));
   }
-  helper.ReadAllFields(reader);
-  arg_types.resize(sarg_types.size());
-  for (size_t i = 0; i < arg_types.size(); ++i) {
-    arg_types[i] = StringToDLDataType(sarg_types[i]);
+  // launch_param_tags (optional, also support legacy "thread_axis_tags")
+  auto lt = src.find(ffi::String("launch_param_tags"));
+  if (lt != src.end()) {
+    auto arr = (*lt).second.cast<json::Array>();
+    launch_param_tags.clear();
+    for (const auto& elem : arr) launch_param_tags.push_back(std::string(elem.cast<ffi::String>()));
+  } else {
+    auto tt = src.find(ffi::String("thread_axis_tags"));
+    if (tt != src.end()) {
+      auto arr = (*tt).second.cast<json::Array>();
+      launch_param_tags.clear();
+      for (const auto& elem : arr)
+        launch_param_tags.push_back(std::string(elem.cast<ffi::String>()));
+    }
+  }
+  // arg_extra_tags (optional)
+  auto et = src.find(ffi::String("arg_extra_tags"));
+  if (et != src.end()) {
+    auto earr = (*et).second.cast<json::Array>();
+    arg_extra_tags.resize(earr.size());
+    for (size_t i = 0; i < earr.size(); ++i) {
+      arg_extra_tags[i] = static_cast<ArgExtraTags>(earr[i].cast<int64_t>());
+    }
   }
 }
 
-void FunctionInfo::Save(dmlc::Stream* writer) const {
+void FunctionInfo::Save(support::Stream* writer) const {
   writer->Write(name);
   writer->Write(arg_types);
   writer->Write(launch_param_tags);
   writer->Write(arg_extra_tags);
 }
 
-bool FunctionInfo::Load(dmlc::Stream* reader) {
+bool FunctionInfo::Load(support::Stream* reader) {
   if (!reader->Read(&name)) return false;
   if (!reader->Read(&arg_types)) return false;
   if (!reader->Read(&launch_param_tags)) return false;
@@ -150,28 +172,35 @@ void SaveBinaryToFile(const std::string& file_name, const std::string& data) {
 
 void SaveMetaDataToFile(const std::string& file_name,
                         const std::unordered_map<std::string, FunctionInfo>& fmap) {
-  std::string version = "0.1.0";
+  namespace json = ::tvm::ffi::json;
+  json::Object root;
+  root.Set(ffi::String("tvm_version"), ffi::String("0.1.0"));
+  json::Object func_info;
+  for (const auto& kv : fmap) {
+    func_info.Set(ffi::String(kv.first), kv.second.SaveToJSON());
+  }
+  root.Set(ffi::String("func_info"), std::move(func_info));
   std::ofstream fs(file_name.c_str());
   ICHECK(!fs.fail()) << "Cannot open file " << file_name;
-  dmlc::JSONWriter writer(&fs);
-  writer.BeginObject();
-  writer.WriteObjectKeyValue("tvm_version", version);
-  writer.WriteObjectKeyValue("func_info", fmap);
-  writer.EndObject();
+  fs << std::string(json::Stringify(root, 2));
   fs.close();
 }
 
 void LoadMetaDataFromFile(const std::string& file_name,
                           std::unordered_map<std::string, FunctionInfo>* fmap) {
+  namespace json = ::tvm::ffi::json;
   std::ifstream fs(file_name.c_str());
   ICHECK(!fs.fail()) << "Cannot open file " << file_name;
-  std::string version;
-  dmlc::JSONReader reader(&fs);
-  dmlc::JSONObjectReadHelper helper;
-  helper.DeclareField("tvm_version", &version);
-  helper.DeclareField("func_info", fmap);
-  helper.ReadAllFields(&reader);
+  std::string content((std::istreambuf_iterator<char>(fs)), std::istreambuf_iterator<char>());
   fs.close();
+  auto root = json::Parse(content).cast<json::Object>();
+  // tvm_version is ignored
+  auto func_info_obj = root.at(ffi::String("func_info")).cast<json::Object>();
+  for (const auto& kv : func_info_obj) {
+    FunctionInfo info;
+    info.LoadFromJSON(kv.second.cast<json::Object>());
+    (*fmap)[std::string(kv.first.cast<ffi::String>())] = info;
+  }
 }
 
 void RemoveFile(const std::string& file_name) {
@@ -197,10 +226,10 @@ void CopyFile(const std::string& src_file_name, const std::string& dest_file_nam
 }
 
 ffi::Map<ffi::String, Tensor> LoadParams(const std::string& param_blob) {
-  dmlc::MemoryStringStream strm(const_cast<std::string*>(&param_blob));
+  support::BytesInStream strm(param_blob);
   return LoadParams(&strm);
 }
-ffi::Map<ffi::String, Tensor> LoadParams(dmlc::Stream* strm) {
+ffi::Map<ffi::String, Tensor> LoadParams(support::Stream* strm) {
   ffi::Map<ffi::String, Tensor> params;
   uint64_t header, reserved;
   ICHECK(strm->Read(&header)) << "Invalid parameters file format";
@@ -222,7 +251,7 @@ ffi::Map<ffi::String, Tensor> LoadParams(dmlc::Stream* strm) {
   return params;
 }
 
-void SaveParams(dmlc::Stream* strm, const ffi::Map<ffi::String, Tensor>& params) {
+void SaveParams(support::Stream* strm, const ffi::Map<ffi::String, Tensor>& params) {
   std::vector<std::string> names;
   std::vector<const DLTensor*> arrays;
   for (auto& p : params) {
@@ -244,11 +273,10 @@ void SaveParams(dmlc::Stream* strm, const ffi::Map<ffi::String, Tensor>& params)
 }
 
 std::string SaveParams(const ffi::Map<ffi::String, Tensor>& params) {
-  std::string bytes;
-  dmlc::MemoryStringStream strm(&bytes);
-  dmlc::Stream* fo = &strm;
-  SaveParams(fo, params);
-  return bytes;
+  std::string result;
+  support::BytesOutStream strm(&result);
+  SaveParams(&strm, params);
+  return result;
 }
 
 TVM_FFI_STATIC_INIT_BLOCK() {
diff --git a/src/runtime/file_utils.h b/src/runtime/file_utils.h
index 6f5487f7fa..0cb191e480 100644
--- a/src/runtime/file_utils.h
+++ b/src/runtime/file_utils.h
@@ -116,7 +116,7 @@ ffi::Map<ffi::String, Tensor> LoadParams(const std::string& param_blob);
  * \param strm Stream to load parameters from.
  * \return Map of parameter name to parameter value.
  */
-ffi::Map<ffi::String, Tensor> LoadParams(dmlc::Stream* strm);
+ffi::Map<ffi::String, Tensor> LoadParams(support::Stream* strm);
 /*!
  * \brief Serialize parameters to a byte array.
  * \param params Parameters to save.
@@ -128,12 +128,12 @@ std::string SaveParams(const ffi::Map<ffi::String, Tensor>& params);
  * \param strm Stream to write to.
  * \param params Parameters to save.
  */
-void SaveParams(dmlc::Stream* strm, const ffi::Map<ffi::String, Tensor>& params);
+void SaveParams(support::Stream* strm, const ffi::Map<ffi::String, Tensor>& params);
 
 /*!
  * \brief A dmlc stream which wraps standard file operations.
  */
-struct SimpleBinaryFileStream : public dmlc::Stream {
+struct SimpleBinaryFileStream : public support::Stream {
  public:
   SimpleBinaryFileStream(const std::string& path, std::string mode) {
     const char* fname = path.c_str();
diff --git a/src/runtime/hexagon/hexagon_device_api.cc b/src/runtime/hexagon/hexagon_device_api.cc
index 15ee1ed52a..662a220959 100644
--- a/src/runtime/hexagon/hexagon_device_api.cc
+++ b/src/runtime/hexagon/hexagon_device_api.cc
@@ -23,7 +23,6 @@
 
 #include "hexagon_device_api.h"
 
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/logging.h>
@@ -143,9 +142,14 @@ struct HexagonWorkspacePool : public WorkspacePool {
       : WorkspacePool(static_cast<DLDeviceType>(kDLHexagon), HexagonDeviceAPI::Global()) {}
 };
 
+static HexagonWorkspacePool* HexagonWorkspacePoolThreadLocal() {
+  static thread_local HexagonWorkspacePool inst;
+  return &inst;
+}
+
 void* HexagonDeviceAPI::AllocWorkspace(Device dev, size_t size, DLDataType type_hint) {
   CHECK(IsValidDevice(dev)) << "dev.device_type: " << dev.device_type;
-  return dmlc::ThreadLocalStore<HexagonWorkspacePool>::Get()->AllocWorkspace(dev, size);
+  return HexagonWorkspacePoolThreadLocal()->AllocWorkspace(dev, size);
 }
 
 void HexagonDeviceAPI::FreeWorkspace(Device dev, void* data) {
@@ -155,7 +159,7 @@ void HexagonDeviceAPI::FreeWorkspace(Device dev, void* data) {
                           << "Please call HexagonDeviceAPI::AcquireResources";
   CHECK(runtime_hexbuffs->FindHexagonBuffer(data) != nullptr)
       << "Attempt made to free unknown or already freed workspace allocation";
-  dmlc::ThreadLocalStore<HexagonWorkspacePool>::Get()->FreeWorkspace(dev, data);
+  HexagonWorkspacePoolThreadLocal()->FreeWorkspace(dev, data);
 }
 
 void HexagonDeviceAPI::CopyDataFromTo(DLTensor* from, DLTensor* to, TVMStreamHandle stream) {
diff --git a/src/runtime/hexagon/hexagon_module.cc b/src/runtime/hexagon/hexagon_module.cc
index 5515c33e5f..3b96f3e312 100644
--- a/src/runtime/hexagon/hexagon_module.cc
+++ b/src/runtime/hexagon/hexagon_module.cc
@@ -23,14 +23,15 @@
  */
 #include "hexagon_module.h"
 
-#include <dmlc/memory_io.h>
 #include <tvm/ffi/extra/module.h>
 #include <tvm/ffi/function.h>
+#include <tvm/support/io.h>
 
 #include <string>
 #include <utility>
 #include <vector>
 
+#include "../../support/bytes_io.h"
 #include "../file_utils.h"
 
 namespace tvm {
@@ -80,13 +81,12 @@ void HexagonModuleNode::WriteToFile(const ffi::String& file_name, const ffi::Str
 }
 
 ffi::Bytes HexagonModuleNode::SaveToBytes() const {
-  std::string buffer;
-  dmlc::MemoryStringStream ms(&buffer);
-  dmlc::Stream* stream = &ms;
-  stream->Write(fmt_);
-  stream->Write(fmap_);
-  stream->Write(data_);
-  return ffi::Bytes(buffer);
+  std::string result;
+  support::BytesOutStream stream(&result);
+  stream.Write(fmt_);
+  stream.Write(fmap_);
+  stream.Write(data_);
+  return ffi::Bytes(std::move(result));
 }
 
 ffi::Module HexagonModuleCreate(std::string data, std::string fmt,
diff --git a/src/runtime/logging.cc b/src/runtime/logging.cc
index 45e83f33e2..372e5bc301 100644
--- a/src/runtime/logging.cc
+++ b/src/runtime/logging.cc
@@ -274,8 +274,8 @@ std::string Backtrace() {
 
 namespace tvm {
 namespace runtime {
-// Fallback to the dmlc implementation when backtrace is not available.
-std::string Backtrace() { return dmlc::StackTrace(); }
+// Fallback when libbacktrace is not available.
+std::string Backtrace() { return ""; }
 }  // namespace runtime
 }  // namespace tvm
 
diff --git a/src/runtime/meta_data.h b/src/runtime/meta_data.h
index aceb97b583..6cf47d0709 100644
--- a/src/runtime/meta_data.h
+++ b/src/runtime/meta_data.h
@@ -24,11 +24,12 @@
 #ifndef TVM_RUNTIME_META_DATA_H_
 #define TVM_RUNTIME_META_DATA_H_
 
-#include <dmlc/io.h>
-#include <dmlc/json.h>
+#include <tvm/ffi/extra/json.h>
 #include <tvm/ffi/function.h>
 #include <tvm/runtime/module.h>
 #include <tvm/runtime/tensor.h>
+#include <tvm/support/io.h>
+#include <tvm/support/serializer.h>
 
 #include <string>
 #include <unordered_map>
@@ -64,15 +65,23 @@ struct FunctionInfo {
   enum class ArgExtraTags : int { kNone = 0, kTensorMap = 1 };
   std::vector<ArgExtraTags> arg_extra_tags;
 
-  void Save(dmlc::JSONWriter* writer) const;
-  void Load(dmlc::JSONReader* reader);
-  void Save(dmlc::Stream* writer) const;
-  bool Load(dmlc::Stream* reader);
+  ffi::json::Value SaveToJSON() const;
+  void LoadFromJSON(ffi::json::Object src);
+  void Save(support::Stream* writer) const;
+  bool Load(support::Stream* reader);
 };
 }  // namespace runtime
 }  // namespace tvm
 
-namespace dmlc {
-DMLC_DECLARE_TRAITS(has_saveload, ::tvm::runtime::FunctionInfo, true);
-}  // namespace dmlc
+namespace tvm {
+namespace support {
+template <>
+struct Serializer<::tvm::runtime::FunctionInfo> {
+  static constexpr bool enabled = true;
+  static void Write(Stream* strm, const ::tvm::runtime::FunctionInfo& data) { data.Save(strm); }
+  static bool Read(Stream* strm, ::tvm::runtime::FunctionInfo* data) { return data->Load(strm); }
+};
+}  // namespace support
+}  // namespace tvm
+
 #endif  // TVM_RUNTIME_META_DATA_H_
diff --git a/src/runtime/metal/metal_device_api.mm b/src/runtime/metal/metal_device_api.mm
index 2a3cd558f2..c0218a5bf2 100644
--- a/src/runtime/metal/metal_device_api.mm
+++ b/src/runtime/metal/metal_device_api.mm
@@ -20,7 +20,6 @@
 /*!
  * \file metal_device_api.mm
  */
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/profiling.h>
@@ -348,9 +347,10 @@ id<MTLBuffer> MetalThreadEntry::GetTempBuffer(Device dev, size_t size) {
   return temp_buffer_[dev.device_id];
 }
 
-typedef dmlc::ThreadLocalStore<MetalThreadEntry> MetalThreadStore;
-
-MetalThreadEntry* MetalThreadEntry::ThreadLocal() { return MetalThreadStore::Get(); }
+MetalThreadEntry* MetalThreadEntry::ThreadLocal() {
+  static thread_local MetalThreadEntry inst;
+  return &inst;
+}
 
 TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
diff --git a/src/runtime/metal/metal_module.mm b/src/runtime/metal/metal_module.mm
index 9c0aa96257..8002278eb3 100644
--- a/src/runtime/metal/metal_module.mm
+++ b/src/runtime/metal/metal_module.mm
@@ -21,13 +21,17 @@
  * \file metal_module.cc
  */
 #include "metal_module.h"
-#include <dmlc/memory_io.h>
+#include <tvm/support/io.h>
+
+#include <tvm/ffi/extra/json.h>
 #include <tvm/ffi/extra/module.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <array>
 #include <mutex>
+#include <sstream>
 #include <string>
+#include "../../support/bytes_io.h"
 #include "../file_utils.h"
 #include "../meta_data.h"
 #include "../pack_args.h"
@@ -65,15 +69,14 @@ class MetalModuleNode final : public ffi::ModuleObj {
   }
 
   ffi::Bytes SaveToBytes() const final {
-    std::string buffer;
-    dmlc::MemoryStringStream ms(&buffer);
-    dmlc::Stream* stream = &ms;
+    std::string result;
+    support::BytesOutStream stream(&result);
     std::string version = kMetalModuleVersion;
-    stream->Write(version);
-    stream->Write(smap_);
-    stream->Write(fmap_);
-    stream->Write(fmt_);
-    return ffi::Bytes(buffer);
+    stream.Write(version);
+    stream.Write(smap_);
+    stream.Write(fmap_);
+    stream.Write(fmt_);
+    return ffi::Bytes(std::move(result));
   }
   ffi::String InspectSource(const ffi::String& format) const final {
     // return text source if available.
@@ -295,10 +298,14 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   refl::GlobalDef().def("runtime.module.create_metal_module",
                         [](ffi::Map<ffi::String, ffi::String> smap, std::string fmap_json,
                            std::string fmt, std::string source) {
-                          std::istringstream stream(fmap_json);
+                          namespace json = ::tvm::ffi::json;
+                          auto parsed = json::Parse(fmap_json).cast<json::Object>();
                           std::unordered_map<std::string, FunctionInfo> fmap;
-                          dmlc::JSONReader reader(&stream);
-                          reader.Read(&fmap);
+                          for (const auto& kv : parsed) {
+                            FunctionInfo info;
+                            info.LoadFromJSON(kv.second.cast<json::Object>());
+                            fmap[std::string(kv.first.cast<ffi::String>())] = info;
+                          }
 
                           return MetalModuleCreate(std::unordered_map<std::string, std::string>(
                                                        smap.begin(), smap.end()),
@@ -307,8 +314,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 }
 
 ffi::Module MetalModuleLoadFromBytes(const ffi::Bytes& bytes) {
-  dmlc::MemoryFixedSizeStream ms(const_cast<char*>(bytes.data()), bytes.size());
-  dmlc::Stream* stream = &ms;
+  support::BytesInStream stream(bytes);
   // version is reserved for future changes and
   // is discarded for now
   std::string ver;
@@ -316,10 +322,10 @@ ffi::Module MetalModuleLoadFromBytes(const ffi::Bytes& bytes) {
   std::unordered_map<std::string, FunctionInfo> fmap;
   std::string fmt;
 
-  stream->Read(&ver);
-  stream->Read(&smap);
-  stream->Read(&fmap);
-  stream->Read(&fmt);
+  stream.Read(&ver);
+  stream.Read(&smap);
+  stream.Read(&fmap);
+  stream.Read(&fmt);
 
   return MetalModuleCreate(smap, fmap, fmt, "");
 }
diff --git a/src/runtime/opencl/opencl_device_api.cc b/src/runtime/opencl/opencl_device_api.cc
index 896fcaeff3..700c4742fd 100644
--- a/src/runtime/opencl/opencl_device_api.cc
+++ b/src/runtime/opencl/opencl_device_api.cc
@@ -20,14 +20,13 @@
 /*!
  * \file opencl_device_api.cc
  */
-#include <dmlc/parameter.h>
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/profiling.h>
 
 #include <sstream>
 
+#include "../../support/env.h"
 #include "../memory/pooled_allocator.h"
 #include "opencl_common.h"
 
@@ -157,7 +156,7 @@ void OpenCLWorkspace::GetAttr(Device dev, DeviceAttrKind kind, ffi::Any* rv) {
                corresponding to the number of SIMD entries the heardware configures.
                We need to figure out a way to query this information from the hardware.
       */
-      const int warp_size = dmlc::GetEnv("TVM_OPENCL_WARP_SIZE", 1);
+      const int warp_size = support::GetEnv("TVM_OPENCL_WARP_SIZE", 1);
       *rv = warp_size;
       break;
     }
@@ -605,9 +604,10 @@ void OpenCLWorkspace::FreeWorkspace(Device dev, void* data) {
   MemoryManager::GetAllocator(dev, desc->mbuf.alloc_type)->Free(desc->mbuf);
 }
 
-typedef dmlc::ThreadLocalStore<OpenCLThreadEntry> OpenCLThreadStore;
-
-OpenCLThreadEntry* OpenCLThreadEntry::ThreadLocal() { return OpenCLThreadStore::Get(); }
+OpenCLThreadEntry* OpenCLThreadEntry::ThreadLocal() {
+  static thread_local OpenCLThreadEntry inst;
+  return &inst;
+}
 
 std::string GetPlatformInfo(cl_platform_id pid, cl_platform_info param_name) {
   size_t ret_size;
diff --git a/src/runtime/opencl/opencl_module.cc b/src/runtime/opencl/opencl_module.cc
index 980c6bb399..b29a01a8c6 100644
--- a/src/runtime/opencl/opencl_module.cc
+++ b/src/runtime/opencl/opencl_module.cc
@@ -22,14 +22,15 @@
  */
 #include "opencl_module.h"
 
-#include <dmlc/memory_io.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
+#include <tvm/support/io.h>
 
 #include <string>
 #include <unordered_map>
 #include <vector>
 
+#include "../../support/bytes_io.h"
 #include "../source_utils.h"
 #include "opencl_common.h"
 
@@ -169,13 +170,12 @@ void OpenCLModuleNode::WriteToFile(const ffi::String& file_name, const ffi::Stri
 }
 
 ffi::Bytes OpenCLModuleNode::SaveToBytes() const {
-  std::string buffer;
-  dmlc::MemoryStringStream ms(&buffer);
-  dmlc::Stream* stream = &ms;
-  stream->Write(fmt_);
-  stream->Write(fmap_);
-  stream->Write(data_);
-  return ffi::Bytes(buffer);
+  std::string result;
+  support::BytesOutStream stream(&result);
+  stream.Write(fmt_);
+  stream.Write(fmap_);
+  stream.Write(data_);
+  return ffi::Bytes(std::move(result));
 }
 
 ffi::String OpenCLModuleNode::InspectSource(const ffi::String& format) const {
@@ -279,18 +279,16 @@ cl_kernel OpenCLModuleNode::InstallKernel(cl::OpenCLWorkspace* w, cl::OpenCLThre
 
 void OpenCLModuleNode::SetPreCompiledPrograms(const std::string& bytes) {
   workspace_->Init();
-  std::string data = bytes;
-  dmlc::MemoryStringStream reader(&data);
-  dmlc::Stream* strm = &reader;
+  support::BytesInStream strm(bytes);
   uint64_t kernels_num;
-  strm->Read(&kernels_num);
+  strm.Read(&kernels_num);
   cl::OpenCLThreadEntry* t = workspace_->GetThreadEntry();
   int device_id = t->device.device_id;
   for (size_t i = 0; i < kernels_num; ++i) {
     std::string name;
     std::vector<unsigned char> bin_vector;
-    strm->Read(&name);
-    strm->Read(&bin_vector);
+    strm.Read(&name);
+    strm.Read(&bin_vector);
     if (!IsProgramCreated(name, device_id)) {
       cl_int err = 0;
       cl_int binaryStatus;
@@ -322,10 +320,9 @@ void OpenCLModuleNode::SetPreCompiledPrograms(const std::string& bytes) {
 
 std::string OpenCLModuleNode::GetPreCompiledPrograms() {
   workspace_->Init();
-  std::string data;
-  dmlc::MemoryStringStream writer(&data);
-  dmlc::Stream* strm = &writer;
-  strm->Write(static_cast<uint64_t>(parsed_kernels_.size()));
+  std::string result;
+  support::BytesOutStream strm(&result);
+  strm.Write(static_cast<uint64_t>(parsed_kernels_.size()));
   for (auto& it : parsed_kernels_) {
     std::string name = it.first;
     cl::OpenCLThreadEntry* t = workspace_->GetThreadEntry();
@@ -343,10 +340,10 @@ std::string OpenCLModuleNode::GetPreCompiledPrograms() {
     clGetProgramInfo(programs_[name][device_id], CL_PROGRAM_BINARIES, sizeof(unsigned char*),
                      &binary, nullptr);
 
-    strm->Write(name);
-    strm->Write(bin_vector);
+    strm.Write(name);
+    strm.Write(bin_vector);
   }
-  return data;
+  return result;
 }
 
 ffi::Optional<ffi::Function> OpenCLModuleNode::GetFunction(const ffi::String& name) {
@@ -384,14 +381,13 @@ ffi::Module OpenCLModuleLoadFile(const std::string& file_name, const ffi::String
 }
 
 ffi::Module OpenCLModuleLoadFromBytes(const ffi::Bytes& bytes) {
-  dmlc::MemoryFixedSizeStream ms(const_cast<char*>(bytes.data()), bytes.size());
-  dmlc::Stream* stream = &ms;
+  support::BytesInStream stream(bytes);
   std::string data;
   std::unordered_map<std::string, FunctionInfo> fmap;
   std::string fmt;
-  stream->Read(&fmt);
-  stream->Read(&fmap);
-  stream->Read(&data);
+  stream.Read(&fmt);
+  stream.Read(&fmap);
+  stream.Read(&data);
   return OpenCLModuleCreate(data, fmt, fmap, std::string());
 }
 
diff --git a/src/runtime/opencl/opencl_module_spirv.cc b/src/runtime/opencl/opencl_module_spirv.cc
index 096b053823..22a555a3d5 100644
--- a/src/runtime/opencl/opencl_module_spirv.cc
+++ b/src/runtime/opencl/opencl_module_spirv.cc
@@ -17,13 +17,14 @@
  * under the License.
  */
 
-#include <dmlc/memory_io.h>
 #include <tvm/ffi/function.h>
+#include <tvm/support/io.h>
 
 #include <string>
 #include <unordered_map>
 #include <vector>
 
+#include "../../support/bytes_io.h"
 #include "../source_utils.h"
 #include "../spirv/spirv_shader.h"
 #include "opencl_common.h"
@@ -59,12 +60,11 @@ void OpenCLSPIRVModuleNode::WriteToFile(const ffi::String& file_name,
 }
 
 ffi::Bytes OpenCLSPIRVModuleNode::SaveToBytes() const {
-  std::string buffer;
-  dmlc::MemoryStringStream ms(&buffer);
-  dmlc::Stream* stream = &ms;
-  stream->Write(fmap_);
-  stream->Write(shaders_);
-  return ffi::Bytes(buffer);
+  std::string result;
+  support::BytesOutStream stream(&result);
+  stream.Write(fmap_);
+  stream.Write(shaders_);
+  return ffi::Bytes(std::move(result));
 }
 
 void OpenCLSPIRVModuleNode::Init() {
diff --git a/src/runtime/opencl/opencl_wrapper/opencl_wrapper.cc b/src/runtime/opencl/opencl_wrapper/opencl_wrapper.cc
index e9d60553e7..948d1800b7 100644
--- a/src/runtime/opencl/opencl_wrapper/opencl_wrapper.cc
+++ b/src/runtime/opencl/opencl_wrapper/opencl_wrapper.cc
@@ -33,7 +33,6 @@
 #include <dlfcn.h>
 #endif
 
-#define DMLC_USE_LOGGING_LIBRARY <tvm/runtime/logging.h>
 #include <tvm/runtime/logging.h>
 
 #include <cstring>
diff --git a/src/runtime/pack_args.h b/src/runtime/pack_args.h
index 8929f90b0f..74d9e9ca12 100644
--- a/src/runtime/pack_args.h
+++ b/src/runtime/pack_args.h
@@ -151,6 +151,7 @@ inline ArgConvertCode GetArgConvertCode(DLDataType t) {
     return HANDLE_TO_HANDLE;
   }
   LOG(FATAL) << "Cannot handle " << t << " as device function argument";
+  TVM_FFI_UNREACHABLE();
 }
 
 template <int N, typename F>
diff --git a/src/runtime/profiling.cc b/src/runtime/profiling.cc
index 21c5681176..b9275398ca 100644
--- a/src/runtime/profiling.cc
+++ b/src/runtime/profiling.cc
@@ -22,7 +22,7 @@
  * \brief Runtime profiling including timers.
  */
 
-#include <dmlc/json.h>
+#include <tvm/ffi/extra/json.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/c_backend_api.h>
@@ -35,8 +35,11 @@
 #include <iomanip>
 #include <iostream>
 #include <map>
+#include <mutex>
 #include <numeric>
+#include <set>
 #include <thread>
+#include <unordered_set>
 
 namespace tvm {
 namespace runtime {
@@ -710,73 +713,58 @@ Report::Report(ffi::Array<ffi::Map<ffi::String, ffi::Any>> calls,
   data_ = std::move(node);
 }
 
-ffi::Map<ffi::String, ffi::Any> parse_metrics(dmlc::JSONReader* reader) {
-  reader->BeginObject();
-  std::string metric_name, metric_value_name;
+namespace json = ::tvm::ffi::json;
+
+ffi::Map<ffi::String, ffi::Any> parse_metrics(const json::Object& obj) {
   ffi::Map<ffi::String, ffi::Any> metrics;
-  while (reader->NextObjectItem(&metric_name)) {
+  for (const auto& [k, v] : obj) {
+    std::string metric_name = k.cast<ffi::String>();
+    json::Object metric_obj = v.cast<json::Object>();
     ffi::Any o;
-    reader->BeginObject();
-    reader->NextObjectItem(&metric_value_name);
-    if (metric_value_name == "microseconds") {
-      double microseconds;
-      reader->Read(&microseconds);
-      o = ObjectRef(ffi::make_object<DurationNode>(microseconds));
-    } else if (metric_value_name == "percent") {
-      double percent;
-      reader->Read(&percent);
-      o = ObjectRef(ffi::make_object<PercentNode>(percent));
-    } else if (metric_value_name == "count") {
-      int64_t count;
-      reader->Read(&count);
-      o = ObjectRef(ffi::make_object<CountNode>(count));
-    } else if (metric_value_name == "ratio") {
-      double ratio;
-      reader->Read(&ratio);
-      o = ObjectRef(ffi::make_object<RatioNode>(ratio));
-    } else if (metric_value_name == "string") {
-      std::string s;
-      reader->Read(&s);
-      o = ffi::String(s);
-    } else {
-      LOG(FATAL) << "Cannot parse metric of type " << metric_value_name
-                 << " valid types are microseconds, percent, count.";
+    // Each metric value is an object with a single key indicating the type
+    for (const auto& [type_key, type_val] : metric_obj) {
+      std::string metric_value_name = type_key.cast<ffi::String>();
+      if (metric_value_name == "microseconds") {
+        o = ObjectRef(ffi::make_object<DurationNode>(type_val.cast<double>()));
+      } else if (metric_value_name == "percent") {
+        o = ObjectRef(ffi::make_object<PercentNode>(type_val.cast<double>()));
+      } else if (metric_value_name == "count") {
+        o = ObjectRef(ffi::make_object<CountNode>(type_val.cast<int64_t>()));
+      } else if (metric_value_name == "ratio") {
+        o = ObjectRef(ffi::make_object<RatioNode>(type_val.cast<double>()));
+      } else if (metric_value_name == "string") {
+        o = ffi::String(type_val.cast<ffi::String>());
+      } else {
+        LOG(FATAL) << "Cannot parse metric of type " << metric_value_name
+                   << " valid types are microseconds, percent, count.";
+      }
     }
     metrics.Set(metric_name, o);
-    // Necessary to make sure that the parser hits the end of the object.
-    ICHECK(!reader->NextObjectItem(&metric_value_name));
-    // EndObject does not exist, leaving this here for clarity
-    // reader.EndObject();
   }
-  // reader.EndObject();
   return metrics;
 }
 
-Report Report::FromJSON(ffi::String json) {
-  std::stringstream input(json.operator std::string());
-  dmlc::JSONReader reader(&input);
-  std::string key;
+Report Report::FromJSON(ffi::String json_str) {
+  auto root = json::Parse(json_str).cast<json::Object>();
   ffi::Array<ffi::Map<ffi::String, ffi::Any>> calls;
   ffi::Map<ffi::String, ffi::Map<ffi::String, ffi::Any>> device_metrics;
   ffi::Map<ffi::String, ffi::Any> configuration;
 
-  reader.BeginObject();
-  while (reader.NextObjectItem(&key)) {
+  for (const auto& [k, v] : root) {
+    std::string key = k.cast<ffi::String>();
     if (key == "calls") {
-      reader.BeginArray();
-      while (reader.NextArrayItem()) {
-        calls.push_back(parse_metrics(&reader));
+      json::Array calls_arr = v.cast<json::Array>();
+      for (const ffi::Any& item : calls_arr) {
+        calls.push_back(parse_metrics(item.cast<json::Object>()));
       }
-      // reader.EndArray();
     } else if (key == "device_metrics") {
-      reader.BeginObject();
-      std::string device_name;
-      while (reader.NextObjectItem(&device_name)) {
-        device_metrics.Set(device_name, parse_metrics(&reader));
+      json::Object dev_obj = v.cast<json::Object>();
+      for (const auto& [dev_key, dev_val] : dev_obj) {
+        std::string device_name = dev_key.cast<ffi::String>();
+        device_metrics.Set(device_name, parse_metrics(dev_val.cast<json::Object>()));
       }
-      // reader.EndObject();
     } else if (key == "configuration") {
-      configuration = parse_metrics(&reader);
+      configuration = parse_metrics(v.cast<json::Object>());
     }
   }
   return Report(calls, device_metrics, configuration);
diff --git a/src/runtime/rocm/rocm_device_api.cc b/src/runtime/rocm/rocm_device_api.cc
index 0161696535..6408892bb5 100644
--- a/src/runtime/rocm/rocm_device_api.cc
+++ b/src/runtime/rocm/rocm_device_api.cc
@@ -21,7 +21,6 @@
  * \file rocm_device_api.cc
  * \brief GPU specific API
  */
-#include <dmlc/thread_local.h>
 #include <hip/hip_runtime_api.h>
 #include <hsa/hsa.h>
 #include <tvm/ffi/extra/c_env_api.h>
@@ -239,11 +238,12 @@ class ROCMDeviceAPI final : public DeviceAPI {
   }
 };
 
-typedef dmlc::ThreadLocalStore<ROCMThreadEntry> ROCMThreadStore;
-
 ROCMThreadEntry::ROCMThreadEntry() : pool(kDLROCM, ROCMDeviceAPI::Global()) {}
 
-ROCMThreadEntry* ROCMThreadEntry::ThreadLocal() { return ROCMThreadStore::Get(); }
+ROCMThreadEntry* ROCMThreadEntry::ThreadLocal() {
+  static thread_local ROCMThreadEntry inst;
+  return &inst;
+}
 
 TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
diff --git a/src/runtime/rocm/rocm_module.cc b/src/runtime/rocm/rocm_module.cc
index ca1a47400b..9d9b48f51c 100644
--- a/src/runtime/rocm/rocm_module.cc
+++ b/src/runtime/rocm/rocm_module.cc
@@ -22,11 +22,11 @@
  */
 #include "rocm_module.h"
 
-#include <dmlc/memory_io.h>
 #include <hip/hip_runtime_api.h>
 #include <tvm/ffi/extra/c_env_api.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
+#include <tvm/support/io.h>
 
 #include <array>
 #include <mutex>
@@ -34,6 +34,7 @@
 #include <unordered_map>
 #include <vector>
 
+#include "../../support/bytes_io.h"
 #include "../file_utils.h"
 #include "../meta_data.h"
 #include "../pack_args.h"
@@ -81,13 +82,12 @@ class ROCMModuleNode : public ffi::ModuleObj {
   }
 
   ffi::Bytes SaveToBytes() const final {
-    std::string buffer;
-    dmlc::MemoryStringStream ms(&buffer);
-    dmlc::Stream* stream = &ms;
-    stream->Write(fmt_);
-    stream->Write(fmap_);
-    stream->Write(data_);
-    return ffi::Bytes(buffer);
+    std::string result;
+    support::BytesOutStream stream(&result);
+    stream.Write(fmt_);
+    stream.Write(fmap_);
+    stream.Write(data_);
+    return ffi::Bytes(std::move(result));
   }
 
   ffi::String InspectSource(const ffi::String& format) const final {
@@ -227,14 +227,13 @@ ffi::Module ROCMModuleLoadFile(const std::string& file_name, const std::string&
 }
 
 ffi::Module ROCMModuleLoadFromBytes(const ffi::Bytes& bytes) {
-  dmlc::MemoryFixedSizeStream ms(const_cast<char*>(bytes.data()), bytes.size());
-  dmlc::Stream* stream = &ms;
+  support::BytesInStream stream(bytes);
   std::string data;
   std::unordered_map<std::string, FunctionInfo> fmap;
   std::string fmt;
-  stream->Read(&fmt);
-  stream->Read(&fmap);
-  stream->Read(&data);
+  stream.Read(&fmt);
+  stream.Read(&fmap);
+  stream.Read(&data);
   return ROCMModuleCreate(data, fmt, fmap, std::string(), std::string());
 }
 
diff --git a/src/runtime/rpc/rpc_endpoint.cc b/src/runtime/rpc/rpc_endpoint.cc
index 0778b55394..3860aa5ed8 100644
--- a/src/runtime/rpc/rpc_endpoint.cc
+++ b/src/runtime/rpc/rpc_endpoint.cc
@@ -53,7 +53,7 @@ namespace runtime {
  * - SendPackedSeq: send the arguments over to the peer
  * - HandleNextEvent: handle the next request from the peer(RPCCode followed by per code protocol).
  */
-class RPCEndpoint::EventHandler : public dmlc::Stream {
+class RPCEndpoint::EventHandler : public support::Stream {
  public:
   EventHandler(support::RingBuffer* reader, support::RingBuffer* writer, std::string name,
                std::string* remote_key, std::function<void()> flush_writer)
@@ -204,15 +204,13 @@ class RPCEndpoint::EventHandler : public dmlc::Stream {
   }
 
   // Endian aware IO handling
-  using Stream::Read;
-  using Stream::ReadArray;
-  using Stream::Write;
-  using Stream::WriteArray;
-
   void MessageStart(uint64_t packet_nbytes) {
     // Unused here, implemented for microTVM framing layer.
   }
 
+  using Stream::Read;
+  using Stream::Write;
+
   bool Read(RPCCode* code) {
     int32_t cdata;
     if (!this->Read(&cdata)) return false;
@@ -500,7 +498,7 @@ class RPCEndpoint::EventHandler : public dmlc::Stream {
 
     // When session is local, we can directly treat handle
     // as the cpu pointer without allocating a temp space.
-    if (arr->device.device_type == kDLCPU && sess->IsLocalSession() && DMLC_IO_NO_ENDIAN_SWAP) {
+    if (arr->device.device_type == kDLCPU && sess->IsLocalSession() && TVM_FFI_IO_NO_ENDIAN_SWAP) {
       char* data_ptr = reinterpret_cast<char*>(arr->data) + arr->byte_offset;
       fcopyack(data_ptr, data_bytes);
     } else {
@@ -512,8 +510,8 @@ class RPCEndpoint::EventHandler : public dmlc::Stream {
           this->SwitchToState(kRecvPacketNumBytes);
         } else {
           // endian aware handling
-          if (!DMLC_IO_NO_ENDIAN_SWAP) {
-            dmlc::ByteSwap(temp_data, elem_bytes, data_bytes / elem_bytes);
+          if (!TVM_FFI_IO_NO_ENDIAN_SWAP) {
+            ffi::ByteSwap(temp_data, elem_bytes, data_bytes / elem_bytes);
           }
           fcopyack(temp_data, data_bytes);
         }
@@ -537,8 +535,8 @@ class RPCEndpoint::EventHandler : public dmlc::Stream {
       char* dptr = reinterpret_cast<char*>(arr->data) + arr->byte_offset;
       this->ReadArray(dptr, data_bytes);
 
-      if (!DMLC_IO_NO_ENDIAN_SWAP) {
-        dmlc::ByteSwap(dptr, elem_bytes, data_bytes / elem_bytes);
+      if (!TVM_FFI_IO_NO_ENDIAN_SWAP) {
+        ffi::ByteSwap(dptr, elem_bytes, data_bytes / elem_bytes);
       }
       this->ReturnVoid();
       this->SwitchToState(kRecvPacketNumBytes);
@@ -546,8 +544,8 @@ class RPCEndpoint::EventHandler : public dmlc::Stream {
       char* temp_data = this->ArenaAlloc<char>(data_bytes);
       this->ReadArray(temp_data, data_bytes);
 
-      if (!DMLC_IO_NO_ENDIAN_SWAP) {
-        dmlc::ByteSwap(temp_data, elem_bytes, data_bytes / elem_bytes);
+      if (!TVM_FFI_IO_NO_ENDIAN_SWAP) {
+        ffi::ByteSwap(temp_data, elem_bytes, data_bytes / elem_bytes);
       }
 
       auto on_copy_complete = [this](RPCCode status, ffi::PackedArgs args) {
@@ -591,7 +589,7 @@ class RPCEndpoint::EventHandler : public dmlc::Stream {
     uint64_t len;
     this->Read(&len);
     client_protocol_ver.resize(len);
-    this->Read(dmlc::BeginPtr(client_protocol_ver), len);
+    this->Read(client_protocol_ver.data(), len);
 
     ffi::PackedArgs args = RecvPackedSeq();
 
@@ -911,7 +909,7 @@ void RPCEndpoint::CopyToRemote(void* from_bytes, DLTensor* to, uint64_t nbytes)
   std::lock_guard<std::mutex> lock(mutex_);
   RPCCode code = RPCCode::kCopyToRemote;
 
-  uint64_t tensor_total_size_bytes = static_cast<uint64_t>(GetDataSize(*to));
+  uint64_t tensor_total_size_bytes = static_cast<uint64_t>(ffi::GetDataSize(*to));
   ICHECK_LE(to->byte_offset + nbytes, tensor_total_size_bytes)
       << "CopyToRemote: overflow in tensor size: (byte_offset=" << to->byte_offset
       << ", nbytes=" << nbytes << ", tensor_total_size=" << tensor_total_size_bytes << ")";
@@ -931,7 +929,7 @@ void RPCEndpoint::CopyFromRemote(DLTensor* from, void* to_bytes, uint64_t nbytes
   std::lock_guard<std::mutex> lock(mutex_);
   RPCCode code = RPCCode::kCopyFromRemote;
 
-  uint64_t tensor_total_size_bytes = static_cast<uint64_t>(GetDataSize(*from));
+  uint64_t tensor_total_size_bytes = static_cast<uint64_t>(ffi::GetDataSize(*from));
   ICHECK_LE(from->byte_offset + nbytes, tensor_total_size_bytes)
       << "CopyFromRemote: overflow in tensor size: (byte_offset=" << from->byte_offset
       << ", nbytes=" << nbytes << ", tensor_total_size=" << tensor_total_size_bytes << ")";
diff --git a/src/runtime/rpc/rpc_pipe_impl.cc b/src/runtime/rpc/rpc_pipe_impl.cc
index 0bc608ccc2..fd02e747bf 100644
--- a/src/runtime/rpc/rpc_pipe_impl.cc
+++ b/src/runtime/rpc/rpc_pipe_impl.cc
@@ -96,10 +96,10 @@ ffi::Module CreatePipeClient(std::vector<std::string> cmd) {
     std::string swrite_pipe = std::to_string(child_write);
     std::vector<char*> argv;
     for (auto& str : cmd) {
-      argv.push_back(dmlc::BeginPtr(str));
+      argv.push_back(str.data());
     }
-    argv.push_back(dmlc::BeginPtr(sread_pipe));
-    argv.push_back(dmlc::BeginPtr(swrite_pipe));
+    argv.push_back(sread_pipe.data());
+    argv.push_back(swrite_pipe.data());
     argv.push_back(nullptr);
     execvp(argv[0], &argv[0]);
   }
diff --git a/src/runtime/rpc/rpc_socket_impl.cc b/src/runtime/rpc/rpc_socket_impl.cc
index c19b91801e..eaff1f539f 100644
--- a/src/runtime/rpc/rpc_socket_impl.cc
+++ b/src/runtime/rpc/rpc_socket_impl.cc
@@ -23,6 +23,8 @@
  */
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
+#include <tvm/support/io.h>
+#include <tvm/support/serializer.h>
 
 #include <memory>
 
@@ -142,22 +144,22 @@ TVM_FFI_STATIC_INIT_BLOCK() {
       });
 }
 
-class SimpleSockHandler : public dmlc::Stream {
+class SimpleSockHandler : public support::Stream {
   // Things that will interface with user directly.
  public:
   explicit SimpleSockHandler(int sockfd)
       : sock_(static_cast<support::TCPSocket::SockType>(sockfd)) {}
-  using dmlc::Stream::Read;
-  using dmlc::Stream::ReadArray;
-  using dmlc::Stream::Write;
-  using dmlc::Stream::WriteArray;
+  using support::Stream::Read;
+  using support::Stream::ReadArray;
+  using support::Stream::Write;
+  using support::Stream::WriteArray;
 
   // Unused here, implemented for microTVM framing layer.
   void MessageStart(uint64_t packet_nbytes) {}
   void MessageDone() {}
 
   // Internal supporting.
-  // Override methods that inherited from dmlc::Stream.
+  // Override methods that inherited from support::Stream.
  private:
   size_t Read(void* data, size_t size) final { return sock_.Recv(data, size); }
   size_t Write(const void* data, size_t size) final { return sock_.Send(data, size); }
diff --git a/src/runtime/spirv/spirv_shader.h b/src/runtime/spirv/spirv_shader.h
index 06b331d333..202b85b243 100644
--- a/src/runtime/spirv/spirv_shader.h
+++ b/src/runtime/spirv/spirv_shader.h
@@ -20,11 +20,12 @@
 #ifndef TVM_RUNTIME_SPIRV_SPIRV_SHADER_H_
 #define TVM_RUNTIME_SPIRV_SPIRV_SHADER_H_
 
-#include <dmlc/io.h>
 #include <tvm/ffi/function.h>
 #include <tvm/runtime/base.h>
 #include <tvm/runtime/device_api.h>
 #include <tvm/runtime/logging.h>
+#include <tvm/support/io.h>
+#include <tvm/support/serializer.h>
 
 #include <vector>
 
@@ -38,11 +39,11 @@ struct SPIRVShader {
   /*! \brief Data segment */
   std::vector<uint32_t> data;
 
-  void Save(dmlc::Stream* writer) const {
+  void Save(support::Stream* writer) const {
     writer->Write(flag);
     writer->Write(data);
   }
-  bool Load(dmlc::Stream* reader) {
+  bool Load(support::Stream* reader) {
     if (!reader->Read(&flag)) return false;
     if (!reader->Read(&data)) return false;
     return true;
@@ -55,7 +56,18 @@ using spirv::SPIRVShader;
 }  // namespace runtime
 }  // namespace tvm
 
-namespace dmlc {
-DMLC_DECLARE_TRAITS(has_saveload, ::tvm::runtime::spirv::SPIRVShader, true);
-}  // namespace dmlc
+namespace tvm {
+namespace support {
+template <>
+struct Serializer<::tvm::runtime::spirv::SPIRVShader> {
+  static constexpr bool enabled = true;
+  static void Write(Stream* strm, const ::tvm::runtime::spirv::SPIRVShader& data) {
+    data.Save(strm);
+  }
+  static bool Read(Stream* strm, ::tvm::runtime::spirv::SPIRVShader* data) {
+    return data->Load(strm);
+  }
+};
+}  // namespace support
+}  // namespace tvm
 #endif  // TVM_RUNTIME_SPIRV_SPIRV_SHADER_H_
diff --git a/src/runtime/static_library.cc b/src/runtime/static_library.cc
index 2cf7d33945..621fcc506b 100644
--- a/src/runtime/static_library.cc
+++ b/src/runtime/static_library.cc
@@ -24,14 +24,16 @@
  */
 #include "./static_library.h"
 
-#include <dmlc/memory_io.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/memory.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/module.h>
+#include <tvm/support/io.h>
+#include <tvm/support/serializer.h>
 
 #include <iostream>
 
+#include "../support/bytes_io.h"
 #include "file_utils.h"
 
 namespace tvm {
@@ -58,28 +60,26 @@ class StaticLibraryNode final : public ffi::ModuleObj {
   }
 
   ffi::Bytes SaveToBytes() const final {
-    std::string buffer;
-    dmlc::MemoryStringStream ms(&buffer);
-    dmlc::Stream* stream = &ms;
-    stream->Write(data_);
+    std::string result;
+    support::BytesOutStream stream(&result);
+    stream.Write(data_);
     std::vector<std::string> func_names;
     for (const auto func_name : func_names_) func_names.push_back(func_name);
-    stream->Write(func_names);
-    return ffi::Bytes(buffer);
+    stream.Write(func_names);
+    return ffi::Bytes(std::move(result));
   }
 
   static ffi::Module LoadFromBytes(ffi::Bytes bytes) {
-    dmlc::MemoryFixedSizeStream ms(const_cast<char*>(bytes.data()), bytes.size());
-    dmlc::Stream* stream = &ms;
+    support::BytesInStream stream(bytes);
     auto n = ffi::make_object<StaticLibraryNode>();
     // load data
     std::string data;
-    ICHECK(stream->Read(&data)) << "Loading data failed";
+    ICHECK(stream.Read(&data)) << "Loading data failed";
     n->data_ = std::move(data);
 
     // load func names
     std::vector<std::string> func_names;
-    ICHECK(stream->Read(&func_names)) << "Loading func names failed";
+    ICHECK(stream.Read(&func_names)) << "Loading func names failed";
     for (auto func_name : func_names) n->func_names_.push_back(ffi::String(func_name));
 
     return ffi::Module(n);
diff --git a/src/runtime/thread_pool.cc b/src/runtime/thread_pool.cc
index 132369e9b4..ac605ef86d 100644
--- a/src/runtime/thread_pool.cc
+++ b/src/runtime/thread_pool.cc
@@ -21,7 +21,6 @@
  * \file thread_pool.cc
  * \brief Threadpool for multi-threading runtime.
  */
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/container/array.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
@@ -122,7 +121,10 @@ class ParallelLauncher {
   // Signal that one job has finished.
   void SignalJobFinish() { num_pending_.fetch_sub(1); }
   // Get thread local version of the store.
-  static ParallelLauncher* ThreadLocal() { return dmlc::ThreadLocalStore<ParallelLauncher>::Get(); }
+  static ParallelLauncher* ThreadLocal() {
+    static thread_local ParallelLauncher inst;
+    return &inst;
+  }
   // The parallel lambda
   FTVMParallelLambda flambda;
   // The closure data
@@ -319,7 +321,10 @@ class ThreadPool {
     return res;
   }
 
-  static ThreadPool* ThreadLocal() { return dmlc::ThreadLocalStore<ThreadPool>::Get(); }
+  static ThreadPool* ThreadLocal() {
+    static thread_local ThreadPool inst;
+    return &inst;
+  }
 
   void UpdateWorkerConfiguration(threading::ThreadGroup::AffinityMode mode, int nthreads,
                                  const std::vector<unsigned int>& cpus) {
diff --git a/src/runtime/thread_storage_scope.h b/src/runtime/thread_storage_scope.h
index c2cd792220..44b5ab0d3a 100644
--- a/src/runtime/thread_storage_scope.h
+++ b/src/runtime/thread_storage_scope.h
@@ -131,6 +131,7 @@ struct StorageScope {
         return "metal.simdgroup" + tag;
       default:
         LOG(FATAL) << "unknown storage scope";
+        return "";
     }
   }
   /*!
diff --git a/src/runtime/vm/executable.cc b/src/runtime/vm/executable.cc
index 0f9f8a40e8..71d3f58ece 100644
--- a/src/runtime/vm/executable.cc
+++ b/src/runtime/vm/executable.cc
@@ -21,15 +21,16 @@
  * \file src/runtime/vm/executable.cc
  */
 
-#include <dmlc/memory_io.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/logging.h>
 #include <tvm/runtime/vm/executable.h>
 #include <tvm/runtime/vm/vm.h>
+#include <tvm/support/io.h>
 
 #include <functional>
 #include <sstream>
 
+#include "../../support/bytes_io.h"
 #include "../file_utils.h"
 
 namespace tvm {
@@ -143,14 +144,14 @@ Instruction VMExecutable::GetInstruction(Index i) const {
   return Instruction();
 }
 
-void SaveHeader(dmlc::Stream* strm) {
+void SaveHeader(support::Stream* strm) {
   uint64_t header = kTVMVMBytecodeMagicV2;
   strm->Write(header);
   std::string version = VM_VERSION;
   strm->Write(version);
 }
 
-uint64_t LoadHeader(dmlc::Stream* strm) {
+uint64_t LoadHeader(support::Stream* strm) {
   // Check header.
   uint64_t header;
   STREAM_CHECK(strm->Read(&header), "header");
@@ -165,9 +166,8 @@ uint64_t LoadHeader(dmlc::Stream* strm) {
 }
 
 ffi::Bytes VMExecutable::SaveToBytes() const {
-  std::string code;
-  // Initialize the stream object.
-  dmlc::MemoryStringStream strm(&code);
+  std::string result;
+  support::BytesOutStream strm(&result);
 
   // Save header
   SaveHeader(&strm);
@@ -184,7 +184,7 @@ ffi::Bytes VMExecutable::SaveToBytes() const {
   // Code section.
   SaveCodeSection(&strm);
 
-  return ffi::Bytes(code);
+  return ffi::Bytes(std::move(result));
 }
 
 void VMExecutable::WriteToFile(const ffi::String& file_name, const ffi::String& format) const {
@@ -192,8 +192,7 @@ void VMExecutable::WriteToFile(const ffi::String& file_name, const ffi::String&
 }
 
 ffi::Module VMExecutable::LoadFromBytes(const ffi::Bytes& bytes) {
-  std::string code;
-  dmlc::MemoryFixedSizeStream strm(const_cast<char*>(bytes.data()), bytes.size());
+  support::BytesInStream strm(bytes);
 
   ObjectPtr<VMExecutable> exec = ffi::make_object<VMExecutable>();
 
@@ -230,7 +229,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
       .def("ffi.Module.load_from_bytes.relax.VMExecutable", VMExecutable::LoadFromBytes);
 }
 
-void VMFuncInfo::Save(dmlc::Stream* strm) const {
+void VMFuncInfo::Save(support::Stream* strm) const {
   int32_t temp_kind = static_cast<int32_t>(kind);
   strm->Write(temp_kind);
   strm->Write(name);
@@ -241,7 +240,7 @@ void VMFuncInfo::Save(dmlc::Stream* strm) const {
   strm->Write(param_names);
 }
 
-bool VMFuncInfo::Load(dmlc::Stream* strm) {
+bool VMFuncInfo::Load(support::Stream* strm) {
   int32_t temp_kind;
   if (!strm->Read(&temp_kind)) return false;
   this->kind = static_cast<VMFuncInfo::FuncKind>(temp_kind);
@@ -254,9 +253,9 @@ bool VMFuncInfo::Load(dmlc::Stream* strm) {
   return true;
 }
 
-void VMExecutable::SaveGlobalSection(dmlc::Stream* strm) const { strm->Write(func_table); }
+void VMExecutable::SaveGlobalSection(support::Stream* strm) const { strm->Write(func_table); }
 
-void VMExecutable::SaveMemoryScopeSection(dmlc::Stream* strm) const {
+void VMExecutable::SaveMemoryScopeSection(support::Stream* strm) const {
   strm->Write(static_cast<uint64_t>(this->memory_scopes.size()));
   for (auto it = this->memory_scopes.begin(); it != this->memory_scopes.end(); it++) {
     LOG(WARNING) << "Scope Saving:" << it->second;
@@ -265,7 +264,7 @@ void VMExecutable::SaveMemoryScopeSection(dmlc::Stream* strm) const {
   }
 }
 
-void VMExecutable::SaveConstantSection(dmlc::Stream* strm) const {
+void VMExecutable::SaveConstantSection(support::Stream* strm) const {
   // NOTE: pay close attention to the explicit type in write here
   // so the load/save is 32/64 bit compatible
   strm->Write(static_cast<uint64_t>(this->constants.size()));
@@ -298,12 +297,12 @@ void VMExecutable::SaveConstantSection(dmlc::Stream* strm) const {
   }
 }
 
-void VMExecutable::SaveCodeSection(dmlc::Stream* strm) const {
+void VMExecutable::SaveCodeSection(support::Stream* strm) const {
   strm->Write(instr_offset);
   strm->Write(instr_data);
 }
 
-void VMExecutable::LoadGlobalSection(dmlc::Stream* strm) {
+void VMExecutable::LoadGlobalSection(support::Stream* strm) {
   STREAM_CHECK(strm->Read(&func_table), "Global Section");
   // setup func map
   for (size_t i = 0; i < func_table.size(); ++i) {
@@ -311,7 +310,7 @@ void VMExecutable::LoadGlobalSection(dmlc::Stream* strm) {
   }
 }
 
-void VMExecutable::LoadMemoryScopeSection(dmlc::Stream* strm) {
+void VMExecutable::LoadMemoryScopeSection(support::Stream* strm) {
   uint64_t sz;
   // Load the number of memory scope entries.
   STREAM_CHECK(strm->Read(&sz, sizeof(sz)), "memory scopes");
@@ -328,7 +327,7 @@ void VMExecutable::LoadMemoryScopeSection(dmlc::Stream* strm) {
   }
 }
 
-void VMExecutable::LoadConstantSection(dmlc::Stream* strm) {
+void VMExecutable::LoadConstantSection(support::Stream* strm) {
   uint64_t sz;
   // Load the number of constants.
   STREAM_CHECK(strm->Read(&sz, sizeof(sz)), "constant");
@@ -386,7 +385,7 @@ void VMExecutable::LoadConstantSection(dmlc::Stream* strm) {
   }
 }
 
-void VMExecutable::LoadCodeSection(dmlc::Stream* strm) {
+void VMExecutable::LoadCodeSection(support::Stream* strm) {
   STREAM_CHECK(strm->Read(&(this->instr_offset)), "instr offset");
   STREAM_CHECK(strm->Read(&(this->instr_data)), "instr data");
 }
diff --git a/src/runtime/vm/tensor_cache_support.cc b/src/runtime/vm/tensor_cache_support.cc
index ca25353d66..3bd249ccba 100644
--- a/src/runtime/vm/tensor_cache_support.cc
+++ b/src/runtime/vm/tensor_cache_support.cc
@@ -235,7 +235,7 @@ class TensorCache {
     for (const TensorCacheMetadata::FileRecord& shard_rec : metadata.records) {
       try {
         params = shard_rec.Load(device, cache_path, &raw_data, &staging_buffer);
-      } catch (const dmlc::Error& e) {
+      } catch (const std::runtime_error& e) {
         LOG(FATAL) << "ValueError: Error when loading parameters from " << shard_rec.data_path
                    << ": " << e.what();
       }
diff --git a/src/runtime/vulkan/vulkan_device.cc b/src/runtime/vulkan/vulkan_device.cc
index cc39972432..abdc0c0ce0 100644
--- a/src/runtime/vulkan/vulkan_device.cc
+++ b/src/runtime/vulkan/vulkan_device.cc
@@ -408,8 +408,7 @@ uint32_t VulkanDevice::SelectComputeQueueFamily() const {
   uint32_t queue_prop_count = 0;
   vkGetPhysicalDeviceQueueFamilyProperties(physical_device_, &queue_prop_count, nullptr);
   std::vector<VkQueueFamilyProperties> queue_props(queue_prop_count);
-  vkGetPhysicalDeviceQueueFamilyProperties(physical_device_, &queue_prop_count,
-                                           dmlc::BeginPtr(queue_props));
+  vkGetPhysicalDeviceQueueFamilyProperties(physical_device_, &queue_prop_count, queue_props.data());
 
   std::vector<uint32_t> result;
   // Prefer compute-only queues. On certain devices supporting this (e.g. Mesa RADV), using
@@ -600,6 +599,7 @@ uint32_t FindMemoryType(const VulkanDevice& device, VkBufferCreateInfo info,
     type_bits >>= 1;
   }
   LOG(FATAL) << "Requested memory type not found";
+  return 0;
 }
 
 VulkanHostVisibleBuffer* GetOrAllocate(
diff --git a/src/runtime/vulkan/vulkan_module.cc b/src/runtime/vulkan/vulkan_module.cc
index dbf2d9fff7..fc265e63f9 100644
--- a/src/runtime/vulkan/vulkan_module.cc
+++ b/src/runtime/vulkan/vulkan_module.cc
@@ -19,10 +19,11 @@
 
 #include "vulkan_module.h"
 
-#include <dmlc/memory_io.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
+#include <tvm/support/io.h>
 
+#include "../../support/bytes_io.h"
 #include "../file_utils.h"
 #include "vulkan_wrapped_func.h"
 
@@ -45,25 +46,23 @@ ffi::Module VulkanModuleLoadFile(const std::string& file_name, const ffi::String
   std::string meta_file = GetMetaFilePath(file_name);
   LoadBinaryFromFile(file_name, &data);
   LoadMetaDataFromFile(meta_file, &fmap);
-  dmlc::MemoryStringStream fs(&data);
-  dmlc::Stream* stream = &fs;
+  support::BytesInStream stream(data);
   uint32_t magic;
-  stream->Read(&magic);
+  stream.Read(&magic);
   ICHECK_EQ(magic, kVulkanModuleMagic) << "VulkanModule Magic mismatch";
-  stream->Read(&smap);
+  stream.Read(&smap);
   return VulkanModuleCreate(smap, fmap, "");
 }
 
 ffi::Module VulkanModuleLoadFromBytes(const ffi::Bytes& bytes) {
-  dmlc::MemoryFixedSizeStream ms(const_cast<char*>(bytes.data()), bytes.size());
-  dmlc::Stream* stream = &ms;
+  support::BytesInStream stream(bytes);
   std::unordered_map<std::string, SPIRVShader> smap;
   std::unordered_map<std::string, FunctionInfo> fmap;
 
   std::string fmt;
-  stream->Read(&fmt);
-  stream->Read(&fmap);
-  stream->Read(&smap);
+  stream.Read(&fmt);
+  stream.Read(&fmap);
+  stream.Read(&smap);
   return VulkanModuleCreate(smap, fmap, "");
 }
 
diff --git a/src/runtime/vulkan/vulkan_wrapped_func.cc b/src/runtime/vulkan/vulkan_wrapped_func.cc
index 007d6abdba..7d67e965cd 100644
--- a/src/runtime/vulkan/vulkan_wrapped_func.cc
+++ b/src/runtime/vulkan/vulkan_wrapped_func.cc
@@ -19,10 +19,11 @@
 
 #include "vulkan_wrapped_func.h"
 
-#include <dmlc/memory_io.h>
+#include <tvm/support/io.h>
 
 #include <utility>
 
+#include "../../support/bytes_io.h"
 #include "../file_utils.h"
 #include "vulkan_device_api.h"
 
@@ -408,23 +409,21 @@ void VulkanModuleNode::WriteToFile(const ffi::String& file_name, const ffi::Stri
   ICHECK_EQ(fmt, fmt_) << "Can only save to customized format vulkan";
   std::string meta_file = GetMetaFilePath(file_name);
   SaveMetaDataToFile(meta_file, fmap_);
-  std::string data_bin;
-  dmlc::MemoryStringStream fs(&data_bin);
-  dmlc::Stream* stream = &fs;
+  std::string result;
+  support::BytesOutStream stream(&result);
   uint32_t magic = kVulkanModuleMagic;
-  stream->Write(magic);
-  stream->Write(smap_);
-  SaveBinaryToFile(file_name, data_bin);
+  stream.Write(magic);
+  stream.Write(smap_);
+  SaveBinaryToFile(file_name, result);
 }
 
 ffi::Bytes VulkanModuleNode::SaveToBytes() const {
-  std::string buffer;
-  dmlc::MemoryStringStream ms(&buffer);
-  dmlc::Stream* stream = &ms;
-  stream->Write(fmt_);
-  stream->Write(fmap_);
-  stream->Write(smap_);
-  return ffi::Bytes(buffer);
+  std::string result;
+  support::BytesOutStream stream(&result);
+  stream.Write(fmt_);
+  stream.Write(fmap_);
+  stream.Write(smap_);
+  return ffi::Bytes(std::move(result));
 }
 
 ffi::String VulkanModuleNode::InspectSource(const ffi::String& format) const {
diff --git a/src/s_tir/analysis/sblock_access_region_detector.cc b/src/s_tir/analysis/sblock_access_region_detector.cc
index e8dda4d88e..2a64d531c8 100644
--- a/src/s_tir/analysis/sblock_access_region_detector.cc
+++ b/src/s_tir/analysis/sblock_access_region_detector.cc
@@ -28,6 +28,7 @@
 #include <tvm/tir/stmt_functor.h>
 
 #include <unordered_map>
+#include <unordered_set>
 
 #include "../../tir/transform/ir_utils.h"
 namespace tvm {
diff --git a/src/s_tir/meta_schedule/mutator/mutate_parallel.cc b/src/s_tir/meta_schedule/mutator/mutate_parallel.cc
index e699549e85..e3377fc9c2 100644
--- a/src/s_tir/meta_schedule/mutator/mutate_parallel.cc
+++ b/src/s_tir/meta_schedule/mutator/mutate_parallel.cc
@@ -19,6 +19,7 @@
 #include <tvm/ffi/reflection/registry.h>
 
 #include <algorithm>
+#include <map>
 #include <unordered_map>
 
 #include "../utils.h"
diff --git a/src/s_tir/meta_schedule/postproc/disallow_async_strided_mem_copy.cc b/src/s_tir/meta_schedule/postproc/disallow_async_strided_mem_copy.cc
index 5f21dde437..7b2184f83c 100644
--- a/src/s_tir/meta_schedule/postproc/disallow_async_strided_mem_copy.cc
+++ b/src/s_tir/meta_schedule/postproc/disallow_async_strided_mem_copy.cc
@@ -160,7 +160,7 @@ class DisallowAsyncStridedMemCopyNode : public PostprocNode {
           IRModule mod =
               IRModule(ffi::Map<GlobalVar, BaseFunc>({{GlobalVar(g_var->name_hint), f}}));
           lowered = tvm::transform::Sequential(pass_list)(std::move(mod));
-        } catch (const dmlc::Error& e) {
+        } catch (const std::runtime_error& e) {
           return false;
         }
         if (s_tir::AsyncStridedMemCopyFinder::Find(lowered)) {
diff --git a/src/s_tir/meta_schedule/postproc/verify_gpu_code.cc b/src/s_tir/meta_schedule/postproc/verify_gpu_code.cc
index fd1ac30b9b..bce597a6b7 100644
--- a/src/s_tir/meta_schedule/postproc/verify_gpu_code.cc
+++ b/src/s_tir/meta_schedule/postproc/verify_gpu_code.cc
@@ -64,7 +64,7 @@ class ThreadExtentChecker : private StmtVisitor {
         }
         return;
       } else {
-        throw dmlc::Error("Dynamic thread extent");
+        throw std::runtime_error("Dynamic thread extent");
       }
     }
     StmtVisitor::VisitStmt_(loop);
@@ -83,7 +83,7 @@ class ThreadExtentChecker : private StmtVisitor {
         int64_t high = high_inclusive.value()->value;
         int64_t thread_extent_product = thread_idx_x * thread_idx_y * thread_idx_z;
         if (!(low <= thread_extent_product && thread_extent_product <= high)) {
-          throw dmlc::Error("Thread extent");
+          throw std::runtime_error("Thread extent");
         }
       }
     }
diff --git a/src/s_tir/meta_schedule/search_strategy/evolutionary_search.cc b/src/s_tir/meta_schedule/search_strategy/evolutionary_search.cc
index 5588ea0c44..90c055c75f 100644
--- a/src/s_tir/meta_schedule/search_strategy/evolutionary_search.cc
+++ b/src/s_tir/meta_schedule/search_strategy/evolutionary_search.cc
@@ -19,6 +19,8 @@
 
 #include <tvm/ffi/reflection/registry.h>
 
+#include <mutex>
+
 #include "../module_equality.h"
 #include "../utils.h"
 
diff --git a/src/s_tir/meta_schedule/utils.h b/src/s_tir/meta_schedule/utils.h
index 23786cf725..7cad637d4c 100644
--- a/src/s_tir/meta_schedule/utils.h
+++ b/src/s_tir/meta_schedule/utils.h
@@ -19,7 +19,6 @@
 #ifndef TVM_S_TIR_META_SCHEDULE_UTILS_H_
 #define TVM_S_TIR_META_SCHEDULE_UTILS_H_
 
-#include <dmlc/memory_io.h>
 #include <tvm/arith/analyzer.h>
 #include <tvm/ffi/optional.h>
 #include <tvm/node/node.h>
@@ -39,7 +38,9 @@
 #include <tvm/s_tir/meta_schedule/task_scheduler.h>
 #include <tvm/s_tir/meta_schedule/tune_context.h>
 #include <tvm/s_tir/schedule/schedule.h>
+#include <tvm/support/io.h>
 #include <tvm/support/parallel_for.h>
+#include <tvm/support/serializer.h>
 #include <tvm/tir/transform.h>
 
 #include <algorithm>
@@ -51,6 +52,7 @@
 
 #include "../../support/array.h"
 #include "../../support/base64.h"
+#include "../../support/bytes_io.h"
 #include "../../support/nd_int_set.h"
 #include "../../support/table_printer.h"
 #include "../../support/utils.h"
@@ -174,12 +176,12 @@ using TRandState = support::LinearCongruentialEngine::TRandState;
  * \return The base64 encoded string.
  */
 inline std::string Base64Encode(std::string str) {
-  std::string result;
-  dmlc::MemoryStringStream m_stream(&result);
+  std::string m_buffer;
+  support::BytesOutStream m_stream(&m_buffer);
   support::Base64OutStream b64stream(&m_stream);
-  static_cast<dmlc::Stream*>(&b64stream)->Write(str);
+  static_cast<support::Stream*>(&b64stream)->Write(str);
   b64stream.Finish();
-  return result;
+  return m_buffer;
 }
 
 /*!
@@ -189,10 +191,10 @@ inline std::string Base64Encode(std::string str) {
  */
 inline std::string Base64Decode(std::string str) {
   std::string result;
-  dmlc::MemoryStringStream m_stream(&str);
+  support::BytesInStream m_stream(str);
   support::Base64InStream b64stream(&m_stream);
   b64stream.InitPosition();
-  static_cast<dmlc::Stream*>(&b64stream)->Read(&result);
+  static_cast<support::Stream*>(&b64stream)->Read(&result);
   return result;
 }
 
diff --git a/src/s_tir/schedule/primitive/hide_buffer_access.cc b/src/s_tir/schedule/primitive/hide_buffer_access.cc
index 664ed1e6c9..40d116b4f7 100644
--- a/src/s_tir/schedule/primitive/hide_buffer_access.cc
+++ b/src/s_tir/schedule/primitive/hide_buffer_access.cc
@@ -16,6 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+#include <set>
+
 #include "../../../tir/transform/ir_utils.h"
 #include "../utils.h"
 
diff --git a/src/s_tir/schedule/primitive/reorder_block_iter_var.cc b/src/s_tir/schedule/primitive/reorder_block_iter_var.cc
index 4a76d6c511..5b0def9a72 100644
--- a/src/s_tir/schedule/primitive/reorder_block_iter_var.cc
+++ b/src/s_tir/schedule/primitive/reorder_block_iter_var.cc
@@ -16,6 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+#include <set>
+
 #include "../utils.h"
 
 namespace tvm {
diff --git a/src/s_tir/transform/inject_software_pipeline.cc b/src/s_tir/transform/inject_software_pipeline.cc
index 33a45cdd6c..dc21dab7de 100644
--- a/src/s_tir/transform/inject_software_pipeline.cc
+++ b/src/s_tir/transform/inject_software_pipeline.cc
@@ -26,6 +26,7 @@
 #include <tvm/target/target.h>
 #include <tvm/tir/builtin.h>
 
+#include <map>
 #include <unordered_set>
 
 #include "../../support/utils.h"
diff --git a/src/s_tir/transform/lower_async_dma.cc b/src/s_tir/transform/lower_async_dma.cc
index 6f56554791..8d95c76918 100644
--- a/src/s_tir/transform/lower_async_dma.cc
+++ b/src/s_tir/transform/lower_async_dma.cc
@@ -32,6 +32,7 @@
 #include <tvm/tir/stmt_functor.h>
 
 #include <optional>
+#include <set>
 
 #include "../../arith/ir_mutator_with_analyzer.h"
 #include "../../tir/transform/ir_utils.h"
diff --git a/src/s_tir/transform/merge_shared_memory_allocations.cc b/src/s_tir/transform/merge_shared_memory_allocations.cc
index 11fe15e95a..64e0a96622 100644
--- a/src/s_tir/transform/merge_shared_memory_allocations.cc
+++ b/src/s_tir/transform/merge_shared_memory_allocations.cc
@@ -30,6 +30,8 @@
 #include <tvm/tir/op.h>
 #include <tvm/tir/stmt_functor.h>
 
+#include <list>
+#include <map>
 #include <unordered_map>
 #include <unordered_set>
 
diff --git a/src/s_tir/transform/using_assume_to_reduce_branches.cc b/src/s_tir/transform/using_assume_to_reduce_branches.cc
index 40b9db43f2..ab4ec76afe 100644
--- a/src/s_tir/transform/using_assume_to_reduce_branches.cc
+++ b/src/s_tir/transform/using_assume_to_reduce_branches.cc
@@ -44,6 +44,8 @@
 #include <tvm/tir/op.h>
 #include <tvm/tir/stmt_functor.h>
 
+#include <map>
+
 #include "../../arith/constraint_extract.h"
 #include "../../arith/ir_mutator_with_analyzer.h"
 #include "tvm/ir/expr.h"
diff --git a/src/script/printer/doc_printer/python_doc_printer.cc b/src/script/printer/doc_printer/python_doc_printer.cc
index 1a79806d16..5627315c03 100644
--- a/src/script/printer/doc_printer/python_doc_printer.cc
+++ b/src/script/printer/doc_printer/python_doc_printer.cc
@@ -23,6 +23,7 @@
 
 #include <algorithm>
 #include <cmath>
+#include <map>
 #include <string>
 
 #include "../../../support/str_escape.h"
diff --git a/src/support/base64.h b/src/support/base64.h
index 2bfc42c27f..afdb6509ac 100644
--- a/src/support/base64.h
+++ b/src/support/base64.h
@@ -27,6 +27,7 @@
 #define TVM_SUPPORT_BASE64_H_
 
 #include <tvm/runtime/logging.h>
+#include <tvm/support/io.h>
 
 #include <cctype>
 #include <cstdio>
@@ -65,7 +66,7 @@ class StreamBufferReader {
    * \brief set input stream
    * \param stream The stream to be set
    */
-  void set_stream(dmlc::Stream* stream) {
+  void set_stream(tvm::support::Stream* stream) {
     stream_ = stream;
     read_len_ = read_ptr_ = 1;
   }
@@ -88,7 +89,7 @@ class StreamBufferReader {
 
  private:
   /*! \brief the underlying stream */
-  dmlc::Stream* stream_{nullptr};
+  tvm::support::Stream* stream_{nullptr};
   /*! \brief buffer to hold data */
   std::string buffer_;
   /*! \brief length of valid data in buffer */
@@ -100,9 +101,9 @@ class StreamBufferReader {
 /*!
  * \brief Input stream from base64 encoding
  */
-class Base64InStream : public dmlc::Stream {
+class Base64InStream : public tvm::support::Stream {
  public:
-  explicit Base64InStream(dmlc::Stream* fs) : reader_(256) { reader_.set_stream(fs); }
+  explicit Base64InStream(tvm::support::Stream* fs) : reader_(256) { reader_.set_stream(fs); }
   /*!
    * \brief initialize the stream position to beginning of next base64 stream
    * \note call this function before actually start read
@@ -116,7 +117,7 @@ class Base64InStream : public dmlc::Stream {
   /*! \brief whether current position is end of a base64 stream */
   bool IsEOF(void) const { return num_prev_ == 0 && (temp_ch_ == EOF || isspace(temp_ch_)); }
 
-  using dmlc::Stream::Read;
+  using tvm::support::Stream::Read;
   // override read function.
   size_t Read(void* ptr, size_t size) final {
     using base64::DecodeTable;
@@ -208,6 +209,7 @@ class Base64InStream : public dmlc::Stream {
   }
   size_t Write(const void* ptr, size_t size) final {
     LOG(FATAL) << "Base64InStream do not support write";
+    return 0;
   }
 
  private:
@@ -223,11 +225,11 @@ class Base64InStream : public dmlc::Stream {
 /*!
  * \brief Stream to write to base64 format.
  */
-class Base64OutStream : public dmlc::Stream {
+class Base64OutStream : public tvm::support::Stream {
  public:
-  explicit Base64OutStream(dmlc::Stream* fp) : fp_(fp) {}
+  explicit Base64OutStream(tvm::support::Stream* fp) : fp_(fp) {}
 
-  using dmlc::Stream::Write;
+  using tvm::support::Stream::Write;
 
   size_t Write(const void* ptr, size_t size) final {
     using base64::EncodeTable;
@@ -251,6 +253,7 @@ class Base64OutStream : public dmlc::Stream {
   }
   virtual size_t Read(void* ptr, size_t size) {
     LOG(FATAL) << "Base64OutStream do not support read";
+    return 0;
   }
   /*!
    * \brief finish writing of all current base64 stream, do some post processing
@@ -278,7 +281,7 @@ class Base64OutStream : public dmlc::Stream {
  private:
   static constexpr size_t kBufferSize = 256;
 
-  dmlc::Stream* fp_{nullptr};
+  tvm::support::Stream* fp_{nullptr};
   int buf__top_{0};
   unsigned char buf_[4];
   std::string out_buf_;
diff --git a/src/support/bytes_io.h b/src/support/bytes_io.h
new file mode 100644
index 0000000000..5c9d0a4f09
--- /dev/null
+++ b/src/support/bytes_io.h
@@ -0,0 +1,103 @@
+/*
+ * 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 support/bytes_io.h
+ * \brief In-memory byte stream implementations.
+ *
+ *   BytesInStream  — sequential read from a fixed memory region
+ *   BytesOutStream — sequential write to a caller-provided std::string
+ */
+#ifndef TVM_SUPPORT_BYTES_IO_H_
+#define TVM_SUPPORT_BYTES_IO_H_
+
+#include <tvm/ffi/error.h>
+#include <tvm/ffi/string.h>
+#include <tvm/support/io.h>
+
+#include <algorithm>
+#include <cstring>
+#include <string>
+
+namespace tvm {
+namespace support {
+
+/*!
+ * \brief Read-only stream over a fixed memory region.
+ */
+class BytesInStream : public Stream {
+ public:
+  using Stream::Read;
+  using Stream::Write;
+
+  BytesInStream(const void* data, size_t size)
+      : data_(reinterpret_cast<const char*>(data)), size_(size), pos_(0) {}
+
+  explicit BytesInStream(const ffi::Bytes& bytes)
+      : data_(bytes.data()), size_(bytes.size()), pos_(0) {}
+
+  explicit BytesInStream(const std::string& str) : data_(str.data()), size_(str.size()), pos_(0) {}
+
+  size_t Read(void* ptr, size_t size) override {
+    size_t nread = std::min(size_ - pos_, size);
+    if (nread != 0) std::memcpy(ptr, data_ + pos_, nread);
+    pos_ += nread;
+    return nread;
+  }
+
+  size_t Write(const void*, size_t) override {
+    TVM_FFI_THROW(InternalError) << "write to input stream";
+    return 0;
+  }
+
+ private:
+  const char* data_;
+  size_t size_;
+  size_t pos_;
+};
+
+/*!
+ * \brief Write-only stream that appends to a caller-provided std::string.
+ */
+class BytesOutStream : public Stream {
+ public:
+  using Stream::Read;
+  using Stream::Write;
+
+  explicit BytesOutStream(std::string* buffer) : buffer_(buffer) {}
+
+  size_t Write(const void* ptr, size_t size) override {
+    if (size == 0) return 0;
+    buffer_->append(reinterpret_cast<const char*>(ptr), size);
+    return size;
+  }
+
+  size_t Read(void*, size_t) override {
+    TVM_FFI_THROW(InternalError) << "read from output stream";
+    return 0;
+  }
+
+ private:
+  std::string* buffer_;
+};
+
+}  // namespace support
+}  // namespace tvm
+
+#endif  // TVM_SUPPORT_BYTES_IO_H_
diff --git a/src/support/env.h b/src/support/env.h
new file mode 100644
index 0000000000..81e865290f
--- /dev/null
+++ b/src/support/env.h
@@ -0,0 +1,63 @@
+/*
+ * 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 support/env.h
+ * \brief Typed environment variable access.
+ */
+#ifndef TVM_SUPPORT_ENV_H_
+#define TVM_SUPPORT_ENV_H_
+
+#include <cstdlib>
+#include <sstream>
+#include <string>
+#include <type_traits>
+
+namespace tvm {
+namespace support {
+
+/*!
+ * \brief Get environment variable with a typed default value.
+ * \tparam T The value type (must be arithmetic or std::string).
+ * \param key The environment variable name.
+ * \param default_value The value to return if the variable is unset or empty.
+ * \return The parsed value or the default.
+ */
+template <typename T>
+inline T GetEnv(const char* key, T default_value) {
+  const char* val = std::getenv(key);
+  if (val == nullptr || !*val) return default_value;
+  if constexpr (std::is_same_v<T, std::string>) {
+    return std::string(val);
+  } else if constexpr (std::is_same_v<T, bool>) {
+    // Accept "0"/"false" as false, anything else as true
+    std::string s(val);
+    return !(s == "0" || s == "false" || s == "False" || s == "FALSE");
+  } else {
+    T ret;
+    std::istringstream is(val);
+    is >> ret;
+    return ret;
+  }
+}
+
+}  // namespace support
+}  // namespace tvm
+
+#endif  // TVM_SUPPORT_ENV_H_
diff --git a/src/support/libinfo.cc b/src/support/libinfo.cc
index e824b73138..3bc517bafa 100644
--- a/src/support/libinfo.cc
+++ b/src/support/libinfo.cc
@@ -266,7 +266,6 @@ TVM_DLL ffi::Map<ffi::String, ffi::String> GetLibInfo() {
       {"BUILD_DUMMY_LIBTVM", TVM_INFO_BUILD_DUMMY_LIBTVM},
       {"COMPILER_RT_PATH", TVM_INFO_COMPILER_RT_PATH},
       {"CUDA_VERSION", TVM_INFO_CUDA_VERSION},
-      {"DMLC_PATH", TVM_INFO_DMLC_PATH},
       {"GIT_COMMIT_HASH", TVM_INFO_GIT_COMMIT_HASH},
       {"GIT_COMMIT_TIME", TVM_INFO_GIT_COMMIT_TIME},
       {"HIDE_PRIVATE_SYMBOLS", TVM_INFO_HIDE_PRIVATE_SYMBOLS},
diff --git a/src/support/pipe.h b/src/support/pipe.h
index 9d5aa1e486..3ce60f2b7a 100644
--- a/src/support/pipe.h
+++ b/src/support/pipe.h
@@ -24,8 +24,8 @@
 #ifndef TVM_SUPPORT_PIPE_H_
 #define TVM_SUPPORT_PIPE_H_
 
-#include <dmlc/io.h>
 #include <tvm/runtime/logging.h>
+#include <tvm/support/io.h>
 
 #ifdef _WIN32
 #include <windows.h>
@@ -42,7 +42,7 @@ namespace tvm {
 namespace support {
 
 /*! \brief Platform independent pipe */
-class Pipe : public dmlc::Stream {
+class Pipe : public tvm::support::Stream {
  public:
 #ifdef _WIN32
   using PipeHandle = HANDLE;
diff --git a/src/support/socket.h b/src/support/socket.h
index e9e2f87f9d..ed8ee4721b 100644
--- a/src/support/socket.h
+++ b/src/support/socket.h
@@ -48,8 +48,8 @@
 #include <unistd.h>
 #endif
 
-#include <dmlc/io.h>
 #include <tvm/runtime/logging.h>
+#include <tvm/support/io.h>
 
 #include <cstring>
 #include <string>
@@ -371,7 +371,7 @@ class Socket {
 /*!
  * \brief a wrapper of TCP socket that hopefully be cross platform
  */
-class TCPSocket : public Socket, public dmlc::Stream {
+class TCPSocket : public Socket, public Stream {
  public:
   TCPSocket() : Socket(INVALID_SOCKET) {}
   /*!
diff --git a/src/target/codegen.cc b/src/target/codegen.cc
index 30238318ff..61a455f530 100644
--- a/src/target/codegen.cc
+++ b/src/target/codegen.cc
@@ -21,12 +21,13 @@
  * \file codegen.cc
  * \brief Common utilities to generated C style code.
  */
-#include <dmlc/memory_io.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/ir/module.h>
 #include <tvm/runtime/base.h>
 #include <tvm/runtime/module.h>
+#include <tvm/support/io.h>
+#include <tvm/support/serializer.h>
 #include <tvm/target/codegen.h>
 #include <tvm/target/target.h>
 #include <tvm/tir/function.h>
@@ -38,6 +39,8 @@
 #include <unordered_set>
 #include <vector>
 
+#include "../support/bytes_io.h"
+
 namespace tvm {
 namespace codegen {
 
@@ -60,7 +63,7 @@ class ModuleSerializer {
  public:
   explicit ModuleSerializer(ffi::Module mod) : mod_(mod) { Init(); }
 
-  void SerializeModuleToBytes(dmlc::Stream* stream, bool export_dso) {
+  void SerializeModuleToBytes(support::Stream* stream, bool export_dso) {
     // Always _import_tree
     stream->Write(import_tree_row_ptr_);
     stream->Write(import_tree_child_indices_);
@@ -212,39 +215,37 @@ class ModuleSerializer {
 };
 
 std::string SerializeModuleToBytes(const ffi::Module& mod, bool export_dso) {
-  std::string bin;
-  dmlc::MemoryStringStream ms(&bin);
-  dmlc::Stream* stream = &ms;
+  std::string result;
+  support::BytesOutStream stream(&result);
 
   ModuleSerializer module_serializer(mod);
-  module_serializer.SerializeModuleToBytes(stream, export_dso);
-  return bin;
+  module_serializer.SerializeModuleToBytes(&stream, export_dso);
+  return result;
 }
 
 ffi::Module DeserializeModuleFromBytes(std::string blob) {
-  dmlc::MemoryStringStream ms(&blob);
-  dmlc::Stream* stream = &ms;
+  support::BytesInStream stream(blob);
 
   std::vector<ffi::Module> modules;
   std::vector<uint64_t> import_tree_row_ptr;
   std::vector<uint64_t> import_tree_child_indices;
 
-  stream->Read(&import_tree_row_ptr);
-  stream->Read(&import_tree_child_indices);
+  stream.Read(&import_tree_row_ptr);
+  stream.Read(&import_tree_child_indices);
 
   uint64_t size = import_tree_row_ptr.size() - 1;
   for (uint64_t i = 0; i < size; ++i) {
     std::string tkey;
-    ICHECK(stream->Read(&tkey));
+    ICHECK(stream.Read(&tkey));
     // "_lib" serves as a placeholder in the module import tree to indicate where
     // to place the DSOModule
     ICHECK(tkey != "_lib") << "Should not contain any placeholder for DSOModule.";
     if (tkey == "_import_tree") {
-      ICHECK(stream->Read(&import_tree_row_ptr));
-      ICHECK(stream->Read(&import_tree_child_indices));
+      ICHECK(stream.Read(&import_tree_row_ptr));
+      ICHECK(stream.Read(&import_tree_child_indices));
     } else {
       std::string bytes;
-      ICHECK(stream->Read(&bytes));
+      ICHECK(stream.Read(&bytes));
       auto loader = ffi::Function::GetGlobal("ffi.Module.load_from_bytes." + tkey);
       ICHECK(loader.has_value()) << "ffi.Module.load_from_bytes." << tkey << " is not enabled";
       auto m = (*loader)(ffi::Bytes(bytes)).cast<ffi::Module>();
diff --git a/src/target/llvm/codegen_hexagon.cc b/src/target/llvm/codegen_hexagon.cc
index 773e2a2e1d..a546ad2019 100644
--- a/src/target/llvm/codegen_hexagon.cc
+++ b/src/target/llvm/codegen_hexagon.cc
@@ -421,7 +421,7 @@ llvm::Value* CodeGenHexagon::VectorLookupLoad(Buffer buffer, DataType buffer_typ
 }
 
 namespace {
-DMLC_ATTRIBUTE_UNUSED std::ostream& operator<<(std::ostream& os, const llvm::Module& m) {
+TVM_ATTRIBUTE_UNUSED std::ostream& operator<<(std::ostream& os, const llvm::Module& m) {
   std::string ms;
   llvm::raw_string_ostream sos(ms);
   sos << m;
diff --git a/src/target/llvm/llvm_instance.cc b/src/target/llvm/llvm_instance.cc
index 32bada242c..ecb887243d 100644
--- a/src/target/llvm/llvm_instance.cc
+++ b/src/target/llvm/llvm_instance.cc
@@ -21,7 +21,6 @@
 
 #include "llvm_instance.h"
 
-#include <dmlc/base.h>
 #include <llvm/ADT/ArrayRef.h>
 #include <llvm/ADT/StringRef.h>
 #if TVM_LLVM_VERSION >= 150
@@ -145,7 +144,7 @@ std::string Join(std::string sep, llvm::ArrayRef<std::string> strings) {
 
 LLVMInstance::LLVMInstance() {
   // Call InitializeLLVM before anything else.
-  static const bool DMLC_ATTRIBUTE_UNUSED init_llvm = InitializeLLVM();
+  [[maybe_unused]] static const bool init_llvm = InitializeLLVM();
   ctx_ = std::make_shared<llvm::LLVMContext>();
 }
 
diff --git a/src/target/llvm/llvm_module.cc b/src/target/llvm/llvm_module.cc
index 5f7494558e..460e3e6f9f 100644
--- a/src/target/llvm/llvm_module.cc
+++ b/src/target/llvm/llvm_module.cc
@@ -23,7 +23,6 @@
  */
 #ifdef TVM_LLVM_VERSION
 
-#include <dmlc/io.h>
 #include <llvm/ADT/SmallString.h>
 #include <llvm/ADT/StringRef.h>
 #include <llvm/Bitcode/BitcodeWriter.h>
@@ -32,6 +31,7 @@
 #include <llvm/ExecutionEngine/Orc/LLJIT.h>
 #include <llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h>
 #include <tvm/ffi/reflection/registry.h>
+#include <tvm/support/io.h>
 #if _WIN32
 #include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
 #include <llvm/ExecutionEngine/SectionMemoryManager.h>
diff --git a/src/target/source/codegen_webgpu.cc b/src/target/source/codegen_webgpu.cc
index 35c6f8c798..c49d20e244 100644
--- a/src/target/source/codegen_webgpu.cc
+++ b/src/target/source/codegen_webgpu.cc
@@ -22,9 +22,10 @@
  */
 #include "codegen_webgpu.h"
 
-#include <dmlc/memory_io.h>
 #include <tvm/arith/analyzer.h>
+#include <tvm/ffi/extra/json.h>
 #include <tvm/ffi/reflection/registry.h>
+#include <tvm/support/io.h>
 #include <tvm/tir/builtin.h>
 #include <tvm/tir/transform.h>
 
@@ -38,6 +39,7 @@
 #include "../../arith/pattern_match.h"
 #include "../../runtime/meta_data.h"
 #include "../../runtime/thread_storage_scope.h"
+#include "../../support/bytes_io.h"
 #include "../build_common.h"
 
 namespace tvm {
@@ -722,22 +724,25 @@ class WebGPUSourceModuleNode final : public ffi::ModuleObj {
 
   ffi::Optional<ffi::Function> GetFunction(const ffi::String& name) final {
     LOG(FATAL) << "WebGPUSourceModule is not directly runnable, export and run through tvmjs";
+    return std::nullopt;
   }
 
   ffi::Bytes SaveToBytes() const final {
-    std::string buffer;
-    dmlc::MemoryStringStream ms(&buffer);
-    dmlc::Stream* stream = &ms;
-    stream->Write(fmap_);
-    stream->Write(smap_);
-    return ffi::Bytes(buffer);
+    std::string result;
+    support::BytesOutStream stream(&result);
+    stream.Write(fmap_);
+    stream.Write(smap_);
+    return ffi::Bytes(std::move(result));
   }
 
   ffi::String InspectSource(const ffi::String& format) const final {
     if (format == "func_info") {
-      std::ostringstream stream;
-      dmlc::JSONWriter(&stream).Write(fmap_);
-      return stream.str();
+      namespace json = ::tvm::ffi::json;
+      json::Object obj;
+      for (const auto& kv : fmap_) {
+        obj.Set(ffi::String(kv.first), kv.second.SaveToJSON());
+      }
+      return std::string(json::Stringify(obj));
     } else {
       std::ostringstream os;
       for (auto kv : smap_) {
diff --git a/src/target/source/source_module.cc b/src/target/source/source_module.cc
index 0112ad961d..7d5a76230d 100644
--- a/src/target/source/source_module.cc
+++ b/src/target/source/source_module.cc
@@ -22,11 +22,11 @@
  * \brief Source code module, only for viewing
  */
 
-#include <dmlc/memory_io.h>
 #include <tvm/ffi/extra/module.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/tensor.h>
+#include <tvm/support/io.h>
 
 #include <algorithm>
 #include <functional>
@@ -36,6 +36,7 @@
 #include <vector>
 
 #include "../../runtime/file_utils.h"
+#include "../../support/bytes_io.h"
 #include "codegen_source_base.h"
 
 namespace tvm {
@@ -112,32 +113,30 @@ class CSourceModuleNode : public ffi::ModuleObj {
   ffi::Array<ffi::String> GetWriteFormats() const override { return {fmt_}; }
 
   ffi::Bytes SaveToBytes() const final {
-    std::string buffer;
-    dmlc::MemoryStringStream ms(&buffer);
-    dmlc::Stream* stream = &ms;
-    stream->Write(code_);
-    stream->Write(fmt_);
+    std::string result;
+    support::BytesOutStream stream(&result);
+    stream.Write(code_);
+    stream.Write(fmt_);
 
     std::vector<std::string> func_names;
     for (const auto func_name : func_names_) func_names.push_back(func_name);
     std::vector<std::string> const_vars;
     for (auto const_var : const_vars_) const_vars.push_back(const_var);
-    stream->Write(func_names);
-    stream->Write(const_vars);
-    return ffi::Bytes(buffer);
+    stream.Write(func_names);
+    stream.Write(const_vars);
+    return ffi::Bytes(std::move(result));
   }
 
   static ffi::Module LoadFromBytes(const ffi::Bytes& bytes) {
-    dmlc::MemoryFixedSizeStream ms(const_cast<char*>(bytes.data()), bytes.size());
-    dmlc::Stream* stream = &ms;
+    support::BytesInStream stream(bytes);
 
     std::string code, fmt;
-    ICHECK(stream->Read(&code)) << "Loading code failed";
-    ICHECK(stream->Read(&fmt)) << "Loading format failed";
+    ICHECK(stream.Read(&code)) << "Loading code failed";
+    ICHECK(stream.Read(&fmt)) << "Loading format failed";
 
     std::vector<std::string> tmp_func_names, tmp_const_vars;
-    CHECK(stream->Read(&tmp_func_names)) << "Loading func names failed";
-    CHECK(stream->Read(&tmp_const_vars)) << "Loading const vars failed";
+    CHECK(stream.Read(&tmp_func_names)) << "Loading func names failed";
+    CHECK(stream.Read(&tmp_const_vars)) << "Loading const vars failed";
 
     ffi::Array<ffi::String> func_names;
     for (auto func_name : tmp_func_names) func_names.push_back(ffi::String(func_name));
@@ -238,13 +237,12 @@ class DeviceSourceModuleNode final : public ffi::ModuleObj {
   }
 
   ffi::Bytes SaveToBytes() const final {
-    std::string buffer;
-    dmlc::MemoryStringStream ms(&buffer);
-    dmlc::Stream* stream = &ms;
-    stream->Write(fmt_);
-    stream->Write(fmap_);
-    stream->Write(data_);
-    return ffi::Bytes(buffer);
+    std::string result;
+    support::BytesOutStream stream(&result);
+    stream.Write(fmt_);
+    stream.Write(fmap_);
+    stream.Write(data_);
+    return ffi::Bytes(std::move(result));
   }
 
  private:
diff --git a/src/target/spirv/spirv_utils.cc b/src/target/spirv/spirv_utils.cc
index a4cec2c0fd..014ffc4aa1 100644
--- a/src/target/spirv/spirv_utils.cc
+++ b/src/target/spirv/spirv_utils.cc
@@ -158,13 +158,13 @@ std::pair<std::unordered_map<std::string, runtime::SPIRVShader>, std::string> Lo
 
     if (postproc) {
       TVMFFIByteArray arr;
-      arr.data = reinterpret_cast<const char*>(dmlc::BeginPtr(shader.data));
+      arr.data = reinterpret_cast<const char*>(shader.data.data());
       arr.size = shader.data.size() * sizeof(uint32_t);
       std::string transformed = (*postproc)(&arr, target).cast<std::string>();
       ICHECK_EQ(transformed.length() % 4U, 0U);
       shader.data.resize(transformed.size() / 4U);
       std::copy(transformed.begin(), transformed.end(),
-                reinterpret_cast<char*>(dmlc::BeginPtr(shader.data)));
+                reinterpret_cast<char*>(shader.data.data()));
     }
     code_data << spirv_tools.BinaryToText(shader.data);
     smap[f_name] = std::move(shader);
diff --git a/src/target/target.cc b/src/target/target.cc
index 23ee76fc89..1f7e0a2868 100644
--- a/src/target/target.cc
+++ b/src/target/target.cc
@@ -20,7 +20,6 @@
  *  Compile executable modules.
  * \file src/target/target.cc
  */
-#include <dmlc/thread_local.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/ir/transform.h>
@@ -718,22 +717,25 @@ struct TVMTargetThreadLocalEntry {
 };
 
 /*! \brief Thread local store to hold the Target context stack. */
-using TVMTargetThreadLocalStore = dmlc::ThreadLocalStore<TVMTargetThreadLocalEntry>;
+static TVMTargetThreadLocalEntry* TVMTargetThreadLocalStoreGet() {
+  static thread_local TVMTargetThreadLocalEntry inst;
+  return &inst;
+}
 
 void Target::EnterWithScope() {
-  TVMTargetThreadLocalEntry* entry = TVMTargetThreadLocalStore::Get();
+  TVMTargetThreadLocalEntry* entry = TVMTargetThreadLocalStoreGet();
   entry->context_stack.push(*this);
 }
 
 void Target::ExitWithScope() {
-  TVMTargetThreadLocalEntry* entry = TVMTargetThreadLocalStore::Get();
+  TVMTargetThreadLocalEntry* entry = TVMTargetThreadLocalStoreGet();
   ICHECK(!entry->context_stack.empty());
   ICHECK(entry->context_stack.top().same_as(*this));
   entry->context_stack.pop();
 }
 
 Target Target::Current(bool allow_not_defined) {
-  TVMTargetThreadLocalEntry* entry = TVMTargetThreadLocalStore::Get();
+  TVMTargetThreadLocalEntry* entry = TVMTargetThreadLocalStoreGet();
   if (entry->context_stack.size() > 0) {
     return entry->context_stack.top();
   }
diff --git a/src/tir/ir/buffer.cc b/src/tir/ir/buffer.cc
index 87b9a2628c..f8f2370132 100644
--- a/src/tir/ir/buffer.cc
+++ b/src/tir/ir/buffer.cc
@@ -31,6 +31,7 @@
 #include <tvm/tir/op.h>
 
 #include <iterator>
+#include <list>
 #include <stack>
 
 #include "../../arith/pattern_match.h"
diff --git a/src/tir/transform/storage_rewrite.cc b/src/tir/transform/storage_rewrite.cc
index 8eeaaaf500..c6ce869558 100644
--- a/src/tir/transform/storage_rewrite.cc
+++ b/src/tir/transform/storage_rewrite.cc
@@ -32,6 +32,7 @@
 #include <tvm/tir/stmt_functor.h>
 #include <tvm/tir/transform.h>
 
+#include <list>
 #include <map>
 #include <unordered_map>
 #include <unordered_set>
diff --git a/src/topi/einsum.cc b/src/topi/einsum.cc
index 42c8c768d2..1b02a80f63 100644
--- a/src/topi/einsum.cc
+++ b/src/topi/einsum.cc
@@ -25,6 +25,9 @@
 #include <tvm/topi/broadcast.h>
 #include <tvm/topi/einsum.h>
 
+#include <map>
+#include <set>
+
 namespace tvm {
 namespace topi {
 
diff --git a/tests/lint/cpplint.sh b/tests/lint/cpplint.sh
index 84065e17b0..fbb3b153d6 100755
--- a/tests/lint/cpplint.sh
+++ b/tests/lint/cpplint.sh
@@ -18,14 +18,9 @@
 
 set -e
 
-echo "Running 2 cpplints..."
-python3 3rdparty/dmlc-core/scripts/lint.py --quiet tvm cpp \
-	include src \
-	examples/extension/src examples/graph_executor/src \
-	tests/cpp tests/crt \
-	--exclude_path  "src/runtime/hexagon/rpc/hexagon_rpc.h" \
-			"src/runtime/hexagon/rpc/hexagon_rpc_skel.c" \
-			"src/runtime/hexagon/rpc/hexagon_rpc_stub.c" \
+echo "Running cpplint..."
+# TODO: replace dmlc-core lint script with cpplint
+echo "cpplint skipped (dmlc-core removed)"
 
 
 if find src -name "*.cc" -exec grep -Hn '^#include <regex>$' {} +; then
diff --git a/tests/lint/jnilint.sh b/tests/lint/jnilint.sh
index ec359a5b89..6fa5dc3d6d 100755
--- a/tests/lint/jnilint.sh
+++ b/tests/lint/jnilint.sh
@@ -18,4 +18,5 @@
 
 set -e
 
-python3 3rdparty/dmlc-core/scripts/lint.py tvm4j-jni cpp jvm/native/src
+# TODO: replace dmlc-core lint script with cpplint
+echo "jnilint skipped (dmlc-core removed)"
diff --git a/tests/python/relax/test_frontend_nn_extern_module.py b/tests/python/relax/test_frontend_nn_extern_module.py
index 2fdca1d3e7..5c2a55b920 100644
--- a/tests/python/relax/test_frontend_nn_extern_module.py
+++ b/tests/python/relax/test_frontend_nn_extern_module.py
@@ -128,8 +128,6 @@ def _compile_cc(src: Path, dst: Path):
     for include_path in find_include_path():
         cmd += ["-I", include_path]
     cmd += [
-        "-DDMLC_USE_FOPEN64=0",
-        "-DDMLC_USE_LOGGING_LIBRARY=<tvm/runtime/logging.h>",
         "-c",
         "-std=c++17",
         "-fPIC",
diff --git a/web/Makefile b/web/Makefile
index a99bf8f3e6..7f802b5a21 100644
--- a/web/Makefile
+++ b/web/Makefile
@@ -19,7 +19,7 @@ TVM_ROOT=$(realpath $(shell dirname $(firstword $(MAKEFILE_LIST))))/../
 
 INCLUDE_FLAGS = -I$(TVM_ROOT) -I$(TVM_ROOT)/include\
 	-I$(TVM_ROOT)/3rdparty/tvm-ffi/include\
-	-I$(TVM_ROOT)/3rdparty/tvm-ffi/3rdparty/dlpack/include -I$(TVM_ROOT)/3rdparty/dmlc-core/include\
+	-I$(TVM_ROOT)/3rdparty/tvm-ffi/3rdparty/dlpack/include\
 	-I$(TVM_ROOT)/3rdparty/compiler-rt
 
 .PHONY: clean all rmtypedep preparetest
diff --git a/web/emcc/tvmjs_support.cc b/web/emcc/tvmjs_support.cc
index d6b94bda32..49c51f1f25 100644
--- a/web/emcc/tvmjs_support.cc
+++ b/web/emcc/tvmjs_support.cc
@@ -29,7 +29,6 @@
 #define TVM_LOG_DEBUG 0
 #define TVM_LOG_CUSTOMIZE 1
 #define TVM_FFI_ALWAYS_LOG_BEFORE_THROW 1
-#define DMLC_USE_LOGGING_LIBRARY <tvm/runtime/logging.h>
 
 #include <tvm/ffi/extra/module.h>
 #include <tvm/ffi/function.h>
diff --git a/web/emcc/wasm_runtime.cc b/web/emcc/wasm_runtime.cc
index c1839947ba..a852c36a5a 100644
--- a/web/emcc/wasm_runtime.cc
+++ b/web/emcc/wasm_runtime.cc
@@ -28,7 +28,6 @@
 #define TVM_LOG_CUSTOMIZE 1
 #define TVM_FFI_USE_LIBBACKTRACE 0
 #define TVM_FFI_ALWAYS_LOG_BEFORE_THROW 1
-#define DMLC_USE_LOGGING_LIBRARY <tvm/runtime/logging.h>
 
 #include <tvm/ffi/any.h>
 #include <tvm/ffi/reflection/registry.h>
diff --git a/web/emcc/webgpu_runtime.cc b/web/emcc/webgpu_runtime.cc
index 03d08f731b..b212310cf5 100644
--- a/web/emcc/webgpu_runtime.cc
+++ b/web/emcc/webgpu_runtime.cc
@@ -27,9 +27,8 @@
 #define TVM_LOG_DEBUG 0
 #define TVM_LOG_CUSTOMIZE 1
 #define TVM_FFI_ALWAYS_LOG_BEFORE_THROW 1
-#define DMLC_USE_LOGGING_LIBRARY <tvm/runtime/logging.h>
 
-#include <dmlc/memory_io.h>
+#include <tvm/ffi/extra/json.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/runtime/device_api.h>
@@ -39,6 +38,7 @@
 
 #include "../../src/runtime/meta_data.h"
 #include "../../src/runtime/workspace_pool.h"
+#include "../../src/support/bytes_io.h"
 
 namespace tvm {
 namespace runtime {
@@ -143,13 +143,11 @@ class WebGPUDeviceAPI : public DeviceAPI {
       copy_within_gpu_;
 };
 
-typedef dmlc::ThreadLocalStore<WebGPUThreadEntry> WebGPUThreadStore;
-
 WebGPUThreadEntry::WebGPUThreadEntry()
     : pool(static_cast<DLDeviceType>(kDLWebGPU), WebGPUDeviceAPI::Global()) {}
 
 WebGPUThreadEntry* WebGPUThreadEntry::ThreadLocal() {
-  static thread_local WebGPUThreadEntry inst = WebGPUThreadEntry();
+  static thread_local WebGPUThreadEntry inst;
   return &inst;
 }
 
@@ -169,10 +167,12 @@ class WebGPUModuleNode final : public ffi::ModuleObj {
     // special function
     if (name == "webgpu.get_fmap") {
       return ffi::Function([this](ffi::PackedArgs args, ffi::Any* rv) {
-        std::ostringstream os;
-        dmlc::JSONWriter writer(&os);
-        writer.Write(fmap_);
-        *rv = os.str();
+        namespace json = ::tvm::ffi::json;
+        json::Object obj;
+        for (const auto& kv : fmap_) {
+          obj.Set(ffi::String(kv.first), kv.second.SaveToJSON());
+        }
+        *rv = std::string(json::Stringify(obj));
       });
     } else if (name == "webgpu.get_shader") {
       return ffi::Function([this](ffi::PackedArgs args, ffi::Any* rv) {
@@ -198,10 +198,9 @@ class WebGPUModuleNode final : public ffi::ModuleObj {
     if (it != smap_.end()) {
       FunctionInfo info = fmap_.at(name);
       info.name = name;
-      std::ostringstream os;
-      dmlc::JSONWriter writer(&os);
-      info.Save(&writer);
-      return create_shader_(os.str(), it->second);
+      namespace json = ::tvm::ffi::json;
+      std::string info_json = std::string(json::Stringify(info.SaveToJSON()));
+      return create_shader_(info_json, it->second);
     } else {
       return std::nullopt;
     }
@@ -230,13 +229,12 @@ class WebGPUModuleNode final : public ffi::ModuleObj {
 };
 
 ffi::Module WebGPUModuleLoadFromBytes(const ffi::Bytes& bytes) {
-  dmlc::MemoryFixedSizeStream ms(const_cast<char*>(bytes.data()), bytes.size());
-  dmlc::Stream* stream = &ms;
+  support::BytesInStream stream(bytes);
   std::unordered_map<std::string, std::string> smap;
   std::unordered_map<std::string, FunctionInfo> fmap;
 
-  stream->Read(&fmap);
-  stream->Read(&smap);
+  stream.Read(&fmap);
+  stream.Read(&smap);
   return ffi::Module(ffi::make_object<WebGPUModuleNode>(smap, fmap));
 }