# 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', )