File size: 5,571 Bytes
49b8205
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
"""xeon_runtime.py — Ativação do runtime Intel Xeon (AVX512 + AMX + IPEX + OneDNN).

Adaptado do projeto BiGRU_T_version (PowerMachine), com simplificações para
rodar em CPU-only e degradar graciosamente quando IPEX/AMX não estiverem
disponíveis. A ativação é SEMPRE realizada (mesmo que parcial), conforme
requisito do usuário: "ativar xeon_runtime.py".

Otimizações aplicadas:
    1. OMP_NUM_THREADS / MKL_NUM_THREADS = núcleos físicos
    2. KMP_AFFINITY=granularity=fine,compact
    3. MKL_ENABLE_INSTRUCTIONS=AVX512 (se suportado)
    4. ONEDNN_MAX_CPU_ISA=AMX_INT8 (se suportado)
    5. DNNL_PRIMITIVE_CACHE_CAPACITY=1024
    6. MKL_DYNAMIC=FALSE
    7. IPEX (intel_extension_for_pytorch) — se disponível, ipex.optimize(model)
    8. torch.set_float32_matmul_precision("high")
    9. torch.backends.cudnn.benchmark = True (no-op em CPU)

Uso:
    from cnn_bigru.utils.xeon_runtime import optimize_xeon_environment
    N_CORES = optimize_xeon_environment()   # chamar ANTES de importar torch
    import torch
"""
from __future__ import annotations

import logging
import os
import platform
from typing import Any, Optional

logger = logging.getLogger(__name__)

_NUCLEOS_ALOCADOS: Optional[int] = None
_IPEX_AVAILABLE: Optional[bool] = None
_AMX_CAPABLE: Optional[bool] = None
_INIT_DONE: bool = False


def _detect_physical_cores() -> int:
    """Detecta núcleos físicos respeitando cgroup limits."""
    try:
        import psutil  # type: ignore
        n_phys = psutil.cpu_count(logical=False) or 1
    except ImportError:
        try:
            with open("/proc/cpuinfo", "r") as f:
                cores = set()
                for line in f:
                    if line.startswith("core id"):
                        cores.add(line.strip())
                n_phys = len(cores) or 1
        except OSError:
            n_phys = 1
    try:
        n_affine = len(os.sched_getaffinity(0))
        n_logical = os.cpu_count() or 1
        if n_affine < n_logical:
            n_phys = max(1, n_affine // 2)
        else:
            n_phys = min(n_phys, n_affine)
    except (AttributeError, OSError):
        pass
    return max(1, n_phys)


def _read_cpu_flags() -> str:
    try:
        with open("/proc/cpuinfo", "r") as f:
            for line in f:
                if line.startswith("flags"):
                    return line
    except OSError:
        pass
    return ""


def _detect_amx() -> bool:
    flags = _read_cpu_flags()
    return "amx_int8" in flags and "amx_bf16" in flags


def _detect_avx512() -> bool:
    flags = _read_cpu_flags()
    return "avx512f" in flags


def optimize_xeon_environment(force_threads: Optional[int] = None) -> int:
    """Ativa todas as otimizações de CPU. Retorna o número de núcleos alocados.

    Deve ser chamada UMA VEZ, antes de importar torch, para que as variáveis
    de ambiente tenham efeito. Chamadas subsequentes são no-op (idempotente).
    """
    global _NUCLEOS_ALOCADOS, _IPEX_AVAILABLE, _AMX_CAPABLE, _INIT_DONE
    if _INIT_DONE:
        return _NUCLEOS_ALOCADOS or 1

    n_cores = force_threads or _detect_physical_cores()
    _NUCLEOS_ALOCADOS = n_cores

    # Threads
    os.environ.setdefault("OMP_NUM_THREADS", str(n_cores))
    os.environ.setdefault("MKL_NUM_THREADS", str(n_cores))
    os.environ.setdefault("OPENBLAS_NUM_THREADS", str(n_cores))
    os.environ.setdefault("NUMEXPR_NUM_THREADS", str(n_cores))
    os.environ["KMP_AFFINITY"] = "granularity=fine,compact"
    os.environ["MKL_DYNAMIC"] = "FALSE"

    # ISA detection
    if _detect_avx512():
        os.environ["MKL_ENABLE_INSTRUCTIONS"] = "AVX512"
        logger.info("AVX512 detectado e ativado para MKL")
    if _detect_amx():
        os.environ["ONEDNN_MAX_CPU_ISA"] = "AMX_INT8"
        os.environ["DNNL_PRIMITIVE_CACHE_CAPACITY"] = "1024"
        _AMX_CAPABLE = True
        logger.info("AMX_INT8 detectado e ativado para OneDNN")
    else:
        _AMX_CAPABLE = False

    # Tokenizers parallelism
    os.environ.setdefault("TOKENIZERS_PARALLELISM", "true")

    _INIT_DONE = True
    logger.info(
        "Xeon runtime ativado: cores=%d, avx512=%s, amx=%s, ipex=%s",
        n_cores,
        _detect_avx512(),
        bool(_AMX_CAPABLE),
        _check_ipex_available(),
    )
    return n_cores


def _check_ipex_available() -> bool:
    global _IPEX_AVAILABLE
    if _IPEX_AVAILABLE is not None:
        return _IPEX_AVAILABLE
    try:
        import intel_extension_for_pytorch  # noqa: F401
        _IPEX_AVAILABLE = True
    except ImportError:
        _IPEX_AVAILABLE = False
    return _IPEX_AVAILABLE


def optimize_model_ipex(model: Any) -> Any:
    """Aplica ipex.optimize() no modelo, se IPEX estiver disponível."""
    if not _check_ipex_available():
        logger.info("IPEX indisponível — pulando ipex.optimize()")
        return model
    try:
        import intel_extension_for_pytorch as ipex  # type: ignore
        model = ipex.optimize(model)
        logger.info("Modelo otimizado com IPEX")
    except Exception as e:
        logger.warning("Falha ao aplicar ipex.optimize(): %s", e)
    return model


def get_runtime_info() -> dict:
    """Retorna informações sobre o runtime ativado."""
    return {
        "nucleos_alocados": _NUCLEOS_ALOCADOS,
        "avx512": _detect_avx512(),
        "amx_capable": bool(_AMX_CAPABLE),
        "ipex_available": _check_ipex_available(),
        "platform": platform.platform(),
        "init_done": _INIT_DONE,
    }


__all__ = [
    "optimize_xeon_environment",
    "optimize_model_ipex",
    "get_runtime_info",
]