Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions csrc/torch_binding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,124 @@ void swap_blocks(torch::Tensor &x, torch::Tensor &y, const torch::Tensor &z)
return;
}

inline bool is_device_pointer(const void* ptr) {
aclrtMemAttr mem_type = ACL_DDR_MEM;
aclError ret = aclrtPointerGetAttr(
&mem_type, ACL_POINTER_ATTR_MEMORY_TYPE, const_cast<void*>(ptr));
if (ret != ACL_SUCCESS) {
return false;
}
return (mem_type == ACL_HBM_MEM);
}

inline aclrtMemcpyKind determine_memcpy_kind(const void* src, const void* dst) {
bool src_on_device = is_device_pointer(src);
bool dst_on_device = is_device_pointer(dst);

if (src_on_device && dst_on_device) {
return ACL_MEMCPY_DEVICE_TO_DEVICE;
} else if (src_on_device && !dst_on_device) {
return ACL_MEMCPY_DEVICE_TO_HOST;
} else if (!src_on_device && dst_on_device) {
return ACL_MEMCPY_HOST_TO_DEVICE;
}
TORCH_CHECK(false, "swap_blocks_batch: invalid device combination, "
"both src and dst appear to be on Host");
return ACL_MEMCPY_HOST_TO_DEVICE; // unreachable, suppress warning
}

void swap_blocks_batch(const torch::Tensor& src_ptrs,
const torch::Tensor& dst_ptrs,
const torch::Tensor& sizes) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The function signature for swap_blocks_batch uses const torch::Tensor& for src_ptrs and dst_ptrs. However, the operator registration on line 881 (ops.def("swap_blocks_batch(Tensor! x, Tensor! y, Tensor z) -> ()");) marks these tensors as mutable (!). This mismatch forces the use of const_cast on lines 167-172, which is unsafe and breaks the const contract.

To fix this, the function signature should be updated to match the registration. This will also allow removing the const_casts. Additionally, the const_cast for size_data is unnecessary as the aclrtMemcpyBatchAsync API expects const size_t* for size-related arguments.

void swap_blocks_batch(torch::Tensor& src_ptrs,
                       torch::Tensor& dst_ptrs,
                       const torch::Tensor& sizes) {


const int64_t n = src_ptrs.size(0);
TORCH_CHECK(dst_ptrs.size(0) == n, "dst_ptrs length must match src_ptrs");
TORCH_CHECK(sizes.size(0) == n, "sizes length must match src_ptrs");

if (n == 0) return;

const int64_t* src_data = src_ptrs.data_ptr<int64_t>();
const int64_t* dst_data = dst_ptrs.data_ptr<int64_t>();
const int64_t* size_data = sizes.data_ptr<int64_t>();

aclrtStream stream = c10_npu::getCurrentNPUStream().stream();

aclrtMemcpyKind memcpy_kind = determine_memcpy_kind(
reinterpret_cast<const void*>(src_data[0]),
reinterpret_cast<const void*>(dst_data[0]));

// =========================================================================
// 路径 1: aclrtMemcpyBatchAsync (CANN 8.5+, 试验特性)
//
// 约束:仅支持 H2D / D2H,不支持 D2D。
// 通过宏 CANN_MEMCPY_BATCH_ASYNC 控制是否启用。
// =========================================================================
#if defined(CANN_MEMCPY_BATCH_ASYNC)
if (memcpy_kind != ACL_MEMCPY_DEVICE_TO_DEVICE) {
static_assert(sizeof(void*) == sizeof(int64_t),
"void* and int64_t must be the same size");
static_assert(sizeof(size_t) == sizeof(int64_t),
"size_t and int64_t must be the same size");

void** dst_arr = reinterpret_cast<void**>(
const_cast<int64_t*>(dst_data));
void** src_arr = reinterpret_cast<void**>(
const_cast<int64_t*>(src_data));
size_t* size_arr = reinterpret_cast<size_t*>(
const_cast<int64_t*>(size_data));
size_t* dest_maxs = size_arr;

aclrtMemcpyBatchAttr attr = {};
attr.memcpyKind = memcpy_kind;
size_t attrs_index = 0;
size_t fail_index = 0;

aclError result = aclrtMemcpyBatchAsync(
dst_arr,
dest_maxs,
src_arr,
size_arr,
static_cast<size_t>(n),
&attr,
&attrs_index,
1,
&fail_index,
stream);

TORCH_CHECK(result == ACL_SUCCESS,
"aclrtMemcpyBatchAsync failed at index ", fail_index,
" with error code ", result);
return;
}
#endif

// =========================================================================
// 路径 2: 逐条 aclrtMemcpyAsync(兼容所有 CANN 版本和所有拷贝方向)
//
// 与 GPU 版本 CUDA < 12.8 的回退路径等价。
// =========================================================================
for (int64_t i = 0; i < n; i++) {
void* dst = reinterpret_cast<void*>(dst_data[i]);
const void* src = reinterpret_cast<const void*>(src_data[i]);
size_t copy_size = static_cast<size_t>(size_data[i]);

aclError ret = aclrtMemcpyAsync(
dst,
copy_size,
src,
copy_size,
memcpy_kind,
stream);

TORCH_CHECK(ret == ACL_SUCCESS,
"aclrtMemcpyAsync failed at index ", i,
" with error code ", ret,
", src=", src_data[i],
", dst=", dst_data[i],
", size=", size_data[i]);
}
}

