Repository: PaddlePaddle/Paddle
Pull request #71721: [XPU] feat: implement xpu pinned memory and sync load with pinned memory, split commit 2
Author: login: tizhou86
Labels: name: XPU
Created: 2025-03-17T09:26:36Z | Merged: 2025-03-18T08:05:10Z

<!-- TemplateReference: https://github.com/PaddlePaddle/Paddle/wiki/PULL-REQUEST-TEMPLATE--REFERENCE -->
<!-- Demo: https://github.com/PaddlePaddle/Paddle/pull/24810 -->

### PR Category
<!-- One of [ User Experience | Execute Infrastructure | Operator Mechanism | CINN | Custom Device | Performance Optimization | Distributed Strategy | Parameter Server | Communication Library | Auto Parallel | Inference | Environment Adaptation ] -->
Custom Device

### PR Types
<!-- One of [ New features | Bug fixes | Improvements | Performance | BC Breaking | Deprecations | Docs | Devs | Not User Facing | Security | Deprecations | Others ] -->
New features

### Description
<!-- Describe what you’ve done -->
Implement xpu pinned memory and sync load with pinned memory, split commit 2
Original commit pr: https://github.com/PaddlePaddle/Paddle/pull/71522
This commit:
- Extend memory copy routines to support XPUPinnedPlace.
- Add XPUPinnedContext and update the XPU device context infrastructure.
- Update XpuAsyncLoad offload/reload to support XPUPinned.
- Update platform initialization and memory method callbacks.
- Adjust C++ tests to accommodate device context initialization.


Diff:
diff --git a/paddle/fluid/distributed/collective/xpu_async_load.cc b/paddle/fluid/distributed/collective/xpu_async_load.cc
index 90d4a3415b69e..f22624df936f4 100644
--- a/paddle/fluid/distributed/collective/xpu_async_load.cc
+++ b/paddle/fluid/distributed/collective/xpu_async_load.cc
@@ -12,249 +12,306 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "paddle/fluid/distributed/collective/xpu_async_load.h"
+#include <chrono>
+#include <ctime>
+#include <iomanip>
+#include <iostream>
+#include <memory>
+#include <sstream>
 
