This commit is contained in:
2026-03-27 17:45:51 +09:00
parent 9803b27741
commit 681472df58
6 changed files with 169 additions and 95 deletions
@@ -49,6 +49,9 @@ import kotlin.math.*
// service/AutoTradingManager.kt
typealias TradingDecisionCallback = (TradingDecision?, Boolean)->Unit
object AutoTradingManager {
val DETAILLOG = true
val LLM_PORT = 13080
val EMBEDDING_PORT = 13081
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
private var discoveryJob: Job? = null
@@ -418,10 +421,10 @@ object AutoTradingManager {
val config = KisSession.config
// LLM 서버 시작 (설정된 모델 경로 사용)
if (config.modelPath.isNotEmpty()) {
LlamaServerManager.startServer(binPath, config.modelPath,port = 8080)
LlamaServerManager.startServer(binPath, config.modelPath,port = LLM_PORT)
}
if (config.embedModelPath.isNotEmpty()) {
LlamaServerManager.startServer(binPath, config.embedModelPath, port = 8081)
LlamaServerManager.startServer(binPath, config.embedModelPath, port = EMBEDDING_PORT)
}
KisWebSocketManager.connect()
isSystemReadyToday = true
+83 -84
View File
@@ -7,6 +7,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import network.RagService
import util.HardwareDetector
import util.NetworkPortDiagnostic
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
@@ -50,6 +51,7 @@ object LlamaServerManager {
fun checkPortStatus(port: Int): String {
return try {
// netstat 명령어로 해당 포트를 점유 중인 프로세스 확인
val process = Runtime.getRuntime().exec("cmd /c netstat -ano | findstr :$port")
val reader = process.inputStream.bufferedReader()
@@ -67,110 +69,107 @@ object LlamaServerManager {
}
fun startServer(binPath: String, modelPath: String, port: Int) {
if (processes.containsKey(port) || modelPath.isBlank()) return
val os = System.getProperty("os.name").lowercase()
val arch = System.getProperty("os.arch").lowercase()
val isWin = os.contains("win")
val isMacArm = os.contains("mac") && (arch.contains("arm64") || arch.contains("aarch64"))
val cpuCores = Runtime.getRuntime().availableProcessors() // HardwareDetector.getCpuCores()와 동일
val hasGpu = HardwareDetector.hasNvidiaGpu()
val canUsePort = if (isWin) NetworkPortDiagnostic.testPortAvailability(port) else true
if (canUsePort) {
if (processes.containsKey(port) || modelPath.isBlank()) return
val cpuCores = Runtime.getRuntime().availableProcessors() // HardwareDetector.getCpuCores()와 동일
val hasGpu = HardwareDetector.hasNvidiaGpu()
val ratio = if (isWin) 0.5 else 0.7
val optimalThreads = (cpuCores * ratio).toInt().coerceIn(4, 16)
// 1. optimalThreads: 할당 비율 적용 및 최소/최대 범위 제한(Safety Boundary)
// 과도한 스레드 할당은 오히려 컨텍스트 스위칭 비용을 높여 성능을 저하시킬 수 있습니다.
val ratio = if (isWin) 0.5 else 0.7
val optimalThreads = (cpuCores * ratio).toInt().coerceIn(4, 16)
var optimalGpuLayers = if ((isWin && hasGpu) || isMacArm) 99 else 4
if(HardwareDetector.getCpuName().contains("i7")) {
optimalGpuLayers = 0
}
println("🖥️ OS: $os / Arch: $arch")
println("⚙️ 할당 스레드: $optimalThreads (Core: $cpuCores, Ratio: $ratio)")
println("🚀 GPU 레이어: $optimalGpuLayers (NVIDIA/MacArm: ${if(optimalGpuLayers == 99) "YES" else "NO"})")
// 2. optimalGpuLayers: GPU 가속 조건 (윈도우 NVIDIA 또는 맥 ARM)
var optimalGpuLayers = if ((isWin && hasGpu) || isMacArm) 99 else 4
if(HardwareDetector.getCpuName().contains("i7")) {
optimalGpuLayers = 0
}
println("🖥️ OS: $os / Arch: $arch")
println("⚙️ 할당 스레드: $optimalThreads (Core: $cpuCores, Ratio: $ratio)")
println("🚀 GPU 레이어: $optimalGpuLayers (NVIDIA/MacArm: ${if(optimalGpuLayers == 99) "YES" else "NO"})")
val command = mutableListOf(
binPath,
"-m", modelPath,
"--port", port.toString(),
"-c", if (port == AutoTradingManager.EMBEDDING_PORT) "512" else "8192",
"-ngl", optimalGpuLayers.toString(),
"-t", optimalThreads.toString(),
"--embedding"
)
if (port != AutoTradingManager.EMBEDDING_PORT) { // 텍스트 생성용 모델에만 적용
command.addAll(listOf(
"-b", "512", // Batch size (토큰 병렬 처리량 제한으로 연산 안정화)
"--threads-batch", optimalThreads.toString(),
"-fa","on" // Flash Attention 활성화 (메모리 절약 및 긴 컨텍스트 연산 안정성 증가)
))
}
scope.launch {
try {
val pb = ProcessBuilder(command)
// val (nGpuLayers, threads) = when {
// os.contains("mac") && (arch.contains("arm64") || arch.contains("aarch64")) -> 99 to 8
// isWin -> optimalGpuLayers to optimalThreads // NUC Core Ultra 7: GPU 레이어 40 내외, 스레드 12 권장
// else -> 0 to 4 // 인텔 맥 2017 등
// }
// 2. 윈도우 Vulkan 환경 변수 설정
if (isWin && binPath.contains("win-x64")) {
val env = pb.environment()
// 특정 GPU 선택 (내장 GPU가 여러 개일 경우)
// env["GGML_VULKAN_DEVICE"] = "0"
val command = mutableListOf(
binPath,
"-m", modelPath,
"--port", port.toString(),
"-c", if (port == 8081) "512" else "8192",
"-ngl", optimalGpuLayers.toString(),
"-t", optimalThreads.toString(),
"--embedding"
)
if (port != 8081) { // 텍스트 생성용 모델에만 적용
command.addAll(listOf(
"-b", "512", // Batch size (토큰 병렬 처리량 제한으로 연산 안정화)
"--threads-batch", optimalThreads.toString(),
"-fa","on" // Flash Attention 활성화 (메모리 절약 및 긴 컨텍스트 연산 안정성 증가)
))
}
scope.launch {
try {
val pb = ProcessBuilder(command)
// DLL 로드 경로 강제 지정 (bin 폴더 내 dll 참조)
val libraryPath = File(binPath).parentFile.absolutePath
val currentPath = System.getenv("PATH") ?: ""
env["PATH"] = "$libraryPath;$currentPath"
// 2. 윈도우 Vulkan 환경 변수 설정
if (isWin && binPath.contains("win-x64")) {
val env = pb.environment()
// 특정 GPU 선택 (내장 GPU가 여러 개일 경우)
// env["GGML_VULKAN_DEVICE"] = "0"
println("🔧 [Vulkan] 환경 변수 설정 완료: $libraryPath")
}
// DLL 로드 경로 강제 지정 (bin 폴더 내 dll 참조)
val libraryPath = File(binPath).parentFile.absolutePath
val currentPath = System.getenv("PATH") ?: ""
env["PATH"] = "$libraryPath;$currentPath"
pb.redirectErrorStream(true)
File(binPath).setExecutable(true)
println("🔧 [Vulkan] 환경 변수 설정 완료: $libraryPath")
}
val process = pb.start()
processes[port] = process
println("✅ AI 서버 시작 시도 (Port: $port, Model: ${File(modelPath).name})")
if (isWin) {
delay(3000)
pb.redirectErrorStream(true)
File(binPath).setExecutable(true)
val status = checkPortStatus(port)
println(status) // 콘솔 로그
TradingLogStore.addAnalyzer("System", "Port:$port", status, status.contains(""))
}
val process = pb.start()
processes[port] = process
println("✅ AI 서버 시작 시도 (Port: $port, Model: ${File(modelPath).name})")
val reader = BufferedReader(InputStreamReader(process.inputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
// 로그 출력 (디버깅용)
if (AutoTradingManager.DETAILLOG) println("[Server $port] $line")
delay(3000)
val status = checkPortStatus(port)
println(status) // 콘솔 로그
// UI 로그 스토어에도 기록 (TradingDecisionLog 등에서 확인 가능)
TradingLogStore.addAnalyzer("System", "Port:$port", status, status.contains(""))
val reader = BufferedReader(InputStreamReader(process.inputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
// 로그 출력 (디버깅용)
// println("[Server $port] $line")
if (line?.contains("server is listening") == true) {
println("🚀 AI 서버 준비 완료 (Port: $port)")
if (port == 8080){
AutoTradingManager.llmAnalyser = true
}
if (port == 8081){
AutoTradingManager.llmNews = true
}
if (processes.size > 1) {
println("[Cache] ${processes.size}")
RagService.active()
if (line?.contains("server is listening") == true) {
println("🚀 AI 서버 준비 완료 (Port: $port)")
if (port == AutoTradingManager.LLM_PORT){
AutoTradingManager.llmAnalyser = true
}
if (port == AutoTradingManager.EMBEDDING_PORT){
AutoTradingManager.llmNews = true
}
if (processes.size > 1) {
println("[Cache] ${processes.size}")
RagService.active()
}
}
}
} catch (e: Exception) {
println("❌ AI 서버 실행 실패 (Port: $port): ${e.message}")
processes.remove(port)
}
} catch (e: Exception) {
println("❌ AI 서버 실행 실패 (Port: $port): ${e.message}")
processes.remove(port)
}
}
} else {
println("🚨 포트 $port 가 보안 정책에 의해 막혀있어 서버 기동을 중단합니다.")
TradingLogStore.addAnalyzer("System", "Port:$port", "보안 정책에 의한 포트 차단 감지", false)
}
}
fun stopAll(): Boolean {