vectorplasticity's picture
Update via HuggingFace Tool
0a98915 verified
Raw
History Blame Contribute Delete
15.9 kB
"""
Models API Router - Explore and select models from HuggingFace Hub
"""
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel, Field
from typing import Optional, Dict, List, Any
from datetime import datetime
import logging
import httpx
from app.config import settings
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/models", tags=["Models"])
class ModelInfo(BaseModel):
"""Model information."""
id: str
author: str
model_name: str
pipeline_tag: Optional[str]
library_name: Optional[str]
downloads: int = 0
likes: int = 0
private: bool = False
tags: List[str] = []
created_at: Optional[datetime]
last_modified: Optional[datetime]
card_data: Optional[Dict[str, Any]]
class ModelSearchResult(BaseModel):
"""Search result model."""
id: str
author: str
model_name: str
pipeline_tag: Optional[str]
downloads: int
likes: int
class ModelRecommendation(BaseModel):
"""Model recommendation for a task."""
model_id: str
reason: str
estimated_vram: str
suggested_batch_size: int
supports_peft: bool
class ModelCompatibility(BaseModel):
"""Model compatibility check result."""
model_id: str
compatible: bool
issues: List[str]
recommendations: List[str]
max_sequence_length: int
estimated_training_time: str
requires_gpu: bool
HF_API_URL = "https://huggingface.co/api"
@router.get("/search", response_model=List[ModelSearchResult])
async def search_models(
query: str = Query(..., min_length=1, description="Search query"),
task: Optional[str] = Query(None, description="Filter by task/pipeline tag"),
library: Optional[str] = Query(None, description="Filter by library"),
author: Optional[str] = Query(None, description="Filter by author"),
limit: int = Query(20, ge=1, le=100)
):
"""Search HuggingFace Hub for models."""
async with httpx.AsyncClient() as client:
params = {
"search": query,
"limit": limit,
"full": "false"
}
if task:
params["pipeline_tag"] = task
if library:
params["library_name"] = library
if author:
params["author"] = author
try:
response = await client.get(
f"{HF_API_URL}/models",
params=params,
timeout=30.0
)
response.raise_for_status()
data = response.json()
return [
ModelSearchResult(
id=m.get("id", ""),
author=m.get("author", ""),
model_name=m.get("id", "").split("/")[-1] if "/" in m.get("id", "") else m.get("id", ""),
pipeline_tag=m.get("pipeline_tag"),
downloads=m.get("downloads", 0),
likes=m.get("likes", 0)
)
for m in data
]
except Exception as e:
logger.error(f"Search failed: {e}")
raise HTTPException(status_code=500, detail=f"Search failed: {str(e)}")
@router.get("/info/{model_id:path}", response_model=ModelInfo)
async def get_model_info(model_id: str):
"""Get detailed information about a model."""
async with httpx.AsyncClient() as client:
try:
response = await client.get(
f"{HF_API_URL}/models/{model_id}",
timeout=30.0
)
response.raise_for_status()
data = response.json()
return ModelInfo(
id=data.get("id", ""),
author=data.get("author", ""),
model_name=data.get("id", "").split("/")[-1] if "/" in data.get("id", "") else data.get("id", ""),
pipeline_tag=data.get("pipeline_tag"),
library_name=data.get("library_name"),
downloads=data.get("downloads", 0),
likes=data.get("likes", 0),
private=data.get("private", False),
tags=data.get("tags", []),
created_at=datetime.fromisoformat(data["createdAt"].replace("Z", "+00:00")) if data.get("createdAt") else None,
last_modified=datetime.fromisoformat(data["lastModified"].replace("Z", "+00:00")) if data.get("lastModified") else None,
card_data=data.get("cardData")
)
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
raise HTTPException(status_code=404, detail="Model not found")
raise HTTPException(status_code=e.response.status_code, detail=f"API error: {e}")
except Exception as e:
logger.error(f"Failed to get model info: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/recommend/{task_type}", response_model=List[ModelRecommendation])
async def get_model_recommendations(task_type: str):
"""Get model recommendations for a specific task."""
recommendations = {
"causal-lm": [
ModelRecommendation(model_id="microsoft/phi-2", reason="Compact but powerful, excellent for fine-tuning", estimated_vram="8GB", suggested_batch_size=4, supports_peft=True),
ModelRecommendation(model_id="google/gemma-2b", reason="Efficient 2B model, great for instruction tuning", estimated_vram="6GB", suggested_batch_size=8, supports_peft=True),
ModelRecommendation(model_id="mistralai/Mistral-7B-v0.1", reason="Best-in-class 7B model", estimated_vram="16GB", suggested_batch_size=2, supports_peft=True),
ModelRecommendation(model_id="meta-llama/Llama-3.2-3B", reason="Latest Llama architecture", estimated_vram="10GB", suggested_batch_size=4, supports_peft=True),
],
"seq2seq": [
ModelRecommendation(model_id="google/flan-t5-base", reason="Versatile instruction-following model", estimated_vram="4GB", suggested_batch_size=16, supports_peft=True),
ModelRecommendation(model_id="google/flan-t5-large", reason="Larger variant for better quality", estimated_vram="8GB", suggested_batch_size=8, supports_peft=True),
ModelRecommendation(model_id="facebook/bart-large", reason="Strong summarization model", estimated_vram="6GB", suggested_batch_size=8, supports_peft=True),
],
"summarization": [
ModelRecommendation(model_id="facebook/bart-large-cnn", reason="Pre-trained for news summarization", estimated_vram="6GB", suggested_batch_size=8, supports_peft=True),
ModelRecommendation(model_id="google/flan-t5-base", reason="Multi-purpose, handles various summarization tasks", estimated_vram="4GB", suggested_batch_size=16, supports_peft=True),
ModelRecommendation(model_id="mistralai/Mistral-7B-v0.1", reason="Large language model for advanced summarization", estimated_vram="16GB", suggested_batch_size=2, supports_peft=True),
],
"token-classification": [
ModelRecommendation(model_id="dslim/bert-base-NER", reason="Strong NER model, 18 entity types", estimated_vram="2GB", suggested_batch_size=32, supports_peft=True),
ModelRecommendation(model_id="dbmdz/bert-large-cased-finetuned-conll03-english", reason="Large NER model for high accuracy", estimated_vram="4GB", suggested_batch_size=16, supports_peft=True),
ModelRecommendation(model_id="distilbert-base-cased", reason="Fast and efficient for fine-tuning", estimated_vram="1GB", suggested_batch_size=64, supports_peft=True),
],
"sequence-classification": [
ModelRecommendation(model_id="distilbert-base-uncased-finetuned-sst-2-english", reason="Sentiment analysis baseline", estimated_vram="1GB", suggested_batch_size=64, supports_peft=True),
ModelRecommendation(model_id="roberta-base", reason="Strong general-purpose classification model", estimated_vram="2GB", suggested_batch_size=32, supports_peft=True),
ModelRecommendation(model_id="microsoft/deberta-v3-base", reason="State-of-the-art classification accuracy", estimated_vram="4GB", suggested_batch_size=16, supports_peft=True),
],
"question-answering": [
ModelRecommendation(model_id="deepset/roberta-base-squad2", reason="SQuAD2.0 trained, handles unanswerable", estimated_vram="2GB", suggested_batch_size=32, supports_peft=True),
ModelRecommendation(model_id="distilbert-base-cased-distilled-squad", reason="Fast QA model", estimated_vram="1GB", suggested_batch_size=64, supports_peft=True),
ModelRecommendation(model_id="bert-large-uncased-whole-word-masking-finetuned-squad", reason="High accuracy QA", estimated_vram="4GB", suggested_batch_size=16, supports_peft=True),
],
"translation": [
ModelRecommendation(model_id="Helsinki-NLP/opus-mt-en-de", reason="English to German", estimated_vram="2GB", suggested_batch_size=32, supports_peft=True),
ModelRecommendation(model_id="Helsinki-NLP/opus-mt-en-fr", reason="English to French", estimated_vram="2GB", suggested_batch_size=32, supports_peft=True),
ModelRecommendation(model_id="facebook/mbart-large-50-many-to-many-mmt", reason="Multilingual translation", estimated_vram="8GB", suggested_batch_size=8, supports_peft=True),
],
"masked-lm": [
ModelRecommendation(model_id="bert-base-uncased", reason="Classic MLM model", estimated_vram="2GB", suggested_batch_size=32, supports_peft=True),
ModelRecommendation(model_id="roberta-base", reason="Modern MLM architecture", estimated_vram="2GB", suggested_batch_size=32, supports_peft=True),
ModelRecommendation(model_id="distilbert-base-uncased", reason="Fast MLM training", estimated_vram="1GB", suggested_batch_size=64, supports_peft=True),
],
}
result = recommendations.get(task_type, [])
if not result:
result = recommendations.get("causal-lm", [])
return result[:5]
@router.get("/check/{model_id:path}", response_model=ModelCompatibility)
async def check_model_compatibility(
model_id: str,
task: str = Query("causal-lm", description="Target task")
):
"""Check if a model is compatible for training with current setup."""
async with httpx.AsyncClient() as client:
try:
response = await client.get(
f"{HF_API_URL}/models/{model_id}",
timeout=30.0
)
response.raise_for_status()
data = response.json()
issues = []
recommendations = []
compatible = True
library = data.get("library_name", "transformers")
if library not in ["transformers", "peft", "adapter-transformers"]:
issues.append(f"Unsupported library: {library}")
compatible = False
tags = data.get("tags", [])
if "gated" in tags:
recommendations.append("This is a gated model. Ensure you have access and set HF_TOKEN.")
if data.get("private", False):
recommendations.append("Private model requires authentication.")
pipeline_tag = data.get("pipeline_tag", "")
if "bert" in model_id.lower():
max_seq_len = 512
elif "llama" in model_id.lower():
max_seq_len = 4096
elif "mistral" in model_id.lower():
max_seq_len = 32768
elif "t5" in model_id.lower():
max_seq_len = 512
else:
max_seq_len = 2048
task_pipeline_map = {
"causal-lm": "text-generation",
"seq2seq": "text2text-generation",
"summarization": "summarization",
"translation": "translation",
"token-classification": "token-classification",
"sequence-classification": "text-classification",
"question-answering": "question-answering",
"masked-lm": "fill-mask"
}
expected_pipeline = task_pipeline_map.get(task)
if expected_pipeline and pipeline_tag and pipeline_tag != expected_pipeline:
recommendations.append(f"Model pipeline tag ({pipeline_tag}) may not match task ({task}).")
requires_gpu = False
model_name_lower = model_id.lower()
if any(x in model_name_lower for x in ["7b", "13b", "30b", "70b"]):
issues.append("Large models (7B+) require GPU for practical training")
requires_gpu = True
compatible = False
if "7b" in model_id.lower():
training_time = "Hours per epoch on GPU"
elif "2b" in model_id.lower() or "3b" in model_id.lower():
training_time = "30-60 min per epoch on GPU"
else:
training_time = "10-30 min per epoch on GPU"
return ModelCompatibility(
model_id=model_id,
compatible=compatible,
issues=issues,
recommendations=recommendations,
max_sequence_length=max_seq_len,
estimated_training_time=training_time,
requires_gpu=requires_gpu
)
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
raise HTTPException(status_code=404, detail="Model not found")
raise HTTPException(status_code=e.response.status_code, detail=f"API error")
except Exception as e:
logger.error(f"Compatibility check failed: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/popular")
async def get_popular_models(
task: Optional[str] = None,
limit: int = Query(10, ge=1, le=50)
):
"""Get most popular models, optionally filtered by task."""
async with httpx.AsyncClient() as client:
params = {
"sort": "downloads",
"direction": "-1",
"limit": limit
}
if task:
params["pipeline_tag"] = task
try:
response = await client.get(
f"{HF_API_URL}/models",
params=params,
timeout=30.0
)
response.raise_for_status()
return response.json()
except Exception as e:
logger.error(f"Failed to get popular models: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/tasks")
async def get_supported_tasks():
"""Get list of supported training tasks."""
return {
"tasks": [
{"id": "causal-lm", "name": "Causal Language Modeling", "description": "Text generation, instruction tuning, chat models"},
{"id": "seq2seq", "name": "Sequence-to-Sequence", "description": "Input-output transformations"},
{"id": "summarization", "name": "Summarization", "description": "Text summarization models"},
{"id": "translation", "name": "Translation", "description": "Language translation models"},
{"id": "token-classification", "name": "Token Classification", "description": "NER, POS tagging, entity extraction"},
{"id": "sequence-classification", "name": "Sequence Classification", "description": "Sentiment, topic, intent classification"},
{"id": "question-answering", "name": "Question Answering", "description": "Extractive QA models"},
{"id": "masked-lm", "name": "Masked Language Modeling", "description": "BERT-style pretraining"},
]
}