-#include "paddle/fluid/platform/enforce.h"
-#include "paddle/phi/common/memory_utils.h"  // phi::memory_utils::Copy
-// #include "paddle/phi/core/device_context_pool.h" // for DeviceContextPool
-// #include "paddle/phi/core/places.h"              // phi::is_xpu_place(...)
-#include "paddle/phi/core/compat/convert_utils.h"
+#include "paddle/fluid/distributed/collective/xpu_async_load.h"
+#include "paddle/phi/backends/xpu/xpu_context.h"  // For phi::XPUContext
+#include "paddle/phi/common/memory_utils.h"
+#include "paddle/phi/common/place.h"
+#include "paddle/phi/core/memory/allocation/allocator_facade.h"
 
 namespace paddle {
 namespace distributed {
 
-using phi::is_xpu_place;
-
-/**
- * Helper: Insert or retrieve a DeviceEvent in the map without
- * default-constructing it.
- *   - If place is XPU, we skip event usage entirely (dummy).
- *   - If place is NOT XPU, we create a DeviceEvent with the needed constructor.
- */
-static platform::DeviceEvent& GetOrCreateEvent(
-    std::unordered_map<std::string, platform::DeviceEvent>* event_map,
-    const std::string& key,
-    const phi::Place& place) {
-  // If it's XPU, we do a "dummy" CPU-based event or skip
-  // (but let's store a CPU event just so we can return a reference).
-  // In a real design, you might do a separate approach.
-
-  phi::Place event_place = is_xpu_place(place) ? phi::CPUPlace() : place;
-  unsigned int flags = platform::GenerateDeviceEventFlag();
-
-  auto it = event_map->find(key);
-  if (it == event_map->end()) {
-    // Insert using piecewise_construct to avoid default constructor
-    auto emplace_result =
-        event_map->emplace(std::piecewise_construct,
-                           std::forward_as_tuple(key),
-                           std::forward_as_tuple(event_place, flags));
-    it = emplace_result.first;  // newly inserted
-  }
-  return it->second;
-}
-
-/* ------------------- Task Implementation ------------------- */
+//
+// Implementation of XpuAsyncLoad::Task
+//
 
 XpuAsyncLoad::Task::Task(const Place& place)
-    : use_event_(!is_xpu_place(place)),
-      // If place is XPU, we store a CPU event just so load_event_ is valid
-      // (some dummy fallback, we won't really use it)
-      load_event_(use_event_ ? place : phi::CPUPlace(),
-                  platform::GenerateDeviceEventFlag()),
-      task_place_(place) {}
+    : task_place_(place), event_manager_(std::make_shared<XPUEventManager>()) {
+  VLOG(6) << "Created task for place: " << task_place_;
+}
 
-XpuAsyncLoad::Task::~Task() = default;
+XpuAsyncLoad::Task::~Task() {}
 
 bool XpuAsyncLoad::Task::IsCompleted() {
-  if (!use_event_) {
-    // For XPU, skip real event usage and just say "complete"
-    return true;
-  }
-  return load_event_.Query();
+  // XPU event query is not supported; assume the task is complete.
+  return true;
 }
 
-// Example fix in Task::XpuSynchronize():
 void XpuAsyncLoad::Task::XpuSynchronize() {
-  if (!use_event_) {
-    return;
-  }
-  auto* calc_ctx = phi::DeviceContextPool::Instance().Get(task_place_);
-  // OLD (won't compile in your version):
-  //   auto backend = task_place_.GetBackend();
-  //   load_event_.Wait(backend, calc_ctx);
-  // NEW:
-  load_event_.Wait(platform::Place2DeviceType(task_place_), calc_ctx);
+  auto* ctx = static_cast<phi::XPUContext*>(
+      phi::DeviceContextPool::Instance().Get(task_place_));
+  ctx->Wait();
 }
 
 void XpuAsyncLoad::Task::CpuSynchronize() {
-  if (!use_event_) {
-    return;
-  }
-  load_event_.Finish();
+  auto* ctx = static_cast<phi::XPUContext*>(
+      phi::DeviceContextPool::Instance().Get(task_place_));
+  ctx->Wait();
 }
 
 void XpuAsyncLoad::Task::UpdateWaitChain(const phi::DeviceContext& ctx) {
-  if (!use_event_) {
-    // skip
-    return;
+  auto* xpu_ctx = dynamic_cast<const phi::XPUContext*>(&ctx);
+  if (xpu_ctx) {
+    VLOG(6) << "UpdateWaitChain: Recording event on XPU context";
+    event_manager_->Record(*xpu_ctx);
   }
-  load_event_.Record(&ctx);
 }
 
-/* ------------------- XpuAsyncLoad Implementation ------------------- */
+//
+// Implementation of XpuAsyncLoad methods
+//
 
 std::shared_ptr<XpuAsyncLoad::Task> XpuAsyncLoad::CreateTask(
     const Place& place) {
-  return std::make_shared<XpuAsyncLoad::Task>(place);
+  return std::make_shared<Task>(place);
 }
 
-void XpuAsyncLoad::PrepareLoadEnv(const std::string& key, const Place& place) {
-  if (!is_initialized_) {
-    is_initialized_ = true;
-    xpu_place_ = place;
-    // If not XPU, create a real event; if XPU, we store a dummy CPU event
-    (void)GetOrCreateEvent(&place_to_calc_event_, key, place);
-
-    // Create an XPUContext for the offload
-    load_ctx_ = std::make_unique<phi::XPUContext>(place);
-  }
+void XpuAsyncLoad::SyncCalcStream(const Place& /*place*/,
+                                  phi::XPUContext* ctx,
+                                  XPUEventManager* event_manager) {
+  VLOG(6) << "[SyncCalcStream] Recording event and blocking on context.";
+  event_manager->Record(*ctx);
+  event_manager->Block(*ctx);
 }
 
-// Another fix in SyncCalcuStream():
-void XpuAsyncLoad::SyncCalcuStream(const Place& place,
-                                   phi::XPUContext* offload_ctx,
-                                   platform::DeviceEvent* calc_event) {
-  if (is_xpu_place(place)) {
-    // skip or do fallback
-    return;
-  }
-  auto* calc_ctx = phi::DeviceContextPool::Instance().Get(place);
-  calc_event->Record(calc_ctx);
-  // OLD (won't compile):
-  //   auto backend = place.GetBackend();
-  //   calc_event.Wait(backend, offload_ctx);
-  // NEW:
-  calc_event->Wait(platform::Place2DeviceType(place), offload_ctx);
+// Helper function to get the current timestamp as a string.
+std::string currentTimestamp() {
+  auto now = std::chrono::system_clock::now();
+  auto now_time_t = std::chrono::system_clock::to_time_t(now);
+  std::stringstream ss;
+  ss << std::put_time(std::localtime(&now_time_t), "%F %T");
+  return ss.str();
 }
 
-/* ------------ Offload (XPU -> CPU pinned or CPU) ------------ */
 std::shared_ptr<XpuAsyncLoad::Task> XpuAsyncLoad::Offload(
     phi::DenseTensor* dst, const phi::DenseTensor& src) {
-  PADDLE_ENFORCE_EQ(
-      is_xpu_place(src.place()),
-      true,
-      phi::errors::InvalidArgument("Offload only supports XPU source."));
-
-  std::string key = "load_key";
-  PrepareLoadEnv(key, src.place());
-  // retrieve or create the event
-  auto& calc_event = GetOrCreateEvent(&place_to_calc_event_, key, src.place());
-  // sync
-  SyncCalcuStream(xpu_place_, load_ctx_.get(), &calc_event);
-
-  // do synchronous copy to CPU
+  // XPU -> XPUPinned
+  const auto& place = src.place();
+  VLOG(6) << "src place is: " << phi::AllocationTypeStr(src.place().GetType());
+
+  PADDLE_ENFORCE_EQ(phi::is_xpu_place(place),
+                    true,
+                    common::errors::InvalidArgument(
+                        "XpuAsyncLoad::Offload only supports XPU -> XPUPinned "
+                        "now, src place is not correct"));
+
+  VLOG(6) << "[Offload] Offloading tensor from place: " << place;
   dst->Resize(src.dims());
-  size_t size = src.numel() * phi::SizeOf(src.dtype());
-  auto cpu_place = phi::CPUPlace();
-  auto* cpu_ctx = phi::DeviceContextPool::Instance().Get(cpu_place);
-  void* dst_ptr = cpu_ctx->Alloc(dst, src.dtype(), size);
-  const void* src_ptr = src.data();
-
-  phi::memory_utils::Copy(cpu_place,
-                          dst_ptr,
-                          src.place(),
-                          src_ptr,
-                          size,
-                          /*stream=*/nullptr);
-
-  auto task = CreateTask(src.place());
-  task->UpdateWaitChain(*load_ctx_);
+  auto size = src.numel() * phi::SizeOf(src.dtype());
+  VLOG(6) << "[Offload] Tensor size in bytes: " << size;
+
+  auto* dev_ctx = static_cast<phi::XPUContext*>(
+      phi::DeviceContextPool::Instance().Get(place));
+
+  // Set allocators for dev_ctx using the AllocatorFacade.
+  dev_ctx->SetAllocator(memory::allocation::AllocatorFacade::Instance()
+                            .GetAllocator(place)
+                            .get());
+  dev_ctx->SetPinnedAllocator(memory::allocation::AllocatorFacade::Instance()
+                                  .GetAllocator(::phi::GetPinnedPlace(place))
+                                  .get());
+
+  VLOG(6) << "[Offload] Start Allocating destination pinned memory";
+
+  // Allocate pinned memory (true)
+  auto* dst_ptr = dev_ctx->Alloc(dst, src.dtype(), size, true);
+
+  VLOG(6) << "[Offload] Allocated destination pinned memory at: " << dst_ptr;
+  auto* src_ptr = src.data();
+  VLOG(6) << "[Offload] Source data pointer: " << src_ptr;
+
+  std::string key = "load";
+  if (!is_initialized_) {
+    is_initialized_ = true;
+    xpu_place_ = place;
+    VLOG(6) << "[Offload] Initializing load environment on place: " << place;
+    // Create and store an XPUEventManager for this key.
+    place_to_calc_event_.emplace(key, XPUEventManager());
+  }
+
+  VLOG(6) << "dst place is: " << phi::AllocationTypeStr(dst->place().GetType());
+  PADDLE_ENFORCE_EQ(phi::is_xpu_pinned_place(dst->place()),
+                    true,
+                    common::errors::InvalidArgument(
+                        "XpuAsyncLoad::Offload only supports XPU -> XPUPinned "
+                        "now, dst place is not correct"));
+
+  // Perform the synchronous memory copy.
+  VLOG(6) << "[Offload] Copying memory from src (" << src_ptr << ") to dst ("
+          << dst_ptr << ") size " << size;
+  phi::memory_utils::Copy(dst->place(), dst_ptr, src.place(), src_ptr, size);
+  dev_ctx->Wait();
+  // SyncCalcStream(xpu_place_, dev_ctx, &place_to_calc_event_.at(key));
+  // auto stream = dev_ctx->stream();
+  // phi::memory_utils::Copy(dst->place(), dst_ptr, src.place(), src_ptr, size,
+  // stream);
+  VLOG(6) << "[Offload] Copy complete; device context waited.";
+
+  auto task = CreateTask(place);
+  task->UpdateWaitChain(*dev_ctx);
   return task;
 }
 
-/* ------------ OffloadWithOffset (XPU -> CPU partial) ------------ */
 std::shared_ptr<XpuAsyncLoad::Task> XpuAsyncLoad::OffloadWithOffset(
     phi::DenseTensor* dst,
     const phi::DenseTensor& src,
     size_t dst_offset,
     size_t src_offset,
     size_t offload_size) {
-  PADDLE_ENFORCE_EQ(
-      is_xpu_place(src.place()),
-      true,
-      phi::errors::InvalidArgument("OffloadWithOffset requires XPU source."));
+  // XPU -> XPUPinned
+  const auto& place = src.place();
+  VLOG(6) << "src place is: " << phi::AllocationTypeStr(src.place().GetType());
 
+  PADDLE_ENFORCE_EQ(phi::is_xpu_place(place),
+                    true,
+                    common::errors::InvalidArgument(
+                        "XpuAsyncLoad::OffloadWithOffset only supports XPU src "
+                        "now, src place is not correct"));
   PADDLE_ENFORCE_EQ(dst->initialized(),
                     true,
-                    phi::errors::PreconditionNotMet(
-                        "dst must be initialized for partial offload."));
-
-  PADDLE_ENFORCE_LE(
-      src_offset + offload_size,
-      src.numel(),
-      phi::errors::InvalidArgument("src offset + size out of range."));
-  PADDLE_ENFORCE_LE(
-      dst_offset + offload_size,
-      dst->numel(),
-      phi::errors::InvalidArgument("dst offset + size out of range."));
-
-  std::string key = "load_key";
-  PrepareLoadEnv(key, src.place());
-  auto& calc_event = GetOrCreateEvent(&place_to_calc_event_, key, src.place());
-  SyncCalcuStream(xpu_place_, load_ctx_.get(), &calc_event);
-
-  size_t elem_size = phi::SizeOf(src.dtype());
-  size_t copy_bytes = offload_size * elem_size;
-  const void* src_ptr =
-      static_cast<const char*>(src.data()) + src_offset * elem_size;
-  void* dst_ptr = static_cast<char*>(dst->data()) + dst_offset * elem_size;
-
-  phi::memory_utils::Copy(dst->place(),
-                          dst_ptr,
-                          src.place(),
-                          src_ptr,
-                          copy_bytes,
-                          /*stream=*/nullptr);
-
-  auto task = CreateTask(src.place());
-  task->UpdateWaitChain(*load_ctx_);
+                    common::errors::PreconditionNotMet(
+                        "XpuAsyncLoad::OffloadWithOffset requires initialized "
+                        "tensors for both dst and src."));
+  PADDLE_ENFORCE_LE(src_offset + offload_size,
+                    src.numel(),
+                    common::errors::InvalidArgument(
+                        "XpuAsyncLoad::OffloadWithOffset: src_offset + "
+                        "offload_size must be <= src tensor size."));
+  PADDLE_ENFORCE_LE(dst_offset + offload_size,
+                    dst->numel(),
+                    common::errors::InvalidArgument(
+                        "XpuAsyncLoad::OffloadWithOffset: dst_offset + "
+                        "offload_size must be <= dst tensor size."));
+
+  VLOG(6) << "[OffloadWithOffset] Offloading with offset; offload_size: "
+          << offload_size;
+  auto size_in_bytes = offload_size * phi::SizeOf(src.dtype());
+  auto src_offset_in_bytes = src_offset * phi::SizeOf(src.dtype());
+  auto dst_offset_in_bytes = dst_offset * phi::SizeOf(src.dtype());
+
+  auto* dst_ptr = dst->data();
+  auto* src_ptr = src.data();
+  VLOG(6) << "[OffloadWithOffset] Original dst pointer: " << dst_ptr
+          << ", src pointer: " << src_ptr;
+  auto* dst_ptr_tmp = static_cast<char*>(dst_ptr);
+  auto* src_ptr_tmp = static_cast<const char*>(src_ptr);
+  dst_ptr = static_cast<void*>(dst_ptr_tmp + dst_offset_in_bytes);
+  src_ptr = static_cast<const void*>(src_ptr_tmp + src_offset_in_bytes);
+  VLOG(6) << "[OffloadWithOffset] Adjusted dst pointer: " << dst_ptr
+          << ", src pointer: " << src_ptr;
+
+  auto* dev_ctx = static_cast<phi::XPUContext*>(
+      phi::DeviceContextPool::Instance().Get(place));
+
+  dev_ctx->SetAllocator(memory::allocation::AllocatorFacade::Instance()
+                            .GetAllocator(place)
+                            .get());
+  dev_ctx->SetPinnedAllocator(memory::allocation::AllocatorFacade::Instance()
+                                  .GetAllocator(::phi::GetPinnedPlace(place))
+                                  .get());
+
+  std::string key = "load";
+  if (!is_initialized_) {
+    is_initialized_ = true;
+    xpu_place_ = place;
+    VLOG(6) << "[OffloadWithOffset] Initializing load environment with "
+               "offset on place: "
+            << place;
+    place_to_calc_event_.emplace(key, XPUEventManager());
+  }
+
+  VLOG(6) << "dst place is: " << phi::AllocationTypeStr(dst->place().GetType());
+  PADDLE_ENFORCE_EQ(phi::is_xpu_pinned_place(dst->place()),
+                    true,
+                    common::errors::InvalidArgument(
+                        "XpuAsyncLoad::Offload only supports XPU -> XPUPinned "
+                        "now, dst place is not correct"));
+
+  VLOG(6) << "[OffloadWithOffset] Copying " << size_in_bytes
+          << " bytes with offset.";
+  phi::memory_utils::Copy(
+      dst->place(), dst_ptr, src.place(), src_ptr, size_in_bytes);
+  dev_ctx->Wait();
+  // SyncCalcStream(xpu_place_, dev_ctx, &place_to_calc_event_.at(key));
+  // auto stream = dev_ctx->stream();
+  // phi::memory_utils::Copy(dst->place(), dst_ptr, src.place(), src_ptr,
+  // size_in_bytes, stream);
+  VLOG(6) << "[OffloadWithOffset] Copy complete; waited on dev_ctx.";
+
+  auto task = CreateTask(place);
+  task->UpdateWaitChain(*dev_ctx);
   return task;
 }
 
-/* ------------ Reload (CPU -> XPU) ------------ */
 std::shared_ptr<XpuAsyncLoad::Task> XpuAsyncLoad::Reload(
     phi::DenseTensor* dst, const phi::DenseTensor& src) {
-  PADDLE_ENFORCE_EQ(
-      is_initialized_,
-      true,
-      phi::errors::PreconditionNotMet("Call Offload before Reload."));
-
-  // Possibly we check if src is CPU or pinned place
-  // We'll skip that check or treat it as CPU place
-  std::string key = "load_key";
-  auto& calc_event = GetOrCreateEvent(&place_to_calc_event_, key, xpu_place_);
-  SyncCalcuStream(xpu_place_, load_ctx_.get(), &calc_event);
-
-  // Now do CPU->XPU
-  dst->Resize(src.dims());
-  size_t size = src.numel() * phi::SizeOf(src.dtype());
+  // XPUPinned -> XPU
+  const auto& place = src.place();
+  VLOG(6) << "src place is: " << phi::AllocationTypeStr(src.place().GetType());
 
-  auto* xpu_ctx = phi::DeviceContextPool::Instance().Get(xpu_place_);
-  void* dst_ptr = xpu_ctx->Alloc(dst, src.dtype(), size, /*pinned=*/false);
-  const void* src_ptr = src.data();
+  PADDLE_ENFORCE_EQ(phi::is_xpu_pinned_place(place),
+                    true,
+                    common::errors::InvalidArgument(
+                        "XpuAsyncLoad::Reload only supports XPUPinned -> XPU "
+                        "now, src place is not correct"));
+
+  PADDLE_ENFORCE_EQ(is_initialized_,
+                    true,
+                    common::errors::PreconditionNotMet(
+                        "You must call Offload before Reload."));
+
+  VLOG(6) << "[Reload] Reloading tensor from XPUPinned to XPU.";
+  auto* dev_ctx = static_cast<phi::XPUContext*>(
+      phi::DeviceContextPool::Instance().Get(xpu_place_));
 
-  phi::memory_utils::Copy(xpu_place_,
-                          dst_ptr,
-                          src.place(),
-                          src_ptr,
-                          size,
-                          /*stream=*/nullptr);
+  dst->Resize(src.dims());
+  auto size = src.numel() * phi::SizeOf(src.dtype());
+  VLOG(6) << "[Reload] Allocating destination XPU memory; size: " << size;
+  auto* dst_ptr = dev_ctx->Alloc(dst, src.dtype(), size, /*pinned=*/false);
+  auto* src_ptr = src.data();
+
+  VLOG(6) << "dst place is: " << phi::AllocationTypeStr(dst->place().GetType());
+  PADDLE_ENFORCE_EQ(phi::is_xpu_place(dst->place()),
+                    true,
+                    common::errors::InvalidArgument(
+                        "XpuAsyncLoad::Offload only supports XPU -> XPUPinned "
+                        "now, dst place is not correct"));
+
+  VLOG(6) << "[Reload] Copying data from pinned src (" << src_ptr
+          << ") to XPU dst (" << dst_ptr << ")";
+  phi::memory_utils::Copy(dst->place(), dst_ptr, src.place(), src_ptr, size);
+  dev_ctx->Wait();
+  // std::string key = "load";
+  // SyncCalcStream(xpu_place_, dev_ctx, &place_to_calc_event_.at(key));
+  // auto stream = dev_ctx->stream();
+  // phi::memory_utils::Copy(dst->place(), dst_ptr, src.place(), src_ptr, size,
+  // stream);
+  VLOG(6) << "[Reload] Reload complete; waited on dev_ctx.";
 
   auto task = CreateTask(xpu_place_);
-  task->UpdateWaitChain(*load_ctx_);
+  task->UpdateWaitChain(*dev_ctx);
   return task;
 }
 
+void XpuAsyncLoad::PrepareLoadEnv(const std::string& key, const Place& place) {
+  if (!is_initialized_) {
+    is_initialized_ = true;
+    xpu_place_ = place;
+    VLOG(6) << "[PrepareLoadEnv] Initializing environment with key: " << key
+            << " for place: " << place;
+    place_to_calc_event_.emplace(key, XPUEventManager());
+    // Optionally, one can initialize a load context here.
+    // For example:
+    // load_ctx_ = std::make_unique<phi::XPUContext>(place);
+  }
+}
+
 }  // namespace distributed
 }  // namespace paddle
diff --git a/paddle/fluid/distributed/collective/xpu_async_load.h b/paddle/fluid/distributed/collective/xpu_async_load.h
index 71d8efd49d2e7..03e4a3f0ae2c5 100644
--- a/paddle/fluid/distributed/collective/xpu_async_load.h
+++ b/paddle/fluid/distributed/collective/xpu_async_load.h
@@ -10,84 +10,80 @@
 // 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.#pragma once
+// limitations under the License.
 
 #pragma once
 
+#include <chrono>
 #include <memory>
 #include <string>
 #include <unordered_map>
+#include <vector>
 
-// #include "paddle/fluid/platform/device_context.h"
-// #include "paddle/phi/backends/xpu/xpu_context.h"
+#include "paddle/common/errors.h"
 #include "paddle/phi/core/dense_tensor.h"
-#include "paddle/phi/core/platform/device_event_base.h"
-// #include "paddle/phi/core/places.h"
+#include "paddle/phi/core/device_context.h"
+#include "paddle/phi/core/tensor_utils.h"
+
+#include "paddle/fluid/distributed/collective/bkcl_tools.h"
+#include "paddle/fluid/distributed/collective/common.h"
+
+// Include the allocator facade header.
+#include "paddle/phi/core/memory/allocation/allocator_facade.h"
 
 namespace paddle {
 namespace distributed {
 
 using Place = phi::Place;
 
-/**
- * AsyncLoad that does NOT use platform::DeviceEvent if place == XPU.
- */
 class XpuAsyncLoad {
  public:
+  // Task represents an asynchronous offload/reload operation.
   class Task {
    public:
     explicit Task(const Place& place);
     virtual ~Task();
-
     bool IsCompleted();
-
-    // Replaces CudaSynchronize with XpuSynchronize
     void XpuSynchronize();
     void CpuSynchronize();
-
-    // If not XPU, record the event. If XPU, do nothing
     void UpdateWaitChain(const phi::DeviceContext& ctx);
 
    private:
-    bool use_event_;  // false if place is XPU
-    platform::DeviceEvent load_event_;
     Place task_place_;
+    std::shared_ptr<XPUEventManager> event_manager_;
   };
 
-  // Offload
+  // Offload from XPU to XPUPinned memory.
   std::shared_ptr<Task> Offload(phi::DenseTensor* dst,
                                 const phi::DenseTensor& src);
 
-  // OffloadWithOffset
+  // Offload a portion (with offset) from XPU to XPUPinned memory.
   std::shared_ptr<Task> OffloadWithOffset(phi::DenseTensor* dst,
                                           const phi::DenseTensor& src,
                                           size_t dst_offset,
                                           size_t src_offset,
                                           size_t offload_size);
 
-  // Reload
+  // Reload data from XPUPinned memory back to XPU.
   std::shared_ptr<Task> Reload(phi::DenseTensor* dst,
                                const phi::DenseTensor& src);
 
- private:
-  bool is_initialized_{false};
+  // Prepare the load environment (if needed).
+  void PrepareLoadEnv(const std::string& key, const Place& place);
 
-  // A fallback "offload context," though we won't do multi-stream sync for XPU
-  std::unique_ptr<phi::XPUContext> load_ctx_;
-  Place xpu_place_;
+  // Instead of using a device event’s Record/Wait, we use the XPUEventManager.
+  void SyncCalcStream(const Place& place,
+                      phi::XPUContext* ctx,
+                      XPUEventManager* event_manager);
 
+ private:
   std::shared_ptr<Task> CreateTask(const Place& place);
 
-  // If not XPU, store calc-event. If XPU, skip
-  std::unordered_map<std::string, platform::DeviceEvent> place_to_calc_event_;
-
-  // Prepare env
-  void PrepareLoadEnv(const std::string& key, const Place& place);
-
-  // If not XPU, do event sync. If XPU, skip
-  void SyncCalcuStream(const Place& place,
-                       phi::XPUContext* offload_ctx,
-                       platform::DeviceEvent* calc_event);
+  // Map from a key (e.g. "load") to an XPUEventManager instance.
+  std::unordered_map<std::string, XPUEventManager> place_to_calc_event_;
+  bool is_initialized_{false};
+  // std::unique_ptr<phi::XPUContext> load_ctx_;
+  Place xpu_place_;
 };
 
 }  // namespace distributed
diff --git a/paddle/fluid/framework/dlpack_tensor.cc b/paddle/fluid/framework/dlpack_tensor.cc
index 99808691e108e..e227223e57616 100644
--- a/paddle/fluid/framework/dlpack_tensor.cc
+++ b/paddle/fluid/framework/dlpack_tensor.cc
@@ -95,6 +95,18 @@ struct DLDeviceVisitor {
         common::errors::Unimplemented("phi::XPUPlace is not supported"));
   }
 
+  inline ::DLDevice operator()(const phi::XPUPinnedPlace &place) const {
+#if defined(PADDLE_WITH_XPU)
+    ::DLDevice device;
+    device.device_type = kDLCUDAHost;
+    device.device_id = 0;
+    return device;
+#else
+    PADDLE_THROW(common::errors::Unavailable(
+        "phi::XPUPinnedPlace is not supported in CPU only version."));
+#endif
+  }
+
   inline ::DLDevice operator()(const phi::CustomPlace &place) const {
     PADDLE_THROW(
         common::errors::Unimplemented("phi::CustomPlace is not supported"));
diff --git a/paddle/fluid/framework/garbage_collector.cc b/paddle/fluid/framework/garbage_collector.cc
index fd65efd946356..23faa5ad8e2ff 100644
--- a/paddle/fluid/framework/garbage_collector.cc
+++ b/paddle/fluid/framework/garbage_collector.cc
@@ -52,6 +52,15 @@ XPUGarbageCollector::XPUGarbageCollector(const phi::XPUPlace &place,
 void XPUGarbageCollector::ClearCallback(const std::function<void()> &callback) {
   callback();
 }
+
+XPUPinnedGarbageCollector::XPUPinnedGarbageCollector(
+    const phi::XPUPinnedPlace &place, size_t max_memory_size)
+    : GarbageCollector(place, max_memory_size) {}
+
+void XPUPinnedGarbageCollector::ClearCallback(
+    const std::function<void()> &callback) {
+  callback();
+}
 #endif
 
 #ifdef PADDLE_WITH_IPU
diff --git a/paddle/fluid/framework/garbage_collector.h b/paddle/fluid/framework/garbage_collector.h
index 3f97be1112aae..85da50a60adaa 100644
--- a/paddle/fluid/framework/garbage_collector.h
+++ b/paddle/fluid/framework/garbage_collector.h
@@ -73,6 +73,15 @@ class XPUGarbageCollector : public GarbageCollector {
  protected:
   void ClearCallback(const std::function<void()> &callback) override;
 };
+
+class XPUPinnedGarbageCollector : public GarbageCollector {
+ public:
+  XPUPinnedGarbageCollector(const phi::XPUPinnedPlace &place,
+                            size_t max_memory_size);
+
+ protected:
+  void ClearCallback(const std::function<void()> &callback) override;
+};
 #endif
 
 #ifdef PADDLE_WITH_IPU
diff --git a/paddle/fluid/inference/api/analysis_predictor.cc b/paddle/fluid/inference/api/analysis_predictor.cc
index 41eee64a3d5c1..341ebe963ce1e 100644
--- a/paddle/fluid/inference/api/analysis_predictor.cc
+++ b/paddle/fluid/inference/api/analysis_predictor.cc
@@ -754,6 +754,10 @@ void AnalysisPredictor::InitDeviceContexts() {
           xpu_context->SetAllocator(instance.GetAllocator(place_).get());
           xpu_context->SetGenerator(
               phi::DefaultXPUGenerator(place_.GetDeviceId()).get());
+          xpu_context->SetPinnedAllocator(
+              memory::allocation::AllocatorFacade::Instance()
+                  .GetAllocator(phi::XPUPinnedPlace())
+                  .get());
           xpu_context->SetHostAllocator(
               instance.GetAllocator(phi::CPUPlace()).get());
           xpu_context->SetHostGenerator(phi::DefaultCPUGenerator().get());
diff --git a/paddle/fluid/pir/dialect/operator/utils/utils.cc b/paddle/fluid/pir/dialect/operator/utils/utils.cc
index 64d4694488ca4..80f55aec80196 100644
--- a/paddle/fluid/pir/dialect/operator/utils/utils.cc
+++ b/paddle/fluid/pir/dialect/operator/utils/utils.cc
@@ -533,6 +533,7 @@ const std::unordered_map<std::string, phi::Place>& StringToPlaceMap() {
       {"gpu", phi::GPUPlace{}},
       {"gpu_pinned", phi::GPUPinnedPlace{}},
       {"xpu", phi::XPUPlace{}},
+      {"xpu_pinned", phi::XPUPinnedPlace{}},
       {"ipu", phi::IPUPlace{}},
       {":", phi::CustomPlace{}},
       {"undefined", phi::Place{}}};
diff --git a/paddle/fluid/pir/transforms/pd_op_to_kernel_pass.cc b/paddle/fluid/pir/transforms/pd_op_to_kernel_pass.cc
index 1c2474712f5c6..c277a83ed78a6 100644
--- a/paddle/fluid/pir/transforms/pd_op_to_kernel_pass.cc
+++ b/paddle/fluid/pir/transforms/pd_op_to_kernel_pass.cc
@@ -190,6 +190,7 @@ const std::unordered_map<int, phi::Place> MemcpyOpAttr2Place = {
     {2, phi::GPUPinnedPlace()},
 #elif defined(PADDLE_WITH_XPU)
     {3, phi::XPUPlace()},
+    {5, phi::XPUPinnedPlace()},
 #elif defined(PADDLE_WITH_CUSTOM_DEVICE)
     {4, phi::CustomPlace()}
 #endif
diff --git a/paddle/fluid/platform/init.cc b/paddle/fluid/platform/init.cc
index cdff23a4a31fb..aede125c84b3d 100644
--- a/paddle/fluid/platform/init.cc
+++ b/paddle/fluid/platform/init.cc
@@ -218,6 +218,7 @@ void InitDevices(const std::vector<int> devices) {
 #endif
 #ifdef PADDLE_WITH_XPU
     places.emplace_back(phi::XPUPlace(device));
+    places.emplace_back(phi::XPUPinnedPlace());
 #endif
 #ifdef PADDLE_WITH_IPU
     places.emplace_back(phi::IPUPlace(device));
@@ -501,8 +502,11 @@ void InitMemoryMethod() {
           .GetZeroAllocator(phi::CPUPlace())
           .get();
     };
-    // XPUs do not have the concept of pinned memory,
-    // so the get_pinned_allocator function is not set.
+    memory_method->get_pinned_allocator = []() -> phi::Allocator * {
+      return paddle::memory::allocation::AllocatorFacade::Instance()
+          .GetAllocator(phi::XPUPinnedPlace())
+          .get();
+    };
     memory_method->get_new_xpu_event = [](int device_id) {
       return paddle::platform::XpuEventResourcePool::Instance().New(device_id);
     };
diff --git a/paddle/phi/backends/xpu/xpu_context.cc b/paddle/phi/backends/xpu/xpu_context.cc
index 4cb1b4455319c..572aca541e62a 100644
--- a/paddle/phi/backends/xpu/xpu_context.cc
+++ b/paddle/phi/backends/xpu/xpu_context.cc
@@ -35,6 +35,10 @@
 #include "xpu/runtime_ex.h"
 #include "xpu/xdnn.h"
 
+#if !defined(PADDLE_WITH_XPU_KP) || defined(__xpu_on_host__)
+#include "unsupported/Eigen/CXX11/Tensor"
+#endif
+
 namespace xpu = baidu::xpu::api;
 
 namespace phi {
@@ -477,4 +481,21 @@ void XPUContext::AddStashedMemory(int stream, const DenseTensor& tensor) {
 }
 
 void XPUContext::Init() { impls_[0]->Init(); }
+
+#if defined(PADDLE_WITH_XPU)
+XPUPinnedContext::XPUPinnedContext() {
+  eigen_device_ = std::make_unique<Eigen::DefaultDevice>();
+}
+
+XPUPinnedContext::XPUPinnedContext(XPUPinnedPlace place) : place_(place) {
+  eigen_device_ = std::make_unique<Eigen::DefaultDevice>();
+}
+
+Eigen::DefaultDevice* XPUPinnedContext::eigen_device() const {
+  return eigen_device_.get();
+}
+
+const Place& XPUPinnedContext::GetPlace() const { return place_; }
+#endif
+
 }  // namespace phi
diff --git a/paddle/phi/backends/xpu/xpu_context.h b/paddle/phi/backends/xpu/xpu_context.h
index 98afbfb3759be..2a9823d6a8de8 100644
--- a/paddle/phi/backends/xpu/xpu_context.h
+++ b/paddle/phi/backends/xpu/xpu_context.h
@@ -78,6 +78,9 @@ class XPUContext : public DeviceContext,
   int64_t GetStreamNum() const;
   void AddStashedMemory(int stream, const phi::DenseTensor& tensor);
 
+  void SetEigenDevice(Eigen::DefaultDevice*);
+  void SetEigenDevice(std::function<Eigen::DefaultDevice*()>&&);
+
   // For share external stream.
   void SetStream(void* stream, int i = 0);
 
@@ -127,4 +130,27 @@ using KPSContext = XPUContext;
 
 }  // namespace phi
 
+namespace phi {
+#if defined(PADDLE_WITH_XPU)
+// Currently, XPUPinnedContext is only used to data copying.
+class XPUPinnedContext
+    : public DeviceContext,
+      public phi::TypeInfoTraits<DeviceContext, XPUPinnedContext> {
+ public:
+  XPUPinnedContext();
+  explicit XPUPinnedContext(XPUPinnedPlace place);
+
+  const Place& GetPlace() const override;
+
+  Eigen::DefaultDevice* eigen_device() const;
+
+  static const char* name() { return "XPUPinnedContext"; }
+
+ private:
+  XPUPinnedPlace place_;
+  std::unique_ptr<Eigen::DefaultDevice> eigen_device_;
+};
+#endif
+}  // namespace phi
+
 #endif
diff --git a/paddle/phi/core/memory/memcpy.cc b/paddle/phi/core/memory/memcpy.cc
index 3b901de277250..272bfd0d9ccc9 100644
--- a/paddle/phi/core/memory/memcpy.cc
+++ b/paddle/phi/core/memory/memcpy.cc
@@ -21,6 +21,9 @@ limitations under the License. */
 #include "paddle/utils/test_macros.h"
 
 #ifdef PADDLE_WITH_XPU
+#include <cuda.h>
+#include <cuda_runtime.h>
+#include <chrono>
 #include "paddle/phi/backends/xpu/xpu_header.h"
 #include "xpu/runtime.h"
 #include "xpu/runtime_ex.h"
@@ -354,6 +357,165 @@ void Copy<phi::Place, phi::Place>(phi::Place dst_place,
   }
 }
 
+template <>
+void Copy<phi::CPUPlace, phi::XPUPinnedPlace>(phi::CPUPlace dst_place,
+                                              void* dst,
+                                              phi::XPUPinnedPlace src_place,
+                                              const void* src,
+                                              size_t num) {
+  VLOG(4) << "memory::Copy " << num << " Bytes from " << src_place << " to "
+          << dst_place;
+  if (UNLIKELY(num == 0)) return;
+  std::memcpy(dst, src, num);
+}
+
+template <>
+TEST_API void Copy<phi::XPUPinnedPlace, phi::CPUPlace>(
+    phi::XPUPinnedPlace dst_place,
+    void* dst,
+    phi::CPUPlace src_place,
+    const void* src,
+    size_t num) {
+  VLOG(4) << "memory::Copy " << num << " Bytes from " << src_place << " to "
+          << dst_place;
+  if (UNLIKELY(num == 0)) return;
+  std::memcpy(dst, src, num);
+}
+
+template <>
+void Copy<phi::XPUPinnedPlace, phi::XPUPinnedPlace>(
+    phi::XPUPinnedPlace dst_place,
+    void* dst,
+    phi::XPUPinnedPlace src_place,
+    const void* src,
+    size_t num) {
+  VLOG(4) << "memory::Copy " << num << " Bytes from " << src_place << " to "
+          << dst_place;
+  if (UNLIKELY(num == 0)) return;
+  std::memcpy(dst, src, num);
+}
+
+template <>
+void Copy<phi::XPUPinnedPlace, phi::XPUPlace>(phi::XPUPinnedPlace dst_place,
+                                              void* dst,
+                                              phi::XPUPlace src_place,
+                                              const void* src,
+                                              size_t num,
+                                              void* stream) {
+  if (UNLIKELY(num == 0)) return;
+  platform::SetXPUDeviceId(src_place.device);
+  VLOG(4) << "memory::Copy " << num << " Bytes from " << src_place << " to "
+          << dst_place << " by stream(" << stream << ")";
+
+  // Record start time using std::chrono
+  auto start = std::chrono::high_resolution_clock::now();
+
+  if (stream) {
+    phi::RecordEvent record_event(
+        "cudaMemcpyAsync:XPU->XPUPinned", phi::TracerEventType::UserDefined, 1);
+    cudaMemcpyAsync(dst,
+                    src,
+                    num,
+                    cudaMemcpyDeviceToHost,
+                    reinterpret_cast<cudaStream_t>(stream));
+
+  } else {
+    phi::RecordEvent record_event(
+        "cudaMemcpy:XPU->XPUPinned", phi::TracerEventType::UserDefined, 1);
+    cudaMemcpy(dst, src, num, cudaMemcpyDeviceToHost);
+  }
+
+  // Record end time and calculate elapsed time in milliseconds
+  auto end = std::chrono::high_resolution_clock::now();
+  std::chrono::duration<double, std::milli> elapsed = end - start;
+  VLOG(4) << "cudaMemcpy time: " << elapsed.count() << " ms";
+}
+
+template <>
+void Copy<phi::XPUPlace, phi::XPUPinnedPlace>(phi::XPUPlace dst_place,
+                                              void* dst,
+                                              phi::XPUPinnedPlace src_place,
+                                              const void* src,
+                                              size_t num,
+                                              void* stream) {
+  if (UNLIKELY(num == 0)) return;
+
+  platform::SetXPUDeviceId(dst_place.device);
+  VLOG(4) << "memory::Copy " << num << " Bytes from " << src_place << " to "
+          << dst_place << " by stream(" << stream << ")";
+
+  // Record start time using std::chrono
+  auto start = std::chrono::high_resolution_clock::now();
+
+  if (stream) {
+    phi::RecordEvent record_event(
+        "cudaMemcpyAsync:XPUPinned->XPU", phi::TracerEventType::UserDefined, 1);
+    cudaMemcpyAsync(dst,
+                    src,
+                    num,
+                    cudaMemcpyHostToDevice,
+                    reinterpret_cast<cudaStream_t>(stream));
+  } else {
+    phi::RecordEvent record_event(
+        "cudaMemcpy:XPUPinned->XPU", phi::TracerEventType::UserDefined, 1);
+    cudaMemcpy(dst, src, num, cudaMemcpyHostToDevice);
+  }
+
+  // Synchronize to ensure the memcpy operation is finished
+  if (stream) {
+    cudaStreamSynchronize(reinterpret_cast<cudaStream_t>(stream));
+  } else {
+    cudaDeviceSynchronize();
+  }
+
+  // Record end time and calculate elapsed time in milliseconds
+  auto end = std::chrono::high_resolution_clock::now();
+  std::chrono::duration<double, std::milli> elapsed = end - start;
+  VLOG(4) << "cudaMemcpy time: " << elapsed.count() << " ms";
+}
+
+// NOTE: only for (CPUPlace, CUDAPlace and CUDAPinnedPlace) -> (CUDAPinnedPlace)
+template <>
+void Copy<phi::XPUPinnedPlace, phi::Place>(phi::XPUPinnedPlace dst_place,
+                                           void* dst,
+                                           phi::Place src_place,
+                                           const void* src,
+                                           size_t num,
+                                           void* stream) {
+  Copy(phi::Place(dst_place.GetType()), dst, src_place, src, num, stream);
+}
+
+// NOTE: only for (CUDAPinnedPlace) -> (CPUPlace, CUDAPlace and CUDAPinnedPlace)
+template <>
+void Copy<phi::Place, phi::XPUPinnedPlace>(phi::Place dst_place,
+                                           void* dst,
+                                           phi::XPUPinnedPlace src_place,
+                                           const void* src,
+                                           size_t num,
+                                           void* stream) {
+  Copy(dst_place, dst, phi::Place(src_place.GetType()), src, num, stream);
+}
+
+// NOTE: only for (CPUPlace) -> (CUDAPinnedPlace)
+template <>
+void Copy<phi::XPUPinnedPlace, phi::Place>(phi::XPUPinnedPlace dst_place,
+                                           void* dst,
+                                           phi::Place src_place,
+                                           const void* src,
+                                           size_t num) {
+  Copy(phi::Place(dst_place.GetType()), dst, src_place, src, num, nullptr);
+}
+
+// NOTE: only for (CUDAPinnedPlace) -> (CPUPlace)
+template <>
+void Copy<phi::Place, phi::XPUPinnedPlace>(phi::Place dst_place,
+                                           void* dst,
+                                           phi::XPUPinnedPlace src_place,
+                                           const void* src,
+                                           size_t num) {
+  Copy(dst_place, dst, phi::Place(src_place.GetType()), src, num, nullptr);
+}
+
 #endif
 
 #if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
@@ -703,6 +865,33 @@ void Copy<phi::Place, phi::Place>(phi::Place dst_place,
     phi::GPUPinnedPlace place_dst;
     phi::GPUPlace place_src(src_place.GetDeviceId());
     return Copy(place_dst, dst, place_src, src, num, stream);
+#ifdef PADDLE_WITH_XPU
+  } else if (src_place.GetType() == phi::AllocationType::CPU &&
+             dst_place.GetType() == phi::AllocationType::XPUPINNED) {
+    phi::CPUPlace place_src;
+    phi::XPUPinnedPlace place_dst;
+    return Copy(place_dst, dst, place_src, src, num);
+  } else if (src_place.GetType() == phi::AllocationType::XPUPINNED &&
+             dst_place.GetType() == phi::AllocationType::CPU) {
+    phi::CPUPlace place_dst;
+    phi::XPUPinnedPlace place_src;
+    return Copy(place_dst, dst, place_src, src, num);
+  } else if (src_place.GetType() == phi::AllocationType::XPUPINNED &&
+             dst_place.GetType() == phi::AllocationType::XPUPINNED) {
+    phi::XPUPinnedPlace place_dst;
+    phi::XPUPinnedPlace place_src;
+    return Copy(place_dst, dst, place_src, src, num);
+  } else if (src_place.GetType() == phi::AllocationType::XPUPINNED &&
+             dst_place.GetType() == phi::AllocationType::XPU) {
+    phi::XPUPinnedPlace place_src;
+    phi::XPUPlace place_dst(dst_place.GetDeviceId());
+    return Copy(place_dst, dst, place_src, src, num, stream);
+  } else if (src_place.GetType() == phi::AllocationType::XPU &&
+             dst_place.GetType() == phi::AllocationType::XPUPINNED) {
+    phi::XPUPinnedPlace place_dst;
+    phi::XPUPlace place_src(src_place.GetDeviceId());
+    return Copy(place_dst, dst, place_src, src, num, stream);
+#endif
 #ifdef PADDLE_WITH_CUSTOM_DEVICE
   } else if (src_place.GetType() == phi::AllocationType::CPU &&  // NOLINT
              dst_place.GetType() == phi::AllocationType::CUSTOM) {
@@ -866,6 +1055,31 @@ void Copy<phi::Place, phi::Place>(phi::Place dst_place,
     phi::XPUPlace place_src(src_place.GetDeviceId());
     phi::XPUPlace place_dst(dst_place.GetDeviceId());
     return Copy(place_dst, dst, place_src, src, num);
+  } else if (src_place.GetType() == phi::AllocationType::CPU &&
+             dst_place.GetType() == phi::AllocationType::XPUPINNED) {
+    phi::CPUPlace place_src;
+    phi::XPUPinnedPlace place_dst;
+    return Copy(place_dst, dst, place_src, src, num);
+  } else if (src_place.GetType() == phi::AllocationType::XPUPINNED &&
+             dst_place.GetType() == phi::AllocationType::CPU) {
+    phi::CPUPlace place_dst;
+    phi::XPUPinnedPlace place_src;
+    return Copy(place_dst, dst, place_src, src, num);
+  } else if (src_place.GetType() == phi::AllocationType::XPUPINNED &&
+             dst_place.GetType() == phi::AllocationType::XPUPINNED) {
+    phi::XPUPinnedPlace place_dst;
+    phi::XPUPinnedPlace place_src;
+    return Copy(place_dst, dst, place_src, src, num);
+  } else if (src_place.GetType() == phi::AllocationType::XPUPINNED &&
+             dst_place.GetType() == phi::AllocationType::XPU) {
+    phi::XPUPinnedPlace place_src;
+    phi::XPUPlace place_dst(dst_place.GetDeviceId());
+    return Copy(place_dst, dst, place_src, src, num, nullptr);
+  } else if (src_place.GetType() == phi::AllocationType::XPU &&
+             dst_place.GetType() == phi::AllocationType::XPUPINNED) {
+    phi::XPUPinnedPlace place_dst;
+    phi::XPUPlace place_src(src_place.GetDeviceId());
+    return Copy(place_dst, dst, place_src, src, num, nullptr);
   }
 #endif
 #ifdef PADDLE_WITH_IPU
diff --git a/paddle/phi/core/platform/device_context.cc b/paddle/phi/core/platform/device_context.cc
index 4448444105ac3..ab0b2a15da7d2 100644
--- a/paddle/phi/core/platform/device_context.cc
+++ b/paddle/phi/core/platform/device_context.cc
@@ -115,6 +115,8 @@ inline std::unique_ptr<DeviceContext> CreateDeviceContext(
                                 xpu_ctx->stream());
     }
     dev_ctx->SetAllocator(instance.GetAllocator(p, xpu_ctx->stream()).get());
+    dev_ctx->SetPinnedAllocator(
+        instance.GetAllocator(phi::XPUPinnedPlace()).get());
     dev_ctx->SetGenerator(phi::DefaultXPUGenerator(p.GetDeviceId()).get());
 #endif
 #ifdef PADDLE_WITH_CUSTOM_DEVICE
@@ -216,6 +218,18 @@ void EmplaceDeviceContexts(
       PADDLE_THROW(
           common::errors::Unimplemented("XPUPlace is not supported. Please "
                                         "re-compile with WITH_XPU option."));
+#endif
+    } else if (phi::is_xpu_pinned_place(place)) {
+#if defined(PADDLE_WITH_XPU)
+      EmplaceDeviceContext<phi::XPUPinnedContext>(
+          place_to_device_context,
+          place,
+          disable_setting_default_stream_for_allocator,
+          /*unused*/ stream_priority);
+#else
+      PADDLE_THROW(common::errors::Unimplemented(
+          "XPUPinnedPlace is not supported. Please re-compile with WITH_XPU "
+          "option."));
 #endif
     } else if (place.GetType() == phi::AllocationType::CUSTOM) {
 #ifdef PADDLE_WITH_CUSTOM_DEVICE
diff --git a/paddle/phi/core/platform/device_context.h b/paddle/phi/core/platform/device_context.h
index b93cada9e8b09..c40aa29189822 100644
--- a/paddle/phi/core/platform/device_context.h
+++ b/paddle/phi/core/platform/device_context.h
@@ -112,6 +112,10 @@ namespace xpu = baidu::xpu::api;
 using CUDAPinnedDeviceContext = phi::GPUPinnedContext;
 #endif
 
+#if defined(PADDLE_WITH_XPU)
+using XPUPinnedDeviceContext = phi::XPUPinnedContext;
+#endif
+
 void EmplaceDeviceContexts(
     std::map<Place, std::shared_future<std::unique_ptr<DeviceContext>>>*
         place_to_device_context,
@@ -140,4 +144,11 @@ struct DefaultDeviceContextType<phi::GPUPinnedPlace> {
 };
 #endif
 
+#if defined(PADDLE_WITH_XPU)
+template <>
+struct DefaultDeviceContextType<phi::XPUPinnedPlace> {
+  using TYPE = paddle::platform::XPUPinnedDeviceContext;
+};
+#endif
+
 }  //  namespace phi
diff --git a/paddle/phi/core/utils/type_info.cc b/paddle/phi/core/utils/type_info.cc
index f6ffec27fa860..be6e6fb1f1d61 100644
--- a/paddle/phi/core/utils/type_info.cc
+++ b/paddle/phi/core/utils/type_info.cc
@@ -72,6 +72,7 @@ template class TypeInfoTraits<phi::DeviceContext, GPUPinnedContext>;
 
 #ifdef PADDLE_WITH_XPU
 template class TypeInfoTraits<phi::DeviceContext, XPUContext>;
+template class TypeInfoTraits<phi::DeviceContext, XPUPinnedContext>;
 #endif
 
 #ifdef PADDLE_WITH_DNNL
diff --git a/paddle/phi/core/utils/visit_place.h b/paddle/phi/core/utils/visit_place.h
index 348be3db5d1ba..e6cba7ef5af69 100644
--- a/paddle/phi/core/utils/visit_place.h
+++ b/paddle/phi/core/utils/visit_place.h
@@ -52,6 +52,16 @@ typename Visitor::result_type VisitPlace(const phi::Place& place,
       PADDLE_THROW(common::errors::Unavailable(
           ("Paddle is not compiled with XPU. Cannot visit xpu device")));
       return typename Visitor::result_type();
+#endif
+    }
+    case phi::AllocationType::XPUPINNED: {
+#if defined(PADDLE_WITH_XPU)
+      phi::XPUPinnedPlace p;
+      return visitor(p);
+#else
+      PADDLE_THROW(common::errors::Unavailable(
+          ("Paddle is not compiled with XPU. Cannot visit xpu device")));
+      return typename Visitor::result_type();
 #endif
     }
     case phi::AllocationType::IPU: {
diff --git a/test/cpp/fluid/platform/init_test.cc b/test/cpp/fluid/platform/init_test.cc
index 07b40ef7126c0..cee0d1be645d6 100644
--- a/test/cpp/fluid/platform/init_test.cc
+++ b/test/cpp/fluid/platform/init_test.cc
@@ -48,7 +48,7 @@ TEST(InitDevices, XPU) {
   int count = paddle::platform::GetXPUDeviceCount();
   InitDevices();
   DeviceContextPool& pool = DeviceContextPool::Instance();
-  ASSERT_EQ(pool.Size(), 1U + static_cast<unsigned>(count));
+  ASSERT_EQ(pool.Size(), 2U + static_cast<unsigned>(count));
 #endif
 }
 
diff --git a/test/xpu/test_xpu_async_offload_reload.py b/test/xpu/test_xpu_async_offload_reload.py
deleted file mode 100644
index e53e2e352de73..0000000000000
--- a/test/xpu/test_xpu_async_offload_reload.py
+++ /dev/null
@@ -1,132 +0,0 @@
-# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
-#
-# Licensed 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.
-
-# test_async_offload_reload_debug.py (XPU version, with debug info)
-# (c) 2023-2025 PaddlePaddle Authors
-
-import unittest
-
-import numpy as np
-
-import paddle
-from paddle.incubate.tensor.manipulation import (
-    async_offload,
-    async_offload_with_offset,
-    async_reload,
-    create_xpu_async_load,
-)
-
-
-def print_debug_info(tensor, name):
-    """Prints the device placement of a tensor."""
-    print(f"{name} is on device: {tensor.place}")
-
-
-class TestSaveLoadLargeParameters(unittest.TestCase):
-    def offload_and_reload(self, data0):
-        # Print initial device info for the input tensor.
-        print_debug_info(data0, "data0 (original)")
-
-        loader = create_xpu_async_load()
-        data1 = paddle.randn([10, 10])
-        print_debug_info(data1, "data1 (for compute)")
-
-        # Offload data0 -> pinned memory (usually on CPU)
-        cpu_data, task = async_offload(data0, loader)
-        print_debug_info(cpu_data, "cpu_data (after offload)")
-
-        # Do some random compute on the XPU.
-        res = paddle.matmul(data1, data1)
-        print_debug_info(res, "res (after first compute)")
-
-        # Wait on the CPU side for the offload task to complete.
-        task.cpu_wait()
-
-        # Reload from pinned (CPU) memory -> back to XPU.
-        xpu_data, task = async_reload(cpu_data, loader)
-        print_debug_info(xpu_data, "xpu_data (after reload)")
-
-        # Another compute on the XPU.
-        res = paddle.matmul(data1, data1)
-        print_debug_info(res, "res (after second compute)")
-
-        # Wait on both the device (XPU) and CPU sides.
-        task.xpu_wait()
-        task.cpu_wait()
-
-        # Check correctness.
-        np.testing.assert_array_equal(data0.numpy(), cpu_data.numpy())
-        np.testing.assert_array_equal(data0.numpy(), xpu_data.numpy())
-
-    def test_large_parameters_paddle_save_tensor(self):
-        # Create XPU data.
-        data0 = paddle.randn([10, 5])
-        print_debug_info(
-            data0, "data0 in test_large_parameters_paddle_save_tensor"
-        )
-        # NOTE: If you need to explicitly move data0 to XPU (depending on your PaddlePaddle version),
-        # you might do: data0 = data0.xpu()
-        self.offload_and_reload(data0)
-
-    def test_large_parameters_paddle_save_model_weight(self):
-        model = paddle.nn.Linear(10, 5)
-        data0 = model.weight
-        print_debug_info(
-            data0,
-            "model.weight in test_large_parameters_paddle_save_model_weight",
-        )
-        self.offload_and_reload(data0)
-
-    def test_offload_with_offset(self):
-        loader = create_xpu_async_load()
-        data1 = paddle.randn([100])
-        print_debug_info(data1, "data1 in test_offload_with_offset")
-        data2 = paddle.randn(
-            [100]
-        ).cpu()  # Ensure data2 is on CPU (pinned memory)
-        print_debug_info(data2, "data2 in test_offload_with_offset (CPU)")
-
-        # Partial offload in two segments.
-        task1 = async_offload_with_offset(
-            src_tensor=data1,
-            dst_tensor=data2,
-            src_offset=0,
-            dst_offset=0,
-            offload_size=50,
-            async_loader=loader,
-        )
-        task2 = async_offload_with_offset(
-            src_tensor=data1,
-            dst_tensor=data2,
-            src_offset=50,
-            dst_offset=50,
-            offload_size=50,
-            async_loader=loader,
-        )
-
-        # Wait for tasks to complete.
-        task1.xpu_wait()
-        task2.cpu_wait()
-
-        print_debug_info(data1, "data1 after offload_with_offset")
-        print_debug_info(data2, "data2 after offload_with_offset")
-
-        # Check that the data matches.
-        np.testing.assert_array_equal(data1.numpy(), data2.numpy())
-
-
-if __name__ == '__main__':
-    # Print the default device Paddle is set to use.
-    print("Default Paddle device:", paddle.get_device())
-    unittest.main()
diff --git a/test/xpu/test_xpu_async_offload_reload_performance.py b/test/xpu/test_xpu_async_offload_reload_performance.py
deleted file mode 100644
index e10671015c4d2..0000000000000
--- a/test/xpu/test_xpu_async_offload_reload_performance.py
+++ /dev/null
@@ -1,167 +0,0 @@
-# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
-#
-# Licensed 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.
-
-# test_async_offload_reload_debug.py (XPU version, with debug and performance tests)
-# (c) 2023-2025 PaddlePaddle Authors
-
-import time
-import unittest
-
-import numpy as np
-
-import paddle
-from paddle.incubate.tensor.manipulation import (
-    async_offload,
-    async_offload_with_offset,
-    async_reload,
-    create_xpu_async_load,
-)
-
-
-def print_debug_info(tensor, name):
-    """Prints the device placement of a tensor."""
-    print(f"{name} is on device: {tensor.place}")
-
-
-class TestSaveLoadLargeParameters(unittest.TestCase):
-    def offload_and_reload(self, data0):
-        # Print initial device info for the input tensor.
-        print_debug_info(data0, "data0 (original)")
-
-        loader = create_xpu_async_load()
-        data1 = paddle.randn([10, 10])
-        print_debug_info(data1, "data1 (for compute)")
-
-        # Offload data0 -> pinned memory (usually on CPU)
-        cpu_data, task = async_offload(data0, loader)
-        print_debug_info(cpu_data, "cpu_data (after offload)")
-
-        # Do some random compute on the XPU.
-        res = paddle.matmul(data1, data1)
-        print_debug_info(res, "res (after first compute)")
-
-        # Wait on the CPU side for the offload task to complete.
-        task.cpu_wait()
-
-        # Reload from pinned (CPU) memory -> back to XPU.
-        xpu_data, task = async_reload(cpu_data, loader)
-        print_debug_info(xpu_data, "xpu_data (after reload)")
-
-        # Another compute on the XPU.
-        res = paddle.matmul(data1, data1)
-        print_debug_info(res, "res (after second compute)")
-
-        # Wait on both the device (XPU) and CPU sides.
-        task.xpu_wait()
-        task.cpu_wait()
-
-        # Check correctness.
-        np.testing.assert_array_equal(data0.numpy(), cpu_data.numpy())
-        np.testing.assert_array_equal(data0.numpy(), xpu_data.numpy())
-
-    def test_large_parameters_paddle_save_tensor(self):
-        # Create XPU data.
-        data0 = paddle.randn([10, 5])
-        print_debug_info(
-            data0, "data0 in test_large_parameters_paddle_save_tensor"
-        )
-        # NOTE: If you need to explicitly move data0 to XPU (depending on your PaddlePaddle version),
-        # you might do: data0 = data0.xpu()
-        self.offload_and_reload(data0)
-
-    def test_large_parameters_paddle_save_model_weight(self):
-        model = paddle.nn.Linear(10, 5)
-        data0 = model.weight
-        print_debug_info(
-            data0,
-            "model.weight in test_large_parameters_paddle_save_model_weight",
-        )
-        self.offload_and_reload(data0)
-
-    def test_offload_with_offset(self):
-        loader = create_xpu_async_load()
-        data1 = paddle.randn([100])
-        print_debug_info(data1, "data1 in test_offload_with_offset")
-        data2 = paddle.randn(
-            [100]
-        ).cpu()  # Ensure data2 is on CPU (pinned memory)
-        print_debug_info(data2, "data2 in test_offload_with_offset (CPU)")
-
-        # Partial offload in two segments.
-        task1 = async_offload_with_offset(
-            src_tensor=data1,
-            dst_tensor=data2,
-            src_offset=0,
-            dst_offset=0,
-            offload_size=50,
-            async_loader=loader,
-        )
-        task2 = async_offload_with_offset(
-            src_tensor=data1,
-            dst_tensor=data2,
-            src_offset=50,
-            dst_offset=50,
-            offload_size=50,
-            async_loader=loader,
-        )
-
-        # Wait for tasks to complete.
-        task1.xpu_wait()
-        task2.cpu_wait()
-
-        print_debug_info(data1, "data1 after offload_with_offset")
-        print_debug_info(data2, "data2 after offload_with_offset")
-
-        # Check that the data matches.
-        np.testing.assert_array_equal(data1.numpy(), data2.numpy())
-
-    def test_xpu_offload_performance(self):
-        """
-        Performance test: Measure the time cost of offloading 100MB of data.
-
-        All extra print/debug calls have been removed to minimize overhead.
-        The offload is run for multiple iterations (with a warm-up) and averaged
-        for a more accurate measurement.
-        """
-        loader = create_xpu_async_load()
-        # For float32, each element takes 4 bytes. For 100MB, we need 25M elements.
-        num_elements = 25_000_000
-        data = paddle.randn([num_elements])
-        # Warm-up run to stabilize performance.
-        _ = paddle.randn([10, 10])
-
-        iterations = 10
-        times = []
-        for _ in range(iterations):
-            start_time = time.perf_counter()
-            cpu_data, task = async_offload(data, loader)
-            task.cpu_wait()
-            end_time = time.perf_counter()
-            times.append(end_time - start_time)
-
-        avg_time = sum(times) / iterations
-
-        # Check data integrity (this is not measured)
-        np.testing.assert_array_equal(data.numpy(), cpu_data.numpy())
-
-        # Output the average time (printing outside the measured loop is acceptable)
-        print(
-            f"Average time taken for offloading 100MB data over {iterations} iterations: {avg_time:.4f} seconds"
-        )
-
-
-if __name__ == '__main__':
-    # Removing the default device print to avoid any extra overhead during performance tests.
-    # print("Default Paddle device:", paddle.get_device())
-    unittest.main()


Reviews:
user: login: SylarTiaNII
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T07:43:40Z
user: login: SylarTiaNII
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T07:45:09Z
user: login: gongweibao
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T07:47:52Z

Review comments:
diff_hunk: @@ -354,6 +357,165 @@ void Copy<phi::Place, phi::Place>(phi::Place dst_place,
   }
 }
 
+template <>
+void Copy<phi::CPUPlace, phi::XPUPinnedPlace>(phi::CPUPlace dst_place,
+                                              void* dst,
+                                              phi::XPUPinnedPlace src_place,
+                                              const void* src,
+                                              size_t num) {
+  VLOG(4) << "memory::Copy " << num << " Bytes from " << src_place << " to "
+          << dst_place;
+  if (UNLIKELY(num == 0)) return;
+  std::memcpy(dst, src, num);
+}
+
+template <>
+TEST_API void Copy<phi::XPUPinnedPlace, phi::CPUPlace>(
+    phi::XPUPinnedPlace dst_place,
+    void* dst,
+    phi::CPUPlace src_place,
+    const void* src,
+    size_t num) {
+  VLOG(4) << "memory::Copy " << num << " Bytes from " << src_place << " to "
+          << dst_place;
+  if (UNLIKELY(num == 0)) return;
+  std::memcpy(dst, src, num);
+}
+
+template <>
+void Copy<phi::XPUPinnedPlace, phi::XPUPinnedPlace>(
+    phi::XPUPinnedPlace dst_place,
+    void* dst,
+    phi::XPUPinnedPlace src_place,
+    const void* src,
+    size_t num) {
+  VLOG(4) << "memory::Copy " << num << " Bytes from " << src_place << " to "
+          << dst_place;
+  if (UNLIKELY(num == 0)) return;
+  std::memcpy(dst, src, num);
+}
+
+template <>
+void Copy<phi::XPUPinnedPlace, phi::XPUPlace>(phi::XPUPinnedPlace dst_place,
+                                              void* dst,
+                                              phi::XPUPlace src_place,
+                                              const void* src,
+                                              size_t num,
+                                              void* stream) {
+  if (UNLIKELY(num == 0)) return;
+  platform::SetXPUDeviceId(src_place.device);
+  VLOG(4) << "memory::Copy " << num << " Bytes from " << src_place << " to "
+          << dst_place << " by stream(" << stream << ")";
+
+  // Record start time using std::chrono
+  auto start = std::chrono::high_resolution_clock::now();
+
+  if (stream) {
+    phi::RecordEvent record_event(
+        "cudaMemcpyAsync:XPU->XPUPinned", phi::TracerEventType::UserDefined, 1);
+    cudaMemcpyAsync(dst,
+                    src,
+                    num,
+                    cudaMemcpyDeviceToHost,
+                    reinterpret_cast<cudaStream_t>(stream));
+
+  } else {
+    phi::RecordEvent record_event(
+        "cudaMemcpy:XPU->XPUPinned", phi::TracerEventType::UserDefined, 1);
+    cudaMemcpy(dst, src, num, cudaMemcpyDeviceToHost);
+  }
+
+  // Record end time and calculate elapsed time in milliseconds
+  auto end = std::chrono::high_resolution_clock::now();
+  std::chrono::duration<double, std::milli> elapsed = end - start;
+  VLOG(4) << "cudaMemcpy time: " << elapsed.count() << " ms";
+}
+
+template <>
+void Copy<phi::XPUPlace, phi::XPUPinnedPlace>(phi::XPUPlace dst_place,
+                                              void* dst,
+                                              phi::XPUPinnedPlace src_place,
+                                              const void* src,
+                                              size_t num,
+                                              void* stream) {
+  if (UNLIKELY(num == 0)) return;
+
+  platform::SetXPUDeviceId(dst_place.device);
+  VLOG(4) << "memory::Copy " << num << " Bytes from " << src_place << " to "
+          << dst_place << " by stream(" << stream << ")";
+
+  // Record start time using std::chrono
+  auto start = std::chrono::high_resolution_clock::now();
+
+  if (stream) {
+    phi::RecordEvent record_event(
+        "cudaMemcpyAsync:XPUPinned->XPU", phi::TracerEventType::UserDefined, 1);
+    cudaMemcpyAsync(dst,
+                    src,
+                    num,
+                    cudaMemcpyHostToDevice,
+                    reinterpret_cast<cudaStream_t>(stream));
+  } else {
+    phi::RecordEvent record_event(
+        "cudaMemcpy:XPUPinned->XPU", phi::TracerEventType::UserDefined, 1);
+    cudaMemcpy(dst, src, num, cudaMemcpyHostToDevice);
+  }
+
+  // Synchronize to ensure the memcpy operation is finished
+  if (stream) {
+    cudaStreamSynchronize(reinterpret_cast<cudaStream_t>(stream));
+  } else {
+    cudaDeviceSynchronize();
+  }
+
+  // Record end time and calculate elapsed time in milliseconds
+  auto end = std::chrono::high_resolution_clock::now();
+  std::chrono::duration<double, std::milli> elapsed = end - start;
+  VLOG(4) << "cudaMemcpy time: " << elapsed.count() << " ms";
+}
+
+// NOTE: only for (CPUPlace, CUDAPlace and CUDAPinnedPlace) -> (CUDAPinnedPlace)
path: paddle/phi/core/memory/memcpy.cc
user: login: SylarTiaNII
body: note应该可以删掉了吧。
created_at: 2025-03-18T07:40:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -354,6 +357,165 @@ void Copy<phi::Place, phi::Place>(phi::Place dst_place,
   }
 }
 
+template <>
+void Copy<phi::CPUPlace, phi::XPUPinnedPlace>(phi::CPUPlace dst_place,
+                                              void* dst,
+                                              phi::XPUPinnedPlace src_place,
+                                              const void* src,
+                                              size_t num) {
+  VLOG(4) << "memory::Copy " << num << " Bytes from " << src_place << " to "
+          << dst_place;
+  if (UNLIKELY(num == 0)) return;
+  std::memcpy(dst, src, num);
+}
+
+template <>
+TEST_API void Copy<phi::XPUPinnedPlace, phi::CPUPlace>(
+    phi::XPUPinnedPlace dst_place,
+    void* dst,
+    phi::CPUPlace src_place,
+    const void* src,
+    size_t num) {
+  VLOG(4) << "memory::Copy " << num << " Bytes from " << src_place << " to "
+          << dst_place;
+  if (UNLIKELY(num == 0)) return;
+  std::memcpy(dst, src, num);
+}
+
+template <>
+void Copy<phi::XPUPinnedPlace, phi::XPUPinnedPlace>(
+    phi::XPUPinnedPlace dst_place,
+    void* dst,
+    phi::XPUPinnedPlace src_place,
+    const void* src,
+    size_t num) {
+  VLOG(4) << "memory::Copy " << num << " Bytes from " << src_place << " to "
+          << dst_place;
+  if (UNLIKELY(num == 0)) return;
+  std::memcpy(dst, src, num);
+}
+
+template <>
+void Copy<phi::XPUPinnedPlace, phi::XPUPlace>(phi::XPUPinnedPlace dst_place,
+                                              void* dst,
+                                              phi::XPUPlace src_place,
+                                              const void* src,
+                                              size_t num,
+                                              void* stream) {
+  if (UNLIKELY(num == 0)) return;
+  platform::SetXPUDeviceId(src_place.device);
+  VLOG(4) << "memory::Copy " << num << " Bytes from " << src_place << " to "
+          << dst_place << " by stream(" << stream << ")";
+
+  // Record start time using std::chrono
+  auto start = std::chrono::high_resolution_clock::now();
+
+  if (stream) {
+    phi::RecordEvent record_event(
+        "cudaMemcpyAsync:XPU->XPUPinned", phi::TracerEventType::UserDefined, 1);
+    cudaMemcpyAsync(dst,
+                    src,
+                    num,
+                    cudaMemcpyDeviceToHost,
+                    reinterpret_cast<cudaStream_t>(stream));
+
+  } else {
+    phi::RecordEvent record_event(
+        "cudaMemcpy:XPU->XPUPinned", phi::TracerEventType::UserDefined, 1);
+    cudaMemcpy(dst, src, num, cudaMemcpyDeviceToHost);
+  }
+
+  // Record end time and calculate elapsed time in milliseconds
+  auto end = std::chrono::high_resolution_clock::now();
+  std::chrono::duration<double, std::milli> elapsed = end - start;
+  VLOG(4) << "cudaMemcpy time: " << elapsed.count() << " ms";
+}
+
+template <>
+void Copy<phi::XPUPlace, phi::XPUPinnedPlace>(phi::XPUPlace dst_place,
+                                              void* dst,
+                                              phi::XPUPinnedPlace src_place,
+                                              const void* src,
+                                              size_t num,
+                                              void* stream) {
+  if (UNLIKELY(num == 0)) return;
+
+  platform::SetXPUDeviceId(dst_place.device);
+  VLOG(4) << "memory::Copy " << num << " Bytes from " << src_place << " to "
+          << dst_place << " by stream(" << stream << ")";
+
+  // Record start time using std::chrono
+  auto start = std::chrono::high_resolution_clock::now();
+
+  if (stream) {
+    phi::RecordEvent record_event(
+        "cudaMemcpyAsync:XPUPinned->XPU", phi::TracerEventType::UserDefined, 1);
+    cudaMemcpyAsync(dst,
+                    src,
+                    num,
+                    cudaMemcpyHostToDevice,
+                    reinterpret_cast<cudaStream_t>(stream));
+  } else {
+    phi::RecordEvent record_event(
+        "cudaMemcpy:XPUPinned->XPU", phi::TracerEventType::UserDefined, 1);
+    cudaMemcpy(dst, src, num, cudaMemcpyHostToDevice);
+  }
+
+  // Synchronize to ensure the memcpy operation is finished
+  if (stream) {
+    cudaStreamSynchronize(reinterpret_cast<cudaStream_t>(stream));
+  } else {
+    cudaDeviceSynchronize();
+  }
+
+  // Record end time and calculate elapsed time in milliseconds
+  auto end = std::chrono::high_resolution_clock::now();
+  std::chrono::duration<double, std::milli> elapsed = end - start;
+  VLOG(4) << "cudaMemcpy time: " << elapsed.count() << " ms";
+}
+
+// NOTE: only for (CPUPlace, CUDAPlace and CUDAPinnedPlace) -> (CUDAPinnedPlace)
path: paddle/phi/core/memory/memcpy.cc
user: login: SylarTiaNII
body: 下面几个类似
created_at: 2025-03-18T07:41:30Z
author_association: CONTRIBUTOR

PR comments:
user: login: paddle-bot[bot]
created_at: 2025-03-17T09:26:41Z
body: 你的PR提交成功，感谢你对开源项目的贡献!
请关注后续CI自动化测试结果，详情请参考[Paddle-CI手册](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/dev_guides/git_guides/paddle_ci_manual_cn.html)。
Your PR has been submitted. Thanks for your contribution!
Please wait for the result of CI firstly. See [Paddle CI Manual](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/dev_guides/git_guides/paddle_ci_manual_cn.html) for details.
author_association: NONE