# Copyright (c) ModelScope Contributors. All rights reserved.
"""Small vLLM-Ascend memory API compatibility patches.

This module intentionally avoids colocate memory-policy changes.  It only
normalizes API differences that are safe for both standalone vLLM-Ascend
inference and SWIFT GRPO rollout.
"""
from __future__ import annotations

import torch
from contextlib import contextmanager
from functools import partial

_ORIGIN_TORCH_NPU_MEM_GET_INFO = None
_BOUND_TORCH_NPU_MEM_GET_INFO_DEVICE = None


def _patch_vllm_ascend_mem_get_info() -> None:
    """Patch ``NPUPlatform.mem_get_info`` for torch-npu binding differences.

    vLLM-Ascend calls ``current_platform.mem_get_info(device)`` during worker
    initialization.  Without this wrapper, some versions expose
    ``NPUPlatform.mem_get_info`` in a way that gets Python method binding plus
    the explicit device argument at the same time, producing:

        TypeError: mem_get_info() got multiple values for argument 'device'

    Defining a classmethod here gives vLLM-Ascend one stable call surface.  It
    keeps the device-aware torch-npu query when available and falls back to the
    no-argument query only when torch-npu rejects the keyword.  This does not
    change memory profiling policy.
    """
    try:
        from vllm_ascend.platform import NPUPlatform
    except (ImportError, AttributeError):
        return
    if getattr(NPUPlatform, '_swift_mem_get_info_patched', False):
        return

    @classmethod
    def mem_get_info(cls, device=None):
        if device is None:
            return torch.npu.mem_get_info()
        try:
            return torch.npu.mem_get_info(device=device)
        except TypeError:
            return torch.npu.mem_get_info()

    NPUPlatform.mem_get_info = mem_get_info
    NPUPlatform._swift_mem_get_info_patched = True


def patch_vllm_ascend_memory_runtime() -> None:
    """Apply memory patches that do not depend on colocated training."""
    _patch_vllm_ascend_mem_get_info()


@contextmanager
def vllm_ascend_mem_get_info_context(vllm_device: str):
    """Bind bare ``torch.npu.mem_get_info()`` calls to vLLM's device.

    Most vLLM memory accounting goes through ``NPUPlatform.mem_get_info`` and is
    handled by ``patch_vllm_ascend_memory_runtime`` above.  Some vLLM-Ascend
    paths still call ``torch.npu.mem_get_info()`` directly, or assign it to
    ``torch.cuda.mem_get_info`` for CUDA-compatible worker code.

    Keep this binding for the process lifetime after the context exits.  vLLM
    sleep/wake paths can call bare ``torch.npu.mem_get_info()`` after engine
    construction, so restoring here would regress the original behavior in
    ``swift.infer_engine.utils.patch_npu_vllm``.  Re-entering with another device
    rebinds from the original function instead of stacking nested partials.
    """
    global _ORIGIN_TORCH_NPU_MEM_GET_INFO, _BOUND_TORCH_NPU_MEM_GET_INFO_DEVICE

    if (_ORIGIN_TORCH_NPU_MEM_GET_INFO is None
            or getattr(torch.npu.mem_get_info, '_swift_bound_mem_get_info_device', None) is None):
        _ORIGIN_TORCH_NPU_MEM_GET_INFO = torch.npu.mem_get_info

    if _BOUND_TORCH_NPU_MEM_GET_INFO_DEVICE != vllm_device:
        mem_get_info = partial(_ORIGIN_TORCH_NPU_MEM_GET_INFO, device=vllm_device)
        mem_get_info._swift_bound_mem_get_info_device = vllm_device
        torch.npu.mem_get_info = mem_get_info
        _BOUND_TORCH_NPU_MEM_GET_INFO_DEVICE = vllm_device

    yield


__all__ = [
    'patch_vllm_ascend_memory_runtime',
    'vllm_ascend_mem_get_info_context',
]
