306 lines
9.8 KiB
Python
306 lines
9.8 KiB
Python
# rfcp-server-gpu.spec — GPU-enabled build (CuPy + CUDA 13.x)
|
|
# RFCP Iteration 3.6.0
|
|
#
|
|
# Mode: ONEDIR (directory output, not single exe)
|
|
# This is better for CUDA — DLLs load directly without temp extraction
|
|
#
|
|
# Requirements:
|
|
# pip install cupy-cuda13x fastrlock pyinstaller
|
|
# CUDA Toolkit 13.x installed (winget install Nvidia.CUDA)
|
|
#
|
|
# Build:
|
|
# cd backend && pyinstaller ../installer/rfcp-server-gpu.spec --clean --noconfirm
|
|
#
|
|
# Output:
|
|
# backend/dist/rfcp-server/rfcp-server.exe (+ DLLs in same folder)
|
|
|
|
import os
|
|
import sys
|
|
import glob
|
|
from PyInstaller.utils.hooks import collect_all, collect_dynamic_libs
|
|
|
|
backend_path = os.path.abspath(os.path.join(os.path.dirname(SPEC), '..', 'backend'))
|
|
print(f"[GPU SPEC] Backend path: {backend_path}")
|
|
|
|
# ═══════════════════════════════════════════
|
|
# Collect CuPy packages
|
|
# ═══════════════════════════════════════════
|
|
cupy_datas = []
|
|
cupy_binaries = []
|
|
cupy_hiddenimports = []
|
|
cupyb_datas = []
|
|
cupyb_binaries = []
|
|
cupyb_hiddenimports = []
|
|
|
|
try:
|
|
cupy_datas, cupy_binaries, cupy_hiddenimports = collect_all('cupy')
|
|
cupyb_datas, cupyb_binaries, cupyb_hiddenimports = collect_all('cupy_backends')
|
|
print(f"[GPU SPEC] CuPy: {len(cupy_binaries)} binaries, {len(cupy_datas)} data files")
|
|
except Exception as e:
|
|
print(f"[GPU SPEC] WARNING: CuPy collection failed: {e}")
|
|
|
|
# NOTE: nvidia pip packages REMOVED - they have cuda12 DLLs that conflict with cupy-cuda13x
|
|
# We use CUDA Toolkit 13.x DLLs only
|
|
|
|
# ═══════════════════════════════════════════
|
|
# Collect CUDA Toolkit DLLs (system install)
|
|
# ═══════════════════════════════════════════
|
|
# Installed via: winget install Nvidia.CUDA
|
|
cuda_toolkit_binaries = []
|
|
cuda_path = os.environ.get('CUDA_PATH', '')
|
|
|
|
if cuda_path:
|
|
# Scan BOTH bin\ and bin\x64\ directories
|
|
cuda_bin_dirs = [
|
|
os.path.join(cuda_path, 'bin'),
|
|
os.path.join(cuda_path, 'bin', 'x64'),
|
|
]
|
|
|
|
# Only essential CUDA runtime DLLs (exclude NPP, nvjpeg, nvblas, nvfatbin)
|
|
cuda_dll_patterns = [
|
|
'cublas64_*.dll',
|
|
'cublasLt64_*.dll',
|
|
'cudart64_*.dll',
|
|
'cufft64_*.dll',
|
|
'cufftw64_*.dll',
|
|
'curand64_*.dll',
|
|
'cusolver64_*.dll',
|
|
'cusolverMg64_*.dll',
|
|
'cusparse64_*.dll',
|
|
'nvrtc64_*.dll',
|
|
'nvrtc-builtins64_*.dll',
|
|
'nvJitLink_*.dll',
|
|
'nvjitlink_*.dll',
|
|
]
|
|
|
|
collected_dlls = set() # Avoid duplicates
|
|
for cuda_bin in cuda_bin_dirs:
|
|
if os.path.isdir(cuda_bin):
|
|
for pattern in cuda_dll_patterns:
|
|
for dll in glob.glob(os.path.join(cuda_bin, pattern)):
|
|
dll_name = os.path.basename(dll)
|
|
if dll_name not in collected_dlls:
|
|
cuda_toolkit_binaries.append((dll, '.'))
|
|
collected_dlls.add(dll_name)
|
|
print(f"[GPU SPEC] Scanned: {cuda_bin}")
|
|
|
|
print(f"[GPU SPEC] CUDA Toolkit ({cuda_path}): {len(cuda_toolkit_binaries)} DLLs")
|
|
for dll, _ in cuda_toolkit_binaries:
|
|
print(f"[GPU SPEC] {os.path.basename(dll)}")
|
|
else:
|
|
print("[GPU SPEC] ERROR: CUDA_PATH not set!")
|
|
print("[GPU SPEC] Install: winget install Nvidia.CUDA")
|
|
|
|
# All GPU binaries (CUDA Toolkit only, no nvidia pip packages)
|
|
all_gpu_binaries = cuda_toolkit_binaries
|
|
|
|
if len(all_gpu_binaries) == 0:
|
|
print("[GPU SPEC] ⚠ NO CUDA DLLs FOUND!")
|
|
print("[GPU SPEC] Install CUDA Toolkit: winget install Nvidia.CUDA")
|
|
else:
|
|
print(f"[GPU SPEC] ✅ Total GPU DLLs: {len(all_gpu_binaries)}")
|
|
|
|
# ═══════════════════════════════════════════
|
|
# Collect fastrlock (CuPy dependency)
|
|
# ═══════════════════════════════════════════
|
|
fl_datas = []
|
|
fl_binaries = []
|
|
fl_hiddenimports = []
|
|
try:
|
|
fl_datas, fl_binaries, fl_hiddenimports = collect_all('fastrlock')
|
|
print(f"[GPU SPEC] fastrlock: {len(fl_binaries)} binaries")
|
|
except Exception:
|
|
print("[GPU SPEC] fastrlock not found (optional)")
|
|
|
|
# ═══════════════════════════════════════════
|
|
# PyInstaller Analysis
|
|
# ═══════════════════════════════════════════
|
|
|
|
a = Analysis(
|
|
[os.path.join(backend_path, 'run_server.py')],
|
|
pathex=[backend_path],
|
|
binaries=(
|
|
cupy_binaries + cupyb_binaries +
|
|
fl_binaries + all_gpu_binaries
|
|
),
|
|
datas=[
|
|
# Include app/ source code
|
|
(os.path.join(backend_path, 'app'), 'app'),
|
|
] + cupy_datas + cupyb_datas + fl_datas,
|
|
hiddenimports=[
|
|
# ── Uvicorn internals ──
|
|
'uvicorn.logging',
|
|
'uvicorn.loops',
|
|
'uvicorn.loops.auto',
|
|
'uvicorn.loops.asyncio',
|
|
'uvicorn.protocols',
|
|
'uvicorn.protocols.http',
|
|
'uvicorn.protocols.http.auto',
|
|
'uvicorn.protocols.http.h11_impl',
|
|
'uvicorn.protocols.http.httptools_impl',
|
|
'uvicorn.protocols.websockets',
|
|
'uvicorn.protocols.websockets.auto',
|
|
'uvicorn.protocols.websockets.wsproto_impl',
|
|
'uvicorn.lifespan',
|
|
'uvicorn.lifespan.on',
|
|
'uvicorn.lifespan.off',
|
|
# ── FastAPI / Starlette ──
|
|
'fastapi',
|
|
'fastapi.middleware',
|
|
'fastapi.middleware.cors',
|
|
'fastapi.routing',
|
|
'fastapi.responses',
|
|
'fastapi.exceptions',
|
|
'starlette',
|
|
'starlette.routing',
|
|
'starlette.middleware',
|
|
'starlette.middleware.cors',
|
|
'starlette.responses',
|
|
'starlette.requests',
|
|
'starlette.concurrency',
|
|
'starlette.formparsers',
|
|
'starlette.staticfiles',
|
|
# ── Pydantic ──
|
|
'pydantic',
|
|
'pydantic.fields',
|
|
'pydantic_settings',
|
|
'pydantic_core',
|
|
# ── HTTP / networking ──
|
|
'httpx',
|
|
'httpcore',
|
|
'h11',
|
|
'httptools',
|
|
'anyio',
|
|
'anyio._backends',
|
|
'anyio._backends._asyncio',
|
|
'sniffio',
|
|
# ── MongoDB (motor/pymongo) ──
|
|
'motor',
|
|
'motor.motor_asyncio',
|
|
'pymongo',
|
|
'pymongo.errors',
|
|
'pymongo.collection',
|
|
'pymongo.database',
|
|
'pymongo.mongo_client',
|
|
# ── Async I/O ──
|
|
'aiofiles',
|
|
'aiofiles.os',
|
|
'aiofiles.ospath',
|
|
# ── Scientific ──
|
|
'numpy',
|
|
'numpy.core',
|
|
'scipy',
|
|
'scipy.special',
|
|
'scipy.interpolate',
|
|
'shapely',
|
|
'shapely.geometry',
|
|
'shapely.ops',
|
|
# ── Multipart ──
|
|
'multipart',
|
|
'python_multipart',
|
|
# ── Encoding ──
|
|
'email.mime',
|
|
'email.mime.multipart',
|
|
# ── Multiprocessing ──
|
|
'multiprocessing',
|
|
'multiprocessing.pool',
|
|
'multiprocessing.queues',
|
|
'concurrent.futures',
|
|
# ── CuPy + CUDA ──
|
|
'cupy',
|
|
'cupy.cuda',
|
|
'cupy.cuda.runtime',
|
|
'cupy.cuda.driver',
|
|
'cupy.cuda.memory',
|
|
'cupy.cuda.stream',
|
|
'cupy.cuda.device',
|
|
'cupy._core',
|
|
'cupy._core.core',
|
|
'cupy._core._routines_math',
|
|
'cupy._core._routines_logic',
|
|
'cupy._core._routines_manipulation',
|
|
'cupy._core._routines_sorting',
|
|
'cupy._core._routines_statistics',
|
|
'cupy._core._cub_reduction',
|
|
'cupy.fft',
|
|
'cupy.linalg',
|
|
'cupy.random',
|
|
'cupy_backends',
|
|
'cupy_backends.cuda',
|
|
'cupy_backends.cuda.api',
|
|
'cupy_backends.cuda.libs',
|
|
'fastrlock',
|
|
'fastrlock.rlock',
|
|
] + cupy_hiddenimports + cupyb_hiddenimports + fl_hiddenimports,
|
|
hookspath=[],
|
|
hooksconfig={},
|
|
runtime_hooks=[os.path.join(os.path.dirname(SPEC), 'rthook_cuda_dlls.py')],
|
|
# ── Exclude bloat ──
|
|
excludes=[
|
|
# GUI
|
|
'tkinter',
|
|
'matplotlib',
|
|
'PIL',
|
|
'IPython',
|
|
# Data science bloat
|
|
'pandas',
|
|
'tensorflow',
|
|
'torch',
|
|
'keras',
|
|
# Testing
|
|
'pytest',
|
|
|
|
# Jupyter
|
|
'jupyter',
|
|
'notebook',
|
|
'ipykernel',
|
|
# gRPC / telemetry (often pulled in by dependencies)
|
|
'grpc',
|
|
'grpcio',
|
|
'google.protobuf',
|
|
'opentelemetry',
|
|
'opentelemetry.sdk',
|
|
'opentelemetry.instrumentation',
|
|
# Ray (too heavy, we use multiprocessing)
|
|
'ray',
|
|
# Other
|
|
'cv2',
|
|
'sklearn',
|
|
'sympy',
|
|
],
|
|
noarchive=False,
|
|
)
|
|
|
|
pyz = PYZ(a.pure)
|
|
|
|
# ═══════════════════════════════════════════
|
|
# ONEDIR mode: EXE + COLLECT
|
|
# ═══════════════════════════════════════════
|
|
# Creates: dist/rfcp-server/rfcp-server.exe + all DLLs in same folder
|
|
# Better for CUDA — no temp extraction needed
|
|
|
|
exe = EXE(
|
|
pyz,
|
|
a.scripts,
|
|
[], # No binaries/datas in EXE — they go in COLLECT
|
|
exclude_binaries=True, # ONEDIR mode
|
|
name='rfcp-server',
|
|
debug=False,
|
|
bootloader_ignore_signals=False,
|
|
strip=False,
|
|
upx=False, # Don't compress — CUDA libs need fast loading
|
|
console=True,
|
|
icon=os.path.join(os.path.dirname(SPEC), 'rfcp.ico') if os.path.exists(os.path.join(os.path.dirname(SPEC), 'rfcp.ico')) else None,
|
|
)
|
|
|
|
coll = COLLECT(
|
|
exe,
|
|
a.binaries,
|
|
a.zipfiles,
|
|
a.datas,
|
|
strip=False,
|
|
upx=False,
|
|
upx_exclude=[],
|
|
name='rfcp-server',
|
|
)
|