AscendType get_dtype_from_torch(at::ScalarType scalarType)
{
if (scalarType == at::ScalarType::Float) {
Expand Down Expand Up @@ -760,6 +878,9 @@ TORCH_LIBRARY_EXPAND(CONCAT(_C, _ascend), ops)
ops.def("swap_blocks(Tensor! x, Tensor! y, Tensor z) -> ()");
ops.impl("swap_blocks", torch::kPrivateUse1, &vllm_ascend::swap_blocks);

ops.def("swap_blocks_batch(Tensor! x, Tensor! y, Tensor z) -> ()");
ops.impl("swap_blocks_batch", torch::kPrivateUse1, &vllm_ascend::swap_blocks_batch);

ops.def(
"grouped_matmul_swiglu_quant(Tensor x, Tensor weight, Tensor weight_scale, Tensor x_scale,"
" Tensor group_list, *, Tensor? bias=None,"
Expand Down
55 changes: 40 additions & 15 deletions vllm_ascend/kv_offload/cpu_npu.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ def __init__(
),
)
)

# Pre-compute base pointers and block sizes for batch copies.
self._src_base_ptrs = np.array(
[t.data_ptr() for t in self.src_tensors], dtype=np.int64
)
self._dst_base_ptrs = np.array(
[t.data_ptr() for t in self.dst_tensors], dtype=np.int64
)
self._block_size_in_bytes_arr = np.array(
self.tensor_block_size_in_bytes, dtype=np.int64
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The attributes self.src_tensors, self.dst_tensors, and self.tensor_block_size_in_bytes are used here in __init__, but they are not defined on the class instance. The transfer direction, and therefore which tensors are source or destination, is only determined within the transfer_async method. This code will raise an AttributeError when CpuNpuOffloadingHandler is initialized.

This logic for preparing base pointers and sizes should be moved into transfer_async where src_tensors and dst_tensors are defined.



def transfer_async(self, job_id: int, spec: TransferSpec) -> bool:
logger.info("start transfer_async...")
Expand Down Expand Up @@ -123,25 +135,38 @@ def transfer_async(self, job_id: int, spec: TransferSpec) -> bool:

assert src_sub_block_count == dst_blocks.size * dst_block_size_factor - dst_sub_blocks_to_skip

src_to_dst = np.empty((src_sub_block_count, 2), dtype=np.int64)
expand_block_ids(src_blocks, src_block_size_factor, src_to_dst[:, 0])
expand_block_ids(
dst_blocks,
dst_block_size_factor,
src_to_dst[:, 1],
skip_count=dst_sub_blocks_to_skip,
)
src_to_dst_tensor = torch.from_numpy(src_to_dst)
# src_to_dst = np.empty((src_sub_block_count, 2), dtype=np.int64)
src_block_ids = np.empty(dst_sub_block_count, dtype=np.int64)
dst_block_ids = np.empty(dst_sub_block_count, dtype=np.int64)
expand_block_ids(src_blocks, src_block_size_factor, src_block_ids)
expand_block_ids(dst_blocks, self.dst_block_size_factor, dst_block_ids)

# Build flat pointer arrays for all tensors × all block pairs.
num_pairs = dst_sub_block_count
num_tensors = len(self.src_tensors)
total = num_pairs * num_tensors

all_src = np.empty(total, dtype=np.int64)
all_dst = np.empty(total, dtype=np.int64)
all_sizes = np.empty(total, dtype=np.int64)

for t_idx, bsz in enumerate(self._block_size_in_bytes_arr):
start = t_idx * num_pairs
end = start + num_pairs
all_src[start:end] = self._src_base_ptrs[t_idx] + src_block_ids * bsz
all_dst[start:end] = self._dst_base_ptrs[t_idx] + dst_block_ids * bsz
all_sizes[start:end] = bsz

batch_src = torch.from_numpy(all_src)
batch_dst = torch.from_numpy(all_dst)
batch_sizes = torch.from_numpy(all_sizes)

event = self.events_pool.pop() if self.events_pool else torch.npu.Event()
with torch.npu.stream(stream):
for src_tensor, dst_tensor in zip(src_tensors, dst_tensors):
src_key_cache, src_value_cache = src_tensor[0], src_tensor[1]
dst_key_cache, dst_value_cache = dst_tensor[0], dst_tensor[1]

torch.ops._C_ascend.swap_blocks(src_key_cache, dst_key_cache, src_to_dst_tensor)
torch.ops._C_ascend.swap_blocks(src_value_cache, dst_value_cache, src_to_dst_tensor)

torch.ops._C_ascend.swap_blocks_batch(src_key_cache, dst_key_cache, src_to_dst_tensor)
torch.ops._C_ascend.swap_blocks_batch(src_value_cache, dst_value_cache, src_to_dst_tensor)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The calls to torch.ops._C_ascend.swap_blocks_batch are using undefined variables from the old, removed code path (src_key_cache, dst_key_cache, etc.).

The new batched implementation prepares batch_src, batch_dst, and batch_sizes to handle all copies in one go. There should be a single call to swap_blocks_batch with these tensors.

Additionally, there are other errors in this block:

  • self.dst_block_size_factor on line 142 should be the local variable dst_block_size_factor.
  • self.src_tensors on line 146 should be the local variable src_tensors.
  • The logic for preparing all_src, all_dst, all_sizes depends on attributes (_src_base_ptrs, etc.) that are not correctly initialized. This logic needs to be self-contained within transfer_async and correctly handle the structure of src_tensors (a list of tensor tuples).
Suggested change
torch.ops._C_ascend.swap_blocks_batch(src_key_cache, dst_key_cache, src_to_dst_tensor)
torch.ops._C_ascend.swap_blocks_batch(src_value_cache, dst_value_cache, src_to_dst_tensor)
torch.ops._C_ascend.swap_blocks_batch(batch_src, batch_dst, batch_sizes)


event.record(stream)

self.transfer_events[job_id] = event
Expand Down
Loading