Vast.ai GPU Streaming Guide
This guide covers deploying Hyperscape on Vast.ai for live streaming to Twitch, Kick, and X/Twitter with GPU-accelerated WebGPU rendering.Overview
Hyperscape uses a sophisticated streaming pipeline that captures gameplay from a Chrome browser running on an NVIDIA GPU and encodes it to RTMP streams using FFmpeg. Key Components:- WebGPU Rendering: Chrome with NVIDIA GPU via ANGLE/Vulkan
- Display Server: Xorg (preferred) or Xvfb (fallback)
- Audio Capture: PulseAudio with virtual sink
- Video Capture: Chrome DevTools Protocol (CDP) screencast
- Encoding: FFmpeg with H.264 (x264)
- Distribution: RTMP tee muxer for multi-platform streaming
Requirements
Hardware
- NVIDIA GPU with Vulkan support (GTX 1060 or better recommended)
- 8GB+ RAM (16GB recommended for stable streaming)
- 50GB+ storage for Docker images and game assets
Software (Auto-installed by deploy script)
- Ubuntu 20.04+ or Debian 11+
- NVIDIA drivers (auto-detected version)
- Docker
- Chrome Dev channel (google-chrome-unstable)
- FFmpeg
- PulseAudio
- Xorg or Xvfb
Architecture
GPU Rendering Modes
The deployment script tries rendering modes in this order:1. Xorg with NVIDIA (Preferred)
- Best performance with direct GPU access
- Requires DRI/DRM device access (
/dev/dri/card0) - Uses NVIDIA Xorg driver with headless configuration
- Validates GPU rendering (not software fallback)
2. Xvfb with NVIDIA Vulkan (Fallback)
- Virtual framebuffer provides X11 protocol
- Chrome uses NVIDIA GPU via ANGLE/Vulkan
- CDP captures frames from Chrome’s internal GPU rendering
- Works in containers without DRM access
3. Headless Mode (NOT SUPPORTED)
- WebGPU requires a display server
- Deployment fails if neither Xorg nor Xvfb can start
- No soft fallback to headless mode
Audio Capture
PulseAudio provides audio capture from Chrome:- Virtual Sink:
chrome_audionull sink created by deploy script - Monitor Device:
chrome_audio.monitorcaptures audio output - User Mode: PulseAudio runs in user mode (more reliable than system mode)
- Runtime Directory:
/tmp/pulse-runtimefor socket communication
Video Capture
Chrome DevTools Protocol (CDP) screencast provides frame capture:- CDP Connection: Playwright connects to Chrome via CDP
- Screencast:
Page.startScreencast()with JPEG encoding - Frame Piping: Direct JPEG frames to FFmpeg stdin
- Resolution Tracking: Automatic viewport recovery on mismatch
- Timeout Handling: 5s timeout on probe evaluate calls
- Recovery: Proceeds with capture after 5 consecutive probe timeouts
Encoding
FFmpeg encodes video with H.264 (x264): Default Settings (Optimized for Quality):RTMP Multi-Streaming
FFmpeg tee muxer enables simultaneous streaming to multiple platforms: Supported Platforms:- Twitch:
rtmps://live.twitch.tv/app - Kick:
rtmps://fa723fc1b171.global-contribute.live-video.net/app - X/Twitter:
rtmp://sg.pscp.tv:80/x - YouTube: Disabled by default (set
YOUTUBE_STREAM_KEY="")
Production Client Build
When streaming, use production client build for faster page loads: Problem: Vite dev server uses JIT compilation, causing 180s+ page load times that exceed browser timeout. Solution: Pre-build client and serve viavite preview:
- No on-demand module compilation
- Significantly faster page loads (< 10s)
- Prevents browser timeout issues
- Reduces CPU usage during streaming
Deployment
GitHub Secrets
Set these secrets in your GitHub repository (Settings → Secrets → Actions): Required:Deployment Script
Thescripts/deploy-vast.sh script handles complete setup:
Steps:
- DNS Configuration: Sets Google DNS (8.8.8.8, 8.8.4.4)
- Code Update: Pulls latest from
mainbranch - Secrets Restoration: Copies secrets from
/tmp/hyperscape-secrets.env - System Dependencies: Installs build tools, FFmpeg, PulseAudio, Vulkan
- GPU Validation: Verifies NVIDIA GPU via
nvidia-smi - Vulkan Setup: Forces NVIDIA ICD, checks
vulkaninfo - Display Server: Tries Xorg, falls back to Xvfb
- Chrome Installation: Installs Chrome Dev channel
- PulseAudio Setup: Creates
chrome_audiosink, starts daemon - Playwright Installation: Installs Chromium and dependencies
- Build: Builds core packages (physx, shared, etc.)
- Database Migration: Pushes schema with
drizzle-kit - Port Proxies: Sets up socat proxies (35143, 35079, 35144)
- Environment Persistence: Writes GPU/display settings to
.env - PM2 Startup: Starts duel stack via
ecosystem.config.cjs - Health Check: Waits for server to respond on port 5555
- Diagnostics: Checks streaming status, FFmpeg processes, logs
Port Mapping
Vast.ai requires port mapping for external access:| Internal | External | Service |
|---|---|---|
| 5555 | 35143 | HTTP API |
| 5555 | 35079 | WebSocket |
| 8080 | 35144 | CDN |
Troubleshooting
GPU Not Detected
Symptoms:nvidia-smifails or shows no GPU- Deployment script exits with “FATAL: nvidia-smi failed”
- Check Vast.ai instance: Ensure you selected an NVIDIA GPU instance
- Verify drivers:
nvidia-smishould show GPU info - Check CUDA:
nvcc --versionshould show CUDA version - Restart instance: Sometimes drivers need initialization
Vulkan Not Available
Symptoms:vulkaninfofails or shows no devices- Chrome logs “Vulkan not available”
- Check ICD:
ls /usr/share/vulkan/icd.d/nvidia_icd.json - Force NVIDIA ICD:
export VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/nvidia_icd.json - Install Vulkan tools:
apt install vulkan-tools mesa-vulkan-drivers - Check GPU:
vulkaninfo --summaryshould show NVIDIA device
Display Server Fails
Symptoms:- Xorg fails to start
- Xvfb fails to start
- Deployment exits with “FATAL ERROR: Cannot establish WebGPU-capable rendering mode”
- Check DRI devices:
ls -la /dev/dri/ - Check X logs:
cat /var/log/Xorg.99.log - Kill stale processes:
pkill -9 Xorg; pkill -9 Xvfb - Clean lock files:
rm -f /tmp/.X*-lock; rm -rf /tmp/.X11-unix - Verify display:
xdpyinfo -display :99
PulseAudio Issues
Symptoms:- No audio in stream
- FFmpeg errors about PulseAudio device
pactlcommands fail
- Check daemon:
pulseaudio --check(should exit silently if running) - List sinks:
pactl list short sinks(should showchrome_audio) - Restart PulseAudio:
- Create sink manually:
Stream Not Starting
Symptoms:- No FFmpeg processes running
- RTMP status file shows errors
- PM2 logs show capture failures
- Check game client:
curl http://localhost:3333(should return 200) - Check streaming API:
curl http://localhost:5555/api/streaming/state - Check RTMP status:
cat /root/hyperscape/packages/server/public/live/rtmp-status.json - Review PM2 logs:
bunx pm2 logs hyperscape-duel --lines 200 - Check FFmpeg:
ps aux | grep ffmpeg - Restart stack:
bunx pm2 restart hyperscape-duel
Black Frames / No Video
Symptoms:- Stream shows black screen
- CDP capture returns empty frames
- Chrome not rendering
- Check WebGPU: Open
http://localhost:3333in Chrome, check console for WebGPU errors - Verify GPU rendering:
glxinfo | grep "OpenGL renderer"(should show NVIDIA) - Check display:
echo $DISPLAY(should be:99or:0) - Test X server:
xdpyinfo -display $DISPLAY - Check Chrome flags: Review
stream-to-rtmp.tsfor correct GPU flags - Enable production build: Set
DUEL_USE_PRODUCTION_CLIENT=true
Browser Timeout
Symptoms:- “Navigation timeout” errors in logs
- Page takes > 180s to load
- Vite dev server slow
- Enable production build: Set
DUEL_USE_PRODUCTION_CLIENT=trueorNODE_ENV=production - Pre-build client:
cd packages/client && bun run build - Check Vite: Ensure
vite previewis used, notvite dev - Increase timeout: Modify
stream-to-rtmp.tsnavigation timeout (not recommended)
Resolution Mismatch
Symptoms:- CDP reports different resolution than expected
- Viewport restoration messages in logs
- Frames appear stretched or cropped
- Check configuration:
- Verify viewport: CDP automatically recovers viewport on mismatch
- Check browser zoom: Ensure Chrome zoom is 100%
- Review logs: Look for “Resolution mismatch detected” messages
Audio Desync
Symptoms:- Audio ahead or behind video
- Audio dropouts
- Crackling or stuttering
- Check buffering: Ensure
thread_queue_size=1024for audio input - Enable async resampling:
aresample=async=1000:first_pts=0 - Use wallclock timestamps:
use_wallclock_as_timestamps=1 - Increase buffer: Set
STREAM_BUFFER_SIZE=18000000(4x bitrate) - Check PulseAudio:
pactl list short sinksshould showchrome_audio
High CPU Usage
Symptoms:- CPU at 100%
- Dropped frames
- Laggy gameplay
- Use hardware encoding: Ensure NVIDIA GPU is used (not software)
- Lower preset: Set
STREAM_PRESET=veryfastorultrafast - Reduce bitrate: Set
STREAM_BITRATE=3000000(3 Mbps) - Lower resolution: Set
STREAM_CAPTURE_WIDTH=960andSTREAM_CAPTURE_HEIGHT=540 - Enable low-latency: Set
STREAM_LOW_LATENCY=true
Memory Leaks
Symptoms:- RAM usage grows over time
- OOM killer terminates processes
- System becomes unresponsive
- Restart PM2:
bunx pm2 restart hyperscape-duel - Check for zombie processes:
ps aux | grep -E "chrome|ffmpeg" - Monitor memory:
watch -n 1 free -h - Increase swap: Add swap file if RAM < 16GB
- Review logs: Look for memory warnings in PM2 logs
Monitoring
Health Checks
Server Health:PM2 Management
View Logs:System Monitoring
GPU Usage:Performance Tuning
Encoding Presets
| Preset | CPU Usage | Quality | Latency | Use Case |
|---|---|---|---|---|
| ultrafast | Very Low | Low | Very Low | Testing, low-end GPU |
| veryfast | Low | Medium | Low | Budget streaming |
| faster | Medium | Good | Medium | Balanced |
| fast | Medium-High | Good | Medium | Balanced |
| medium | High | Very Good | Medium | Default |
| slow | Very High | Excellent | High | High-quality archive |
medium for live streaming, slow for recording.
Bitrate Guidelines
| Resolution | Bitrate | Buffer | Use Case |
|---|---|---|---|
| 1920x1080 | 6000 kbps | 24000k | Full HD streaming |
| 1280x720 | 4500 kbps | 18000k | Default HD streaming |
| 960x540 | 2500 kbps | 10000k | Low-bandwidth |
| 640x360 | 1500 kbps | 6000k | Mobile/testing |
GOP Size
| GOP Size | Keyframe Interval | Latency | Seeking | Use Case |
|---|---|---|---|---|
| 30 | 1s @ 30fps | Very Low | Excellent | Low-latency |
| 60 | 2s @ 30fps | Low | Good | Default |
| 120 | 4s @ 30fps | Medium | Fair | High compression |
| 240 | 8s @ 30fps | High | Poor | Archive |
Advanced Configuration
Custom FFmpeg Flags
Editpackages/server/src/streaming/stream-to-rtmp.ts to add custom FFmpeg flags:
Multiple RTMP Destinations
Add custom destinations via environment variables:HLS Output
Enable HLS output for local playback:http://your-server:5555/live/stream.m3u8
Security
Secrets Management
NEVER commit secrets to git:- Use GitHub Secrets for CI/CD
- Store secrets in
/tmp/hyperscape-secrets.envon server - Secrets are copied to
packages/server/.envby deploy script .gitignoreblocks all.envfiles
- Stream keys (Twitch, Kick, X)
- Database URL
- JWT secret
- Solana private keys
- Arena bet write key
SSH Access
Vast.ai SSH:- Uses
appleboy/ssh-action - SSH key stored in
VAST_SSH_KEYsecret - Secrets written to
/tmp/hyperscape-secrets.envbefore deploy
Firewall
Vast.ai Firewall:- Only expose required ports (35143, 35079, 35144)
- Use socat proxies for port mapping
- Internal services (5555, 8080) not directly accessible
References
- Deployment Script:
scripts/deploy-vast.sh - Streaming Code:
packages/server/src/streaming/stream-to-rtmp.ts - PM2 Config:
packages/server/ecosystem.config.cjs - Environment Variables:
.env.example,packages/server/.env.example - Architecture: AGENTS.md
- Development Guide: CLAUDE.md
Support
For issues or questions:- GitHub Issues: HyperscapeAI/hyperscape/issues
- Discord: Join our Discord
- Documentation: CLAUDE.md, AGENTS.md