Script Python che intercetta i fallimenti CUDA su Tesla P40 (sm_6.1) e sposta automaticamente l'esecuzione dei kernel su NVIDIA RTX 3050 (sm_8.6), mantenendo i pesi del modello sulla P40 per sfruttare i 24 GB di VRAM.
Il sistema ha due GPU:
| GPU | CUDA Device | Capability | VRAM | Ruolo |
|---|---|---|---|---|
| Tesla P40 | CUDA 0 | sm_6.1 | 24 GB | Storage pesi + KV cache |
| RTX 3050 | CUDA 1 | sm_8.6 | 8 GB | Esecuzione kernel |
Problema: PyTorch 2.7.0+cu128 non supporta sm_6.1 (soglia minima: sm_7.5). Quando ExLlamaV2 v0.3.2 tenta di compilare JIT i kernel CUDA per la P40, fallisce con:
RuntimeError: CUDA error: no kernel image is available for execution on the device
Questo avviene perché i kernel CUDA sono compilati per sm_86 (RTX 3050), ma il dispositivo attivo è sm_61 (P40).
- P40 (sm_6.1): PyTorch 2.7 non supporta esecuzione kernel. Solo lettura/scrittura tensori.
- RTX 3050 (sm_8.6): Supporto completo PyTorch 2.7. VRAM limitata (~7.4 GB disponibili).
- ExLlamaV2 v0.3.2: Compila kernel JIT per la GPU su cui viene inizializzato. Non supporta multi-device nativo.
- Modello 35B Q4_K_XL: ~22 GB VRAM — non entra sulla RTX 3050.
┌─────────────────────────────────────────────────────────┐
│ Router CUDA │
│ │
│ 1. Carica pesi modello su P40 (CUDA:0) │
│ 2. Quando serve eseguire kernel → sposta su RTX 3050 │
│ 3. Esegui prefill/decode su RTX 3050 │
│ 4. Ritorna risultati su P40 │
│ 5. Ripeti per ogni layer │
└─────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌──────────┐ ┌────────────┐
│ P40 │ │ RTX 3050 │
│ 24 GB │ ←→ Tensor │ 7.4 GB │
│ Peseti │ copy │ Kernel │
│ KV cache │ (PCIe) │ execution │
└──────────┘ └────────────┘
Dataclass gerarchica con supporto env vars e JSON:
DeviceConfig: weights_device (P40), compute_device (RTX 3050), fallbackMigrationConfig: prefetch_layers, prefetch_async, pinned memory, integrity checkKvCacheConfig: cache_device, max_seq_len, head_dim, shard_sizeErrorHandlerConfig: max_retries, catch_patterns, timeout, tracebackLoggingConfig: level, file, formatRouterConfig: aggrega tutti i config,from_env(),from_file(),summary()
CudaError: eccezione wrapper con device e error_typeDeviceMismatchError: errore specifico kernel non disponibilecatch_cuda_errors(): decoratore che intercetta errori CUDA e triggera failoverretry_on_device_error(): retry automatico su device di fallback con contatorelog_error_context(): registra contesto completo (VRAM, stack trace, CUDA version)suppress_cuda_errors(): decoratore per operazioni non critiche
LayerSnapshot: cattura e ripristino di tutti i tensori di un layerLayerMigrationManager: manager principale con:copy_layer_to_compute(): copia layer da P40 → RTX 3050 con cacherestore_layer_from_compute(): ripristina layer su P40 dopo esecuzioneprefetch_layer(): precaricamento asincrono in background (thread)clear_compute_cache(): svuota cache layer su RTX 3050_verify_integrity(): verifica correttezza copia tensoriget_memory_stats(): statistiche memoria GPU
KvCacheBlock: blocco KV per singolo layer (key_cache + value_cache)update(): append incrementale con overflow protectionget(): lettura cache correntereset(): reset posizione
KvCacheManager: manager per tutti i layerupdate(): aggiorna cache layer specificoget(): recupera cache layerget_all(): recupera cache tutti i layerget_sequence_length(),get_remaining_slots(),is_full()get_memory_usage(): statistiche memoria cache- Calcolo memoria stimata in fase di init
LayerForwardHook: hook che intercetta forward di un layer- Copia layer su RTX 3050 → esegue forward → copia output su P40 → ripristina layer
CudaRouter: classe principale del router_verify_devices(): verifica disponibilità e capability GPUsetup_kv_cache(): configura e alloca KV cacheattach_hooks(): registra hook su tutti i layer del modellodetach_hooks(): rimuove hook dal modelloforward(): forward pass layer-by-layer con routing automaticogenerate(): wrapper per generazione testo con routing- Modalità
layer_by_layer(default) efull_copy(tutti i layer su RTX 3050)
┌─────────────────────────────────────────────────────────┐
│ CudaRouter │
│ │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ LayerForward │ │ LayerMigration │ │
│ │ Hook │ │ Manager │ │
│ │ │ │ │ │
│ │ 1. copy layer │→│ copy_layer_to │ │
│ │ 2. run forward │ │ compute() │ │
│ │ 3. copy output │ │ │ │
│ │ 4. restore │ │ prefetch_layer │ │
│ └────────────────┘ └────────────────┘ │
│ │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ KvCache │ │ Error │ │
│ │ Manager │ │ Handler │ │
│ │ │ │ │ │
│ │ allocate() │ │ catch_errors │ │
│ │ update() │ │ retry_on_error │ │
│ │ get() │ │ log_context │ │
│ └────────────────┘ └────────────────┘ │
└─────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌──────────┐ ┌────────────┐
│ P40 │ │ RTX 3050 │
│ 24 GB │ ←→ Tensor │ 7.4 GB │
│ Peseti │ copy │ Kernel │
│ KV cache │ (PCIe) │ execution │
└──────────┘ └────────────┘
- Python 3.12+
- PyTorch 2.7.0+cu128
- ExLlamaV2 v0.3.2+
- Due GPU CUDA: P40 (cuda:0) e RTX 3050 (cuda:1)
python3 -m pip install --user --break-system-packages torch torchvision torchaudio
python3 -m pip install --user --break-system-packages exllamav2La configurazione usa variabili d'ambiente o un file JSON:
# Variabili d'ambiente
export ROUTER_WEIGHTS_DEVICE=cuda:0
export ROUTER_COMPUTE_DEVICE=cuda:1
export ROUTER_PREFETCH_LAYERS=2
export ROUTER_MODE=layer_by_layerOppure crea un file router_config.json:
{
"device": {
"weights_device": "cuda:0",
"compute_device": "cuda:1",
"fallback_device": "cuda:1"
},
"migration": {
"prefetch_layers": 2,
"prefetch_async": true,
"use_pinned_memory": true
},
"kv_cache": {
"max_seq_len": 4096,
"head_dim": 0
},
"error_handler": {
"max_retries": 3
},
"mode": "layer_by_layer"
}import torch
from exllamav2 import ExLlamaV2, ExLlamaV2Config, ExLlamaV2Tokenizer
from config import RouterConfig
from cuda_router import CudaRouter
# 1. Carica modello sulla P40
config = ExLlamaV2Config()
config.model_dir = "/path/to/model"
model = ExLlamaV2(config)
model.load()
# 2. Configura router
router_config = RouterConfig()
router_config.kv_cache.max_seq_len = 4096
# 3. Crea e attiva router
router = CudaRouter(model, router_config)
router.attach_hooks()
# 4. Genera testo
output = router.generate("Il tuo prompt qui", max_new_tokens=256)
print(output)
# 5. Disattiva router quando finito
router.detach_hooks()from config import RouterConfig
from cuda_router import CudaRouter
# Caricamento da file JSON
config = RouterConfig.from_file("/path/to/router_config.json")
# Verifica configurazione
print(config.summary())
# Crea router
router = CudaRouter(model, config)
# Verifica dispositivi
router._verify_devices()
# Attiva routing
router.attach_hooks()from config import RouterConfig
from cuda_router import CudaRouter
config = RouterConfig()
config.kv_cache.max_seq_len = 8192
config.migration.prefetch_layers = 4
router = CudaRouter(model, config)
router.attach_hooks()config = RouterConfig()
config.mode = "full_copy" # Copia tutti i layer su RTX 3050
router = CudaRouter(model, config)
router.attach_hooks()from config import RouterConfig, LogLevel
config = RouterConfig()
config.logging.level = LogLevel.DEBUG
config.logging.log_file = "/tmp/router_debug.log"
config.migration.verify_copy_integrity = True
router = CudaRouter(model, config)from config import RouterConfig
config = RouterConfig.from_env()
router = CudaRouter(model, config)COMPLETATO — 2026-06-06
| Fase | Stato | Data |
|---|---|---|
| Fase 1 — Analisi e design | ✅ Completata | 2026-06-06 |
| Fase 2 — Implementazione core | ✅ Completata | 2026-06-06 |
| Fase 3 — Test e benchmark | ⏳ In attesa | — |
| Fase 4 — Documentazione vault | ✅ Completata | 2026-06-06 |
| File | Righe | Descrizione |
|---|---|---|
config.py |
~150 | Configurazione centralizzata (5 dataclass + from_env/from_file) |
error_handler.py |
~180 | Gestione errori CUDA (decoratori, retry, logging) |
layer_migration.py |
~200 | Migrazione layer tra GPU (snapshot, cache, prefetch async) |
kv_cache_manager.py |
~180 | Gestione KV cache (block, manager, overflow protection) |
cuda_router.py |
~350 | Core router (hook, forward layer-by-layer, ExLlamaV2 wrapper) |
README.md |
— | Questa documentazione |
PROGRESS.md |
— | Cronologia avanzamento |
- PyTorch 2.7 minimo sm_7.5 — la P40 (sm_6.1) è completamente incompatibile per esecuzione kernel
- Cross-device tensor copy funziona — i tensori possono essere spostati tra GPU
- llama-mtp 5d246a7 gira già sulla P40 a 44 tok/s — il router è utile solo per casi specifici ExLlamaV2
- VRAM RTX 3050 disponibile: ~7.4 GB (0.6 GB usato da Xorg)
- VRAM P40 disponibile: ~22 GB (modello 35B Q4_K_XL usa ~22 GB)