Skip to content
Open
Changes from all commits
Commits
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
95 changes: 81 additions & 14 deletions lightllm/utils/kv_cache_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,83 @@ def get_merged_head_dim(self):
) // self.data_type.itemsize


def _get_default_hugepage_size() -> int:
try:
with open("/proc/meminfo", "r") as f:
for line in f:
if line.startswith("Hugepagesize:"):
parts = line.split()
if len(parts) >= 2:
kb = int(parts[1])
return kb * 1024
except Exception:
pass
return 2 * 1024 * 1024


def _get_online_numa_nodes() -> List[int]:
for path in ("/sys/devices/system/node/has_memory", "/sys/devices/system/node/online"):
try:
with open(path, "r") as f:
online = f.read().strip()
nodes: List[int] = []
for part in online.split(","):
if "-" in part:
start, end = part.split("-")
nodes.extend(range(int(start), int(end) + 1))
else:
nodes.append(int(part))
return nodes
except Exception:
continue
return [0]


def interleave_pages_across_numa_nodes(libc, addr: int, size: int) -> bool:
MPOL_INTERLEAVE = 3
SYS_MBIND = 237

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.

high

Hardcoding SYS_MBIND = 237 is specific to the x86_64 architecture. On aarch64 (ARM64), the syscall number for mbind is 235. Since ARM64 is a very common platform for LLM inference (e.g., NVIDIA Grace Hopper GH200), calling syscall 237 on ARM64 will execute rt_tgsigqueueinfo instead of mbind, leading to undefined behavior or crashes. We should dynamically determine the syscall number based on the system architecture.

Suggested change
SYS_MBIND = 237
import platform
arch = platform.machine()
if arch == "x86_64":
SYS_MBIND = 237
elif arch == "aarch64":
SYS_MBIND = 235
else:
logger.warning(f"Unsupported architecture {arch} for NUMA interleave")
return False


if enable_huge_page():
huge_sz = _get_default_hugepage_size()
size = triton.cdiv(size, huge_sz) * huge_sz

def _mbind(mode, mask):
nodemask = ctypes.c_ulong(mask)
libc.syscall.restype = ctypes.c_long
return libc.syscall(
ctypes.c_long(SYS_MBIND),
ctypes.c_void_p(addr),
ctypes.c_ulong(size),
ctypes.c_int(mode),
ctypes.byref(nodemask),
ctypes.c_ulong(65),

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.

high

Passing 65 as maxnode to mbind causes the kernel to read 128 bits (16 bytes) from userspace because the bit count is rounded up to the nearest multiple of unsigned long (64 bits). Since nodemask is defined as ctypes.c_ulong(mask), it only allocates 8 bytes (64 bits) on 64-bit systems. This leads to an out-of-bounds read of 8 bytes of uninitialized stack memory/garbage. If any of the garbage bits correspond to an offline or invalid node on the system, mbind will fail with EINVAL (errno 22), causing the NUMA interleave to be skipped entirely. Since max(nodes) >= 64 is already checked and skipped, all active nodes fit within the first 64 bits. Thus, maxnode should be set to 64 to prevent reading out-of-bounds and potential random failures.

Suggested change
ctypes.c_ulong(65),
ctypes.c_ulong(64),

ctypes.c_uint(0),
)

if os.getenv("LIGHTLLM_DISABLE_NUMA_INTERLEAVE", "0") == "1":
logger.info("cpu cache numa interleave disabled by LIGHTLLM_DISABLE_NUMA_INTERLEAVE")
return False
nodes = _get_online_numa_nodes()
if len(nodes) <= 1:
return False
if max(nodes) >= 64:
logger.warning(f"more than 64 numa nodes ({nodes}), skip cpu cache numa interleave")
return False
try:
ret = _mbind(MPOL_INTERLEAVE, sum(1 << n for n in nodes))
if ret != 0:
logger.warning(
f"mbind MPOL_INTERLEAVE failed (errno={ctypes.get_errno()}), "
f"cpu kv cache pages will use default first-touch numa policy"
)
return False
logger.info(f"cpu kv cache pages interleaved across numa nodes {nodes}")
return True
except Exception as e:
logger.warning(f"cpu cache numa interleave skipped: {e}")
return False


@lru_cache(maxsize=None)
def create_shm_kv_cache_ptr(key: int, size: int) -> int:
libc = ctypes.CDLL("/usr/lib/x86_64-linux-gnu/libc.so.6", use_errno=True)
Expand All @@ -181,20 +258,6 @@ def create_shm_kv_cache_ptr(key: int, size: int) -> int:
requested_size = size
use_hugetlb = enable_huge_page()

# 计算大页大小(默认从 /proc/meminfo 读取 Hugepagesize)
def _get_default_hugepage_size() -> int:
try:
with open("/proc/meminfo", "r") as f:
for line in f:
if line.startswith("Hugepagesize:"):
parts = line.split()
if len(parts) >= 2:
kb = int(parts[1])
return kb * 1024
except Exception:
pass
return 2 * 1024 * 1024 # fallback 2MB

shmflg = 0o666 | 0o1000 # 权限和 IPC_CREAT 标志
if use_hugetlb:
# 向上对齐到大页大小
Expand Down Expand Up @@ -234,6 +297,8 @@ def _get_default_hugepage_size() -> int:
raise Exception("Error attaching shared memory")
logger.info(f"Shared cpu kv cache tensor memory at address: {shm_addr}")

interleave_pages_across_numa_nodes(libc, shm_addr, size_to_alloc)

# Best-effort memory prefaulting in background to speed up subsequent cudaHostRegister
def _pre_warm_memory():
page_size = _get_default_hugepage_size() if use_hugetlb else 4096
Expand Down Expand Up @@ -327,4 +392,6 @@ def attach_shm_kv_cache_ptr(key: int, size: int) -> int:
raise Exception(f"Error attaching shared memory (errno={err})")

logger.info(f"Attached to SHM key={key}, shmid={shmid}, addr={shm_addr}")

interleave_pages_across_numa_nodes(libc, shm_addr, size)
return shm_addr
Loading