Spaces:
Running on Zero
Running on Zero
| """ | |
| Model loading for HuggingFace Spaces ZeroGPU deployment. | |
| """ | |
| import torch | |
| from transformers import ( | |
| AutoTokenizer, | |
| AutoModelForCausalLM, | |
| BitsAndBytesConfig, | |
| pipeline, | |
| ) | |
| from langchain_huggingface import HuggingFaceEmbeddings | |
| from config import MODEL_ID, EMBEDDING_MODEL_ID, MAX_NEW_TOKENS | |
| from logging_config import get_logger | |
| logger = get_logger(__name__) | |
| # --- Tokenizer ------------------------------- | |
| logger.info(f"Loading tokenizer: {MODEL_ID}") | |
| tokenizer = AutoTokenizer.from_pretrained( | |
| MODEL_ID, | |
| trust_remote_code=True, # Qwen has a custom tokenizer class | |
| clean_up_tokenization_spaces=False, # BPE tokenizers must NOT strip spaces before punctuation — it | |
| # corrupts output. This flag is meant for WordPiece (BERT) only. | |
| ) | |
| # --- Quantization config ----------------------------------- | |
| # 4-bit NF4 (Normal Float 4-bit) is the most accurate 4-bit format for LLM weights. | |
| # double_quant saves an additional ~0.4 bits per parameter on the quantization constants themselves. | |
| quantization_config = BitsAndBytesConfig( | |
| load_in_4bit=True, | |
| bnb_4bit_use_double_quant=True, | |
| bnb_4bit_quant_type="nf4", | |
| bnb_4bit_compute_dtype=torch.bfloat16, | |
| ) | |
| # --- Model -------------------------------------------------- | |
| # device_map={"": 0} pins the ENTIRE model onto GPU slot 0 at load time. | |
| # This is the module-scope CUDA placement ZeroGPU requires — the | |
| # `spaces` package (imported first in app.py) intercepts this and | |
| # handles it correctly even though no physical GPU is attached yet | |
| # during this import. | |
| logger.info(f"Loading model (4-bit NF4 quantized) — device_map={{'':0}}…") | |
| model = AutoModelForCausalLM.from_pretrained( | |
| MODEL_ID, | |
| quantization_config=quantization_config, | |
| device_map={"": 0}, # Module-scope GPU placement (see docstring) | |
| low_cpu_mem_usage=True, # Streams weights from disk, avoids RAM spike | |
| trust_remote_code=True, | |
| ) | |
| model.eval() # Disable dropout — we're doing inference only | |
| # NOTE: No torch.compile() here. | |
| # ZeroGPU does not support torch.compile. Calling it would either | |
| # silently fail to provide speedup or raise an error when the first | |
| # @spaces.GPU-decorated call tries to actually run on a real GPU worker. | |
| # --- Text generation pipeline -------------------------------- | |
| # The pipeline object itself does not run any CUDA kernels at | |
| # construction time — it only wraps tokenizer + model + generation | |
| # config. Building it here at module scope is safe. The ACTUAL | |
| # inference call (invoking this pipeline) happens inside the | |
| # @spaces.GPU-decorated function in generation.py — never here. | |
| logger.info("Building text generation pipeline…") | |
| text_gen_pipeline = pipeline( | |
| task="text-generation", | |
| model=model, | |
| tokenizer=tokenizer, | |
| max_new_tokens=MAX_NEW_TOKENS, | |
| max_length=None, # Override model's default max_length (often 20) to silence the HF conflict warning | |
| do_sample=False, # Greedy decoding: deterministic, faithful to context | |
| return_full_text=False, # Return only newly generated tokens, not the prompt | |
| ) | |
| # --- Embedding model --------------------------------------------- | |
| # MiniLM stays on CPU — it's fast enough there and doesn't need the | |
| # GPU slot, which should be reserved for the LLM's generation calls. | |
| logger.info("Loading embedding model…") | |
| embedding_model = HuggingFaceEmbeddings( | |
| model_name=EMBEDDING_MODEL_ID, | |
| model_kwargs={"device": "cpu"}, | |
| encode_kwargs={"normalize_embeddings": True}, | |
| ) | |
| logger.info("All models ready.") | |