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:
@@ -635,7 +635,12 @@
|
||||
|
||||
<div class="panel">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
||||
<h1 id="panel-title">Miku Control Panel</h1>
|
||||
<div style="display: flex; gap: 1rem; align-items: center;">
|
||||
<h1 id="panel-title">Miku Control Panel</h1>
|
||||
<button id="gpu-selector-toggle" onclick="toggleGPU()" style="background: #2a5599; color: #fff; padding: 0.5rem 1rem; border: 2px solid #4a7bc9; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 0.9rem;">
|
||||
🎮 GPU: NVIDIA
|
||||
</button>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.5rem; align-items: center;">
|
||||
<button id="bipolar-mode-toggle" onclick="toggleBipolarMode()" style="background: #333; color: #fff; padding: 0.5rem 1rem; border: 2px solid #666; border-radius: 4px; cursor: pointer; font-weight: bold;">
|
||||
🔄 Bipolar: OFF
|
||||
@@ -804,7 +809,33 @@
|
||||
<!-- Bipolar Mode Section (only visible when bipolar mode is on) -->
|
||||
<div id="bipolar-section" class="section" style="display: none; border: 2px solid #9932CC; padding: 1rem; border-radius: 8px; background: #1a1a2e;">
|
||||
<h3 style="color: #9932CC;">🔄 Bipolar Mode Controls</h3>
|
||||
<p style="font-size: 0.9rem; color: #aaa;">Trigger arguments between Regular Miku and Evil Miku</p>
|
||||
<p style="font-size: 0.9rem; color: #aaa;">Trigger arguments or dialogues between Regular Miku and Evil Miku</p>
|
||||
|
||||
<!-- Persona Dialogue Section -->
|
||||
<div style="margin-bottom: 2rem; padding: 1rem; background: #252540; border-radius: 8px; border: 1px solid #555;">
|
||||
<h4 style="color: #6B8EFF; margin-bottom: 0.5rem;">💬 Trigger Persona Dialogue</h4>
|
||||
<p style="font-size: 0.85rem; color: #999; margin-bottom: 1rem;">Start a natural conversation between the personas (can escalate to argument if tension builds)</p>
|
||||
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<label for="dialogue-message-id">Message ID:</label>
|
||||
<input type="text" id="dialogue-message-id" placeholder="e.g., 1234567890123456789" style="width: 250px; margin-left: 0.5rem; font-family: monospace;">
|
||||
</div>
|
||||
|
||||
<div style="font-size: 0.8rem; color: #888; margin-bottom: 1rem;">
|
||||
💡 <strong>Tip:</strong> Right-click any bot response message in Discord and select "Copy Message ID". The opposite persona will analyze it and decide whether to interject.
|
||||
</div>
|
||||
|
||||
<button onclick="triggerPersonaDialogue()" style="background: #6B8EFF; color: #fff; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer;">
|
||||
💬 Trigger Dialogue
|
||||
</button>
|
||||
|
||||
<div id="dialogue-status" style="margin-top: 1rem; font-size: 0.9rem;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Argument Section -->
|
||||
<div style="padding: 1rem; background: #2e1a2e; border-radius: 8px; border: 1px solid #555;">
|
||||
<h4 style="color: #9932CC; margin-bottom: 0.5rem;">⚔️ Trigger Argument</h4>
|
||||
<p style="font-size: 0.85rem; color: #999; margin-bottom: 1rem;">Force an immediate argument (bypasses dialogue system)</p>
|
||||
|
||||
<div style="margin-bottom: 1rem; display: flex; gap: 1rem; flex-wrap: wrap;">
|
||||
<div>
|
||||
@@ -832,6 +863,7 @@
|
||||
</button>
|
||||
|
||||
<div id="bipolar-status" style="margin-top: 1rem; font-size: 0.9rem;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Scoreboard Display -->
|
||||
<div id="bipolar-scoreboard" style="margin-top: 1.5rem; padding: 1rem; background: #0f0f1e; border-radius: 8px; border: 1px solid #444;">
|
||||
@@ -1416,6 +1448,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
loadLogs();
|
||||
checkEvilModeStatus(); // Check evil mode on load
|
||||
checkBipolarModeStatus(); // Check bipolar mode on load
|
||||
checkGPUStatus(); // Check GPU selection on load
|
||||
console.log('🚀 DOMContentLoaded - initializing figurine subscribers list');
|
||||
refreshFigurineSubscribers();
|
||||
loadProfilePictureMetadata();
|
||||
@@ -2194,6 +2227,59 @@ function updateEvilModeUI() {
|
||||
updateBipolarToggleVisibility();
|
||||
}
|
||||
|
||||
// GPU Selection Management
|
||||
let selectedGPU = 'nvidia'; // 'nvidia' or 'amd'
|
||||
|
||||
async function checkGPUStatus() {
|
||||
try {
|
||||
const response = await fetch('/gpu-status');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
selectedGPU = data.gpu || 'nvidia';
|
||||
updateGPUUI();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to check GPU status:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleGPU() {
|
||||
try {
|
||||
const toggleBtn = document.getElementById('gpu-selector-toggle');
|
||||
toggleBtn.disabled = true;
|
||||
toggleBtn.textContent = '⏳ Switching...';
|
||||
|
||||
const result = await apiCall('/gpu-select', 'POST', {
|
||||
gpu: selectedGPU === 'nvidia' ? 'amd' : 'nvidia'
|
||||
});
|
||||
|
||||
selectedGPU = result.gpu;
|
||||
updateGPUUI();
|
||||
|
||||
const gpuName = selectedGPU === 'nvidia' ? 'NVIDIA GTX 1660' : 'AMD RX 6800';
|
||||
showNotification(`🎮 Switched to ${gpuName}!`);
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle GPU:', error);
|
||||
showNotification('Failed to switch GPU: ' + error.message, 'error');
|
||||
toggleBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function updateGPUUI() {
|
||||
const toggleBtn = document.getElementById('gpu-selector-toggle');
|
||||
|
||||
if (selectedGPU === 'amd') {
|
||||
toggleBtn.textContent = '🎮 GPU: AMD';
|
||||
toggleBtn.style.background = '#c91432';
|
||||
toggleBtn.style.borderColor = '#e91436';
|
||||
} else {
|
||||
toggleBtn.textContent = '🎮 GPU: NVIDIA';
|
||||
toggleBtn.style.background = '#2a5599';
|
||||
toggleBtn.style.borderColor = '#4a7bc9';
|
||||
}
|
||||
toggleBtn.disabled = false;
|
||||
}
|
||||
|
||||
// Bipolar Mode Management
|
||||
let bipolarMode = false;
|
||||
|
||||
@@ -2266,6 +2352,48 @@ function updateBipolarToggleVisibility() {
|
||||
bipolarToggle.style.display = 'block';
|
||||
}
|
||||
|
||||
async function triggerPersonaDialogue() {
|
||||
const messageIdInput = document.getElementById('dialogue-message-id').value.trim();
|
||||
const statusDiv = document.getElementById('dialogue-status');
|
||||
|
||||
if (!messageIdInput) {
|
||||
showNotification('Please enter a message ID', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate message ID format (should be numeric)
|
||||
if (!/^\d+$/.test(messageIdInput)) {
|
||||
showNotification('Invalid message ID format - should be a number', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
statusDiv.innerHTML = '<span style="color: #6B8EFF;">⏳ Analyzing message for dialogue trigger...</span>';
|
||||
|
||||
const requestBody = {
|
||||
message_id: messageIdInput
|
||||
};
|
||||
|
||||
const result = await apiCall('/bipolar-mode/trigger-dialogue', 'POST', requestBody);
|
||||
|
||||
if (result.status === 'error') {
|
||||
statusDiv.innerHTML = `<span style="color: #ff4444;">❌ ${result.message}</span>`;
|
||||
showNotification(result.message, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
statusDiv.innerHTML = `<span style="color: #00ff00;">✅ ${result.message}</span>`;
|
||||
showNotification(`💬 ${result.message}`);
|
||||
|
||||
// Clear the input
|
||||
document.getElementById('dialogue-message-id').value = '';
|
||||
|
||||
} catch (error) {
|
||||
statusDiv.innerHTML = `<span style="color: #ff4444;">❌ Failed to trigger dialogue: ${error.message}</span>`;
|
||||
showNotification(`Error: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function triggerBipolarArgument() {
|
||||
const channelIdInput = document.getElementById('bipolar-channel-id').value.trim();
|
||||
const messageIdInput = document.getElementById('bipolar-message-id').value.trim();
|
||||
|
||||
Reference in New Issue
Block a user