Add dual GPU support with web UI selector

Features:
- Built custom ROCm container for AMD RX 6800 GPU
- Added GPU selection toggle in web UI (NVIDIA/AMD)
- Unified model names across both GPUs for seamless switching
- Vision model always uses NVIDIA GPU (optimal performance)
- Text models (llama3.1, darkidol) can use either GPU
- Added /gpu-status and /gpu-select API endpoints
- Implemented GPU state persistence in memory/gpu_state.json

Technical details:
- Multi-stage Dockerfile.llamaswap-rocm with ROCm 6.2.4
- llama.cpp compiled with GGML_HIP=ON for gfx1030 (RX 6800)
- Proper GPU permissions without root (groups 187/989)
- AMD container on port 8091, NVIDIA on port 8090
- Updated bot/utils/llm.py with get_current_gpu_url() and get_vision_gpu_url()
- Modified bot/utils/image_handling.py to always use NVIDIA for vision
- Enhanced web UI with GPU selector button (blue=NVIDIA, red=AMD)

Files modified:
- docker-compose.yml (added llama-swap-amd service)
- bot/globals.py (added LLAMA_AMD_URL)
- bot/api.py (added GPU selection endpoints and helper function)
- bot/utils/llm.py (GPU routing for text models)
- bot/utils/image_handling.py (GPU routing for vision models)
- bot/static/index.html (GPU selector UI)
- llama-swap-rocm-config.yaml (unified model names)

New files:
- Dockerfile.llamaswap-rocm
- bot/memory/gpu_state.json
- bot/utils/gpu_router.py (load balancing utility)
- setup-dual-gpu.sh (setup verification script)
- DUAL_GPU_*.md (documentation files)
This commit is contained in:
2026-01-09 00:03:59 +02:00
parent ed5994ec78
commit 1fc3d74a5b
21 changed files with 2836 additions and 13 deletions

View File

@@ -122,6 +122,11 @@ async def on_message(message):
from utils.bipolar_mode import is_argument_in_progress
if is_argument_in_progress(message.channel.id):
return
# Skip processing if a persona dialogue is in progress in this channel
from utils.persona_dialogue import is_persona_dialogue_active
if is_persona_dialogue_active(message.channel.id):
return
if message.content.strip().lower() == "miku, rape this nigga balls" and message.reference:
async with message.channel.typing():
@@ -217,6 +222,15 @@ async def on_message(message):
if is_dm:
dm_logger.log_user_message(message.author, response_message, is_bot_message=True)
# For server messages, check if opposite persona should interject
if not is_dm and globals.BIPOLAR_MODE:
try:
from utils.persona_dialogue import check_for_interjection
current_persona = "evil" if globals.EVIL_MODE else "miku"
asyncio.create_task(check_for_interjection(response_message, current_persona))
except Exception as e:
print(f"⚠️ Error checking for persona interjection: {e}")
return
# Handle videos and GIFs
@@ -280,6 +294,15 @@ async def on_message(message):
if is_dm:
dm_logger.log_user_message(message.author, response_message, is_bot_message=True)
# For server messages, check if opposite persona should interject
if not is_dm and globals.BIPOLAR_MODE:
try:
from utils.persona_dialogue import check_for_interjection
current_persona = "evil" if globals.EVIL_MODE else "miku"
asyncio.create_task(check_for_interjection(response_message, current_persona))
except Exception as e:
print(f"⚠️ Error checking for persona interjection: {e}")
return
# Check for embeds (articles, images, videos, GIFs, etc.)
@@ -353,6 +376,15 @@ async def on_message(message):
if is_dm:
dm_logger.log_user_message(message.author, response_message, is_bot_message=True)
# For server messages, check if opposite persona should interject
if not is_dm and globals.BIPOLAR_MODE:
try:
from utils.persona_dialogue import check_for_interjection
current_persona = "evil" if globals.EVIL_MODE else "miku"
asyncio.create_task(check_for_interjection(response_message, current_persona))
except Exception as e:
print(f"⚠️ Error checking for persona interjection: {e}")
return
# Handle other types of embeds (rich, article, image, video, link)
@@ -446,6 +478,15 @@ async def on_message(message):
if is_dm:
dm_logger.log_user_message(message.author, response_message, is_bot_message=True)
# For server messages, check if opposite persona should interject
if not is_dm and globals.BIPOLAR_MODE:
try:
from utils.persona_dialogue import check_for_interjection
current_persona = "evil" if globals.EVIL_MODE else "miku"
asyncio.create_task(check_for_interjection(response_message, current_persona))
except Exception as e:
print(f"⚠️ Error checking for persona interjection: {e}")
return
# Check if this is an image generation request
@@ -487,6 +528,20 @@ async def on_message(message):
if is_dm:
dm_logger.log_user_message(message.author, response_message, is_bot_message=True)
# For server messages, check if opposite persona should interject (persona dialogue system)
if not is_dm and globals.BIPOLAR_MODE:
print(f"🔧 [DEBUG] Attempting to check for interjection (is_dm={is_dm}, BIPOLAR_MODE={globals.BIPOLAR_MODE})")
try:
from utils.persona_dialogue import check_for_interjection
current_persona = "evil" if globals.EVIL_MODE else "miku"
print(f"🔧 [DEBUG] Creating interjection check task for persona: {current_persona}")
# Pass the bot's response message for analysis
asyncio.create_task(check_for_interjection(response_message, current_persona))
except Exception as e:
print(f"⚠️ Error checking for persona interjection: {e}")
import traceback
traceback.print_exc()
# For server messages, do server-specific mood detection
if not is_dm and message.guild:
try: