Spaces:
Sleeping
Sleeping
Commit Β·
efb4fe4
1
Parent(s): 6e467d1
feat: Add 3-tier quantization with auto hardware detection
Browse files- Tier 1: Qwen2.5-7B with 4-bit NF4 quantization (needs GPU 5.5GB+)
- Tier 2: Qwen2.5-1.5B with 8-bit quantization (GPU or CPU)
- Tier 3: Qwen2.5-0.5B bfloat16 fallback (CPU, default)
- Auto-detect GPU/CPU and select optimal quantization
- Auto-fallback: 7Bβ1.5B if GPU VRAM insufficient
- Configurable via .env: QUANTIZATION=auto/4bit/8bit/none
- Graceful fallback if bitsandbytes not installed
- Update requirements.txt with bitsandbytes>=0.43.0
- Update Dockerfile with GPU base image option
- .env.example +10 -0
- Dockerfile +5 -0
- requirements.txt +1 -0
- services/model_loader.py +125 -10
.env.example
CHANGED
|
@@ -4,3 +4,13 @@
|
|
| 4 |
# Qwen/Qwen2.5-1.5B-Instruct (Goldilocks - ~3GB, recommended for GPU)
|
| 5 |
# Qwen/Qwen2.5-7B-Instruct (Smartest - needs 4-bit quantization + GPU)
|
| 6 |
MODEL_ID=Qwen/Qwen2.5-0.5B-Instruct
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
# Qwen/Qwen2.5-1.5B-Instruct (Goldilocks - ~3GB, recommended for GPU)
|
| 5 |
# Qwen/Qwen2.5-7B-Instruct (Smartest - needs 4-bit quantization + GPU)
|
| 6 |
MODEL_ID=Qwen/Qwen2.5-0.5B-Instruct
|
| 7 |
+
|
| 8 |
+
# Quantization mode: auto, 4bit, 8bit, none
|
| 9 |
+
# auto = 4-bit on GPU for 7B, 8-bit on GPU for 1.5B, bf16 on CPU
|
| 10 |
+
# 4bit = force 4-bit quantization (requires GPU + bitsandbytes)
|
| 11 |
+
# 8bit = force 8-bit quantization
|
| 12 |
+
# none = disable quantization, uses bfloat16
|
| 13 |
+
QUANTIZATION=auto
|
| 14 |
+
|
| 15 |
+
# Double quantization for 4-bit (saves ~10% more memory)
|
| 16 |
+
USE_DOUBLE_QUANT=true
|
Dockerfile
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
|
|
| 1 |
FROM python:3.9
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
RUN useradd -m -u 1000 user
|
| 4 |
USER user
|
| 5 |
ENV PATH="/home/user/.local/bin:$PATH"
|
|
|
|
| 1 |
+
# CPU-optimized default image (works on HuggingFace free tier)
|
| 2 |
FROM python:3.9
|
| 3 |
|
| 4 |
+
# For GPU support (uncomment to use):
|
| 5 |
+
# FROM pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime
|
| 6 |
+
# RUN pip install bitsandbytes>=0.43.0
|
| 7 |
+
|
| 8 |
RUN useradd -m -u 1000 user
|
| 9 |
USER user
|
| 10 |
ENV PATH="/home/user/.local/bin:$PATH"
|
requirements.txt
CHANGED
|
@@ -7,3 +7,4 @@ torch
|
|
| 7 |
transformers
|
| 8 |
accelerate
|
| 9 |
sentencepiece
|
|
|
|
|
|
| 7 |
transformers
|
| 8 |
accelerate
|
| 9 |
sentencepiece
|
| 10 |
+
bitsandbytes>=0.43.0
|
services/model_loader.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
import os
|
| 2 |
import torch
|
| 3 |
-
from transformers import pipeline
|
| 4 |
from dotenv import load_dotenv
|
| 5 |
from pathlib import Path
|
| 6 |
|
|
@@ -8,25 +8,140 @@ env_path = Path(__file__).resolve().parent.parent / ".env"
|
|
| 8 |
load_dotenv(dotenv_path=env_path)
|
| 9 |
|
| 10 |
MODEL_ID = os.getenv("MODEL_ID", "Qwen/Qwen2.5-0.5B-Instruct")
|
|
|
|
|
|
|
| 11 |
|
| 12 |
_pipe = None
|
|
|
|
| 13 |
|
| 14 |
-
def
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
_pipe = pipeline(
|
| 20 |
"text-generation",
|
| 21 |
-
model=
|
| 22 |
torch_dtype=torch.bfloat16,
|
| 23 |
device_map="auto",
|
| 24 |
-
trust_remote_code=True
|
| 25 |
)
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
|
|
|
| 29 |
_pipe = None
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
return _pipe
|
| 31 |
|
| 32 |
def generate_text(messages, temperature=0.3, max_new_tokens=2000):
|
|
|
|
| 1 |
import os
|
| 2 |
import torch
|
| 3 |
+
from transformers import pipeline, BitsAndBytesConfig
|
| 4 |
from dotenv import load_dotenv
|
| 5 |
from pathlib import Path
|
| 6 |
|
|
|
|
| 8 |
load_dotenv(dotenv_path=env_path)
|
| 9 |
|
| 10 |
MODEL_ID = os.getenv("MODEL_ID", "Qwen/Qwen2.5-0.5B-Instruct")
|
| 11 |
+
QUANTIZATION = os.getenv("QUANTIZATION", "auto")
|
| 12 |
+
USE_DOUBLE_QUANT = os.getenv("USE_DOUBLE_QUANT", "true").lower() == "true"
|
| 13 |
|
| 14 |
_pipe = None
|
| 15 |
+
_current_model = None
|
| 16 |
|
| 17 |
+
def _log(msg: str):
|
| 18 |
+
print(f"[ModelLoader] {msg}")
|
| 19 |
+
|
| 20 |
+
def _has_gpu() -> bool:
|
| 21 |
+
return torch.cuda.is_available()
|
| 22 |
+
|
| 23 |
+
def _gpu_name() -> str:
|
| 24 |
+
if _has_gpu():
|
| 25 |
+
return torch.cuda.get_device_name(0)
|
| 26 |
+
return "None"
|
| 27 |
+
|
| 28 |
+
def _gpu_memory_gb() -> float:
|
| 29 |
+
if _has_gpu():
|
| 30 |
try:
|
| 31 |
+
return torch.cuda.get_device_properties(0).total_mem / 1e9
|
| 32 |
+
except:
|
| 33 |
+
return 0
|
| 34 |
+
return 0
|
| 35 |
+
|
| 36 |
+
def _select_quantization() -> str:
|
| 37 |
+
"""Auto-select quantization tier based on MODEL_ID and hardware."""
|
| 38 |
+
user_mode = QUANTIZATION.lower()
|
| 39 |
+
|
| 40 |
+
if user_mode == "none":
|
| 41 |
+
return "none"
|
| 42 |
+
|
| 43 |
+
if user_mode != "auto":
|
| 44 |
+
return user_mode
|
| 45 |
+
|
| 46 |
+
# Auto-detect: GPU with enough VRAM for requested model
|
| 47 |
+
if "7B" in MODEL_ID:
|
| 48 |
+
if _has_gpu() and _gpu_memory_gb() >= 5.5:
|
| 49 |
+
_log(f"7B model detected, GPU {_gpu_name()} ({_gpu_memory_gb():.1f}GB) β using 4-bit")
|
| 50 |
+
return "4bit"
|
| 51 |
+
_log("7B model requested but no GPU with 5.5GB+ VRAM β falling back to 1.5B 8-bit")
|
| 52 |
+
return "cpu_fallback_8bit"
|
| 53 |
+
|
| 54 |
+
if "1.5B" in MODEL_ID:
|
| 55 |
+
if _has_gpu():
|
| 56 |
+
_log(f"1.5B model detected, GPU available β using 8-bit")
|
| 57 |
+
return "8bit"
|
| 58 |
+
_log("1.5B model detected, CPU only β using bfloat16")
|
| 59 |
+
return "none"
|
| 60 |
+
|
| 61 |
+
return "none"
|
| 62 |
+
|
| 63 |
+
def _build_model_kwargs(quant_mode: str) -> dict:
|
| 64 |
+
"""Build pipeline kwargs based on quantization mode."""
|
| 65 |
+
kwargs = {
|
| 66 |
+
"trust_remote_code": True,
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
if quant_mode == "4bit":
|
| 70 |
+
kwargs["device_map"] = "auto"
|
| 71 |
+
kwargs["quantization_config"] = BitsAndBytesConfig(
|
| 72 |
+
load_in_4bit=True,
|
| 73 |
+
bnb_4bit_compute_dtype=torch.bfloat16,
|
| 74 |
+
bnb_4bit_use_double_quant=USE_DOUBLE_QUANT,
|
| 75 |
+
bnb_4bit_quant_type="nf4",
|
| 76 |
+
)
|
| 77 |
+
_log("β‘ 4-bit quantization enabled (NF4, double quant)")
|
| 78 |
+
|
| 79 |
+
elif quant_mode == "8bit":
|
| 80 |
+
kwargs["device_map"] = "auto"
|
| 81 |
+
kwargs["quantization_config"] = BitsAndBytesConfig(
|
| 82 |
+
load_in_8bit=True,
|
| 83 |
+
)
|
| 84 |
+
_log("β‘ 8-bit quantization enabled")
|
| 85 |
+
|
| 86 |
+
elif quant_mode == "cpu_fallback_8bit":
|
| 87 |
+
kwargs["device_map"] = "auto"
|
| 88 |
+
kwargs["quantization_config"] = BitsAndBytesConfig(
|
| 89 |
+
load_in_8bit=True,
|
| 90 |
+
)
|
| 91 |
+
_log("β‘ CPU fallback 8-bit for 1.5B model")
|
| 92 |
+
|
| 93 |
+
else:
|
| 94 |
+
kwargs["torch_dtype"] = torch.bfloat16
|
| 95 |
+
kwargs["device_map"] = "auto"
|
| 96 |
+
_log(f"π¦ Loading {MODEL_ID} in bfloat16 (CPU-friendly)")
|
| 97 |
+
|
| 98 |
+
return kwargs
|
| 99 |
+
|
| 100 |
+
def get_pipe():
|
| 101 |
+
global _pipe, _current_model
|
| 102 |
+
|
| 103 |
+
if _pipe is not None:
|
| 104 |
+
return _pipe
|
| 105 |
+
|
| 106 |
+
actual_model_id = MODEL_ID
|
| 107 |
+
quant_mode = _select_quantization()
|
| 108 |
+
|
| 109 |
+
# Handle CPU fallback for 7B β 1.5B
|
| 110 |
+
if quant_mode == "cpu_fallback_8bit":
|
| 111 |
+
actual_model_id = "Qwen/Qwen2.5-1.5B-Instruct"
|
| 112 |
+
_log(f"π Fallback: loading {actual_model_id} instead of {MODEL_ID}")
|
| 113 |
+
|
| 114 |
+
_log(f"π Loading {actual_model_id} (quantization: {quant_mode})")
|
| 115 |
+
_log(f" Hardware: GPU={_gpu_name()}, VRAM={_gpu_memory_gb():.1f}GB, CUDA={_has_gpu()}")
|
| 116 |
+
|
| 117 |
+
try:
|
| 118 |
+
kwargs = _build_model_kwargs(quant_mode)
|
| 119 |
+
_pipe = pipeline(
|
| 120 |
+
"text-generation",
|
| 121 |
+
model=actual_model_id,
|
| 122 |
+
**kwargs
|
| 123 |
+
)
|
| 124 |
+
_current_model = actual_model_id
|
| 125 |
+
_log("β
Model loaded successfully!")
|
| 126 |
+
except ImportError as e:
|
| 127 |
+
if "bitsandbytes" in str(e):
|
| 128 |
+
_log("β bitsandbytes not installed. Falling back to CPU bfloat16.")
|
| 129 |
_pipe = pipeline(
|
| 130 |
"text-generation",
|
| 131 |
+
model=actual_model_id,
|
| 132 |
torch_dtype=torch.bfloat16,
|
| 133 |
device_map="auto",
|
| 134 |
+
trust_remote_code=True,
|
| 135 |
)
|
| 136 |
+
_current_model = actual_model_id
|
| 137 |
+
_log("β
Model loaded with CPU fallback")
|
| 138 |
+
else:
|
| 139 |
+
_log(f"β Model load failed: {e}")
|
| 140 |
_pipe = None
|
| 141 |
+
except Exception as e:
|
| 142 |
+
_log(f"β Model load failed: {e}")
|
| 143 |
+
_pipe = None
|
| 144 |
+
|
| 145 |
return _pipe
|
| 146 |
|
| 147 |
def generate_text(messages, temperature=0.3, max_new_tokens=2000):
|