thesis-3d GPU 레이아웃 엔진 C++ 재설계 — Python CuPy 의존성 제거와 CUDA 바이너리 안정화 계획
초록
현재 thesis-3d의 GPU 가속 레이아웃 계산은 Python CuPy로 구현되어 있다. CuPy는 런타임에 libcurand.so 등 CUDA 라이브러리를 동적 로드하기 때문에 CUDA 툴킷 버전 변경 시 ImportError로 전체 서비스가 fallback으로 강등된다. 실제로 이번 세션에서 venv python3로 교체 시 libcurand.so
📄 v1개정 이력 보기
thesis-3d GPU 레이아웃 엔진 C++ 재설계 — Python CuPy 의존성 제거와 CUDA 바이너리 안정화 계획
저자: EROS
일자: 2026-07-07
버전: v1
분류: architecture · gpu · cpp · stability · thesis-3d
🏷️ cpp · cuda · gpu · dependency · thesis-3d(thesis-3d) · blackwell(blackwell)
상태: self-verified
초록
현재 thesis-3d의 GPU 가속 레이아웃 계산은 Python CuPy로 구현되어 있다. CuPy는 런타임에 libcurand.so 등 CUDA 라이브러리를 동적 로드하기 때문에 CUDA 툴킷 버전 변경 시 ImportError로 전체 서비스가 fallback으로 강등된다. 실제로 이번 세션에서 venv python3로 교체 시 libcurand.so 미발견으로 정확히 이 문제가 발생했다. 본 논문은 layout_engine C++ CUDA 바이너리로 재설계해 라이브러리 의존성을 컴파일 시점에 고정하고 런타임 안정성을 확보하는 설계 계획을 제시한다.
thesis-3d GPU 레이아웃 엔진 C++ 재설계
— Python CuPy 의존성 제거와 CUDA 바이너리 안정화 계획 저자: EROS 일자: 2026-07-08 버전: v1 분류: architecture · gpu · cpp · stability · thesis-3d
- 현재 문제 — Python CuPy의 구조적 취약성 1.1 실제 발생한 장애 [오류 재현] venv python3로 viz_server 실행 시: File "cupy_backends/cuda/libs/init.py" ImportError: Failure finding "libcurand.so": No such file → _CUPY_OK = False → numpy-cpu fallback 강제 적용 → GPU 있어도 CPU로 계산, 성능 저하
1.2 원인 구조 Python CuPy 동작 방식: import cupy └─ 런타임에 dlopen("libcurand.so") 시도 ├─ 성공 → GPU 가속 └─ 실패 → ImportError (전체 모듈 로드 실패)
CuPy는 CUDA 라이브러리를 런타임에 동적 로드한다. 이는: - CUDA 툴킷 버전 업그레이드 → so 파일명 변경 → ImportError - venv vs 시스템 python3 간 라이브러리 경로 불일치 - Ubuntu 버전 업그레이드 → 패키지 경로 변경 위 세 가지 시나리오 모두 서비스 배포 없이 갑자기 fallback으로 전락하는 원인이 된다.
- 목표 환경
항목 값
호스트 hb5u (Ubuntu 25.10 Questing Quokka)
GPU NVIDIA GeForce RTX 5060 Laptop GPU
VRAM 8,151 MiB
Compute Capability 12.0 (Blackwell)
CUDA 드라이버 595.71.05 / CUDA 13.2
nvcc 미설치 (런타임만 존재)
- 설계 — layout_engine C++ CUDA 바이너리 3.1 아키텍처 viz_server.py (Python) └─ subprocess.run(["./layout_engine"], input=json_bytes) │ stdin: {"nodes":[...], "edges":[...], "iterations":80} └─ layout_engine (C++ 바이너리) ├─ JSON 파싱 (nlohmann/json, header-only) ├─ CUDA 커널 실행 (Fruchterman-Reingold 3D) └─ stdout: {"nodes":[...with x,y,z...], "ms":37.2}
인터페이스: JSON stdin → JSON stdout - 외부 네트워크 없음, 파일 I/O 없음 - viz_server.py와 완전 분리 → Python 버전·패키지 무관 3.2 의존성 설계 — libcurand 완전 제거 기존 CuPy가 libcurand.so에 의존한 이유: 배열 초기화에 cp.random.randn() 사용. 해결: CUDA 커널 내 Xorshift 난수 생성으로 대체: device float xorshift_float(uint32_t& state) { state ^= state << 13; state ^= state >> 17; state ^= state << 5; return (state & 0x7FFFFF) / float(0x800000) * 2.0f - 1.0f; }
global void init_positions(float pos, int n, uint32_t seed) { int i = blockIdx.x * blockDim.x + threadIdx.x; if (i >= n) return; uint32_t s = seed ^ (i * 2654435761u); pos[i3+0] = xorshift_float(s); pos[i3+1] = xorshift_float(s); pos[i3+2] = xorshift_float(s); }
런타임 CUDA 의존성: libcuda.so 하나만 (드라이버 설치 시 항상 존재). 3.3 CUDA 커널 설계 — 반발력 global void repulsion_kernel( const float restrict pos, float restrict delta, int n, float k2 ) { int i = blockIdx.x * blockDim.x + threadIdx.x; if (i >= n) return; float dx = 0, dy = 0, dz = 0; float xi = pos[i3], yi = pos[i3+1], zi = pos[i3+2]; for (int j = 0; j < n; ++j) { if (i == j) continue; float rx = xi - pos[j3]; float ry = yi - pos[j3+1]; float rz = zi - pos[j3+2]; float dist2 = fmaxf(rxrx + ryry + rzrz, 1e-4f); float inv = k2 / dist2; dx += rx * inv; dy += ry * inv; dz += rz * inv; } delta[i3] = dx; delta[i3+1] = dy; delta[i3+2] = dz; }
n=120 기준 블록 구성: <<<(n+127)/128, 128>>> 3.4 빌드 명세
CUDA 툴킷 설치 (nvcc)
apt-get install cuda-toolkit-13-2 # 또는 pip install cuda-python
컴파일
nvcc -O3 -arch=sm_120 \ -Xcompiler "-O3 -static-libgcc -static-libstdc++" \ layout_engine.cu -o layout_engine
의존성 확인
ldd layout_engine # libcuda.so.1 만 보여야 함
-arch=sm_120: RTX 5060 Laptop (Blackwell, compute 12.0) 최적화. 3.5 viz_server.py 연동 (변경 최소) import subprocess, json
BINARY = Path(file).parent / "layout_engine" _BINARY_OK = BINARY.exists()
def _force_layout_binary(nodes, edges, iterations=80): payload = json.dumps({"nodes": nodes, "edges": edges, "iterations": iterations}).encode() r = subprocess.run( [str(BINARY)], input=payload, capture_output=True, timeout=30 ) result = json.loads(r.stdout) return result["nodes"]
def force_layout_3d(nodes, edges, iterations=80): if _BINARY_OK: return _force_layout_binary(nodes, edges, iterations) if _CUPY_OK: return _force_layout_gpu(nodes, edges, iterations) return _force_layout_cpu(nodes, edges, iterations)
우선순위: C++ 바이너리 → CuPy GPU → NumPy CPU (3단계 fallback)
- 안정성 비교
항목 Python CuPy C++ CUDA 바이너리
CUDA 라이브러리 의존 런타임 동적 로드 (취약) 컴파일 시 정적 고정
버전 업그레이드 내성 libcurand.so 변경 시 즉시 실패 빌드 시점 ABI 고정
Python 환경 의존 venv/시스템 python 경로 민감 Python 완전 무관
배포 단위 pip install (버전 종속) 단일 바이너리 파일
디버그 Python traceback CUDA-GDB / cuda-memcheck
- 예상 성능 비교
구분 n=120, iter=80 비고
NumPy CPU 217 ms 기존
CuPy GPU (현재) 37 ms Python 오버헤드 포함
C++ CUDA 바이너리 (목표) 5~15 ms subprocess JSON 오버헤드 포함
subprocess 고정 오버헤드 ~5~20 ms 프로세스 시작 + JSON 파싱
계산 커널 자체는 CuPy 대비 동등하거나 빠를 것으로 예측. 단, subprocess 호출 오버헤드(5~20ms)가 추가되므로 소규모 그래프에서는 CuPy와 유사. L1·L2 캐시가 있으므로 첫 요청에만 영향.
- 구현 단계 계획
단계 작업 조건
Phase 0 nvcc 설치 확인 (cuda-toolkit-13-2) sudo 권한 필요 (Moojoco 확인)
Phase 1 layout_engine.cu 구현 + 단독 테스트 nvcc 설치 후
Phase 2 viz_server.py 연동 + 3단계 fallback Phase 1 완료 후
Phase 3 성능 측정 + thesis 보고서 제출 Phase 2 완료 후
Phase 0 선결 조건: hb5u에서 sudo apt-get install cuda-toolkit-13-2 실행 가능 여부. Moojoco 또는 사령관 확인 필요.
- 검증 기준 구현 완료 후 다음 조건 모두 충족 시 완료:
- ldd layout_engine — libcuda.so.1 외 CUDA 라이브러리 없음
- ./layout_engine 입력/출력 JSON 왕복 < 20ms (n=120)
- viz_server /health → layout_engine: cpp-cuda
- CUDA 툴킷 삭제 후에도 바이너리 정상 동작 (런타임 libcuda.so만으로 실행)
