NixlKVManager optimizations (#17654)

Signed-off-by: Ovidiu Mara <ovidium@nvidia.com>
This commit is contained in:
ovidiusm
2026-02-05 17:25:23 +01:00
committed by GitHub
parent b639779dd8
commit 498d8d0680

View File

@@ -144,10 +144,12 @@ class NixlKVManager(CommonKVManager):
"to run SGLang with NixlTransferEngine."
) from e
agent_config = nixl_agent_config(backends=[])
self.agent = nixl_agent(str(uuid.uuid4()), agent_config)
backend = envs.SGLANG_DISAGGREGATION_NIXL_BACKEND.get()
agent_config = nixl_agent_config(
backends=[backend],
num_threads=(8 if disaggregation_mode == DisaggregationMode.PREFILL else 0),
)
self.agent = nixl_agent(str(uuid.uuid4()), agent_config)
available_plugins = self.agent.get_plugin_list()
if backend not in available_plugins:
@@ -155,9 +157,6 @@ class NixlKVManager(CommonKVManager):
f"NIXL backend '{backend}' not found. Available: {available_plugins}. "
f"Please install the required NIXL plugin or choose from: {available_plugins}"
)
self.agent.create_backend(backend)
self.nixl_backend = backend
logger.info(f"NIXL KVManager initialized with backend: {backend}")
self.register_buffer_to_engine()
@@ -377,20 +376,47 @@ class NixlKVManager(CommonKVManager):
]
src_addrs = []
src_lens = []
dst_addrs = []
dst_lens = []
# Precompute block starts/lengths to reduce Python-level loops.
prefill_starts = np.fromiter(
(block[0] for block in prefill_kv_blocks), dtype=np.int64
)
dst_starts = np.fromiter((block[0] for block in dst_kv_blocks), dtype=np.int64)
block_lens = np.fromiter(
(len(block) for block in prefill_kv_blocks), dtype=np.int64
)
for src_ptr, dst_ptr, item_len in layers_params:
for prefill_index, decode_index in zip(prefill_kv_blocks, dst_kv_blocks):
src_addr = src_ptr + int(prefill_index[0]) * item_len
dst_addr = dst_ptr + int(decode_index[0]) * item_len
length = item_len * len(prefill_index)
src_addrs.append((src_addr, length, self.kv_args.gpu_id))
dst_addrs.append((dst_addr, length, dst_gpu_id))
lengths = item_len * block_lens
src_addrs.append(src_ptr + prefill_starts * item_len)
src_lens.append(lengths)
dst_addrs.append(dst_ptr + dst_starts * item_len)
dst_lens.append(lengths)
def make_req_array(addr_chunks, len_chunks, gpu):
if not addr_chunks:
return np.empty((0, 3), dtype=np.int64)
flat_addrs = np.concatenate(addr_chunks)
flat_lens = np.concatenate(len_chunks)
return np.column_stack(
(
flat_addrs,
flat_lens,
np.full_like(flat_addrs, gpu),
)
)
src_reqs = make_req_array(src_addrs, src_lens, self.kv_args.gpu_id)
dst_reqs = make_req_array(dst_addrs, dst_lens, dst_gpu_id)
logger.debug(
f"len(src_addrs): before group: {len(prefill_kv_indices)}, after group: {len(src_addrs)}"
)
src_descs = self.agent.get_xfer_descs(src_addrs, "VRAM")
dst_descs = self.agent.get_xfer_descs(dst_addrs, "VRAM")
src_descs = self.agent.get_xfer_descs(src_reqs, "VRAM")
dst_descs = self.agent.get_xfer_descs(dst_reqs, "VRAM")
# Transfer data
xfer_handle = self.agent.initialize_xfer(
"WRITE",
@@ -452,13 +478,6 @@ class NixlKVManager(CommonKVManager):
src_k_ptrs, src_v_ptrs, dst_k_ptrs, dst_v_ptrs, layers_current_pp_stage = (
self.get_mha_kv_ptrs_with_pp(self.kv_args.kv_data_ptrs, dst_kv_ptrs)
)
# Create transfer descriptors
src_addrs = []
dst_addrs = []
bytes_per_token_on_prefill = src_kv_item_len // page_size
bytes_per_token_on_decode = dst_kv_item_len // page_size
# Calculate precise byte offset and length for the sub-slice within the token
src_head_slice_offset = src_head_start_offset * bytes_per_head_slice_to_send
dst_head_slice_offset = dst_head_start_offset * bytes_per_head_slice_to_send
@@ -478,52 +497,53 @@ class NixlKVManager(CommonKVManager):
for layer_id in range(layers_current_pp_stage)
]
prefill_indices = np.asarray(prefill_kv_indices, dtype=np.int64)
dst_indices = np.asarray(dst_kv_indices, dtype=np.int64)
bytes_per_token_prefill = src_kv_item_len // page_size
bytes_per_token_decode = dst_kv_item_len // page_size
token_offsets = np.arange(page_size, dtype=np.int64)
src_addrs = []
dst_addrs = []
# Calculate strides for a single token slot
bytes_per_token_on_prefill = src_kv_item_len // page_size
bytes_per_token_on_decode = dst_kv_item_len // page_size
for src_ptr, dst_ptr in src_dst_ptr_pairs:
for i in range(len(prefill_kv_indices)):
prefill_page_idx = int(prefill_kv_indices[i])
decode_page_idx = int(dst_kv_indices[i])
src_page_bases = src_ptr + prefill_indices * src_kv_item_len
dst_page_bases = dst_ptr + dst_indices * dst_kv_item_len
# Get the starting addresses for the current src and dst pages
src_page_start_addr = src_ptr + prefill_page_idx * src_kv_item_len
dst_page_start_addr = dst_ptr + decode_page_idx * dst_kv_item_len
src_all = (
src_page_bases[:, None]
+ token_offsets[None, :] * bytes_per_token_prefill
+ src_head_slice_offset
).ravel()
dst_all = (
dst_page_bases[:, None]
+ token_offsets[None, :] * bytes_per_token_decode
+ dst_head_slice_offset
).ravel()
# Iterate through each valid token slot within the current page
for token_slot_in_page in range(page_size):
# Calculate the start address of the current token slot
src_token_slot_start_addr = (
src_page_start_addr
+ token_slot_in_page * bytes_per_token_on_prefill
)
dst_token_slot_start_addr = (
dst_page_start_addr
+ token_slot_in_page * bytes_per_token_on_decode
)
src_addrs.append(src_all)
dst_addrs.append(dst_all)
# Calculate final src and dst addresses by applying head-slice offsets
src_slice_addr = src_token_slot_start_addr + src_head_slice_offset
dst_slice_addr = dst_token_slot_start_addr + dst_head_slice_offset
def make_req_array(addr_chunks, size, gpu):
if not addr_chunks:
return np.empty((0, 3), dtype=np.int64)
flat_addrs = np.concatenate(addr_chunks)
return np.column_stack(
(
flat_addrs,
np.full_like(flat_addrs, size),
np.full_like(flat_addrs, gpu),
)
)
src_addrs.append(
(
src_slice_addr,
heads_bytes_per_token_to_send,
self.kv_args.gpu_id,
)
)
dst_addrs.append(
(dst_slice_addr, heads_bytes_per_token_to_send, dst_gpu_id)
)
src_reqs = make_req_array(
src_addrs, heads_bytes_per_token_to_send, self.kv_args.gpu_id
)
dst_reqs = make_req_array(dst_addrs, heads_bytes_per_token_to_send, dst_gpu_id)
# Use NIXL agent for transfer
src_descs = self.agent.get_xfer_descs(src_addrs, "VRAM")
dst_descs = self.agent.get_xfer_descs(dst_addrs, "VRAM")
src_descs = self.agent.get_xfer_descs(src_reqs, "VRAM")
dst_descs = self.agent.get_xfer_descs(dst_reqs, "VRAM")
xfer_handle = self.agent.initialize_xfer(
"WRITE", src_descs, dst_descs, peer_name, notif.encode("ascii")