Add comprehensive dataset column mapping and prompt template controls
Browse files- app/routers/training.py +659 -19
app/routers/training.py
CHANGED
|
@@ -1,14 +1,16 @@
|
|
| 1 |
"""
|
| 2 |
Training API Router - Endpoints for model training
|
|
|
|
| 3 |
"""
|
| 4 |
|
| 5 |
from fastapi import APIRouter, HTTPException, BackgroundTasks, Depends
|
| 6 |
from fastapi.responses import JSONResponse
|
| 7 |
from pydantic import BaseModel, Field, validator
|
| 8 |
-
from typing import Optional, Dict, List, Any
|
| 9 |
from datetime import datetime
|
| 10 |
import uuid
|
| 11 |
import logging
|
|
|
|
| 12 |
|
| 13 |
from app.services.queue_service import TrainingQueue, JobPriority
|
| 14 |
from app.database import get_db, TrainingJob, JobStatus, TaskType
|
|
@@ -20,23 +22,427 @@ logger = logging.getLogger(__name__)
|
|
| 20 |
router = APIRouter(prefix="/api/training", tags=["Training"])
|
| 21 |
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
class DatasetConfig(BaseModel):
|
| 24 |
-
"""
|
| 25 |
-
|
|
|
|
| 26 |
name: Optional[str] = Field(None, description="HuggingFace dataset name")
|
| 27 |
-
config: Optional[str] = Field(None, description="Dataset config/subset")
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
max_length: int = Field(default=512, description="Max sequence length")
|
| 34 |
max_target_length: int = Field(default=128, description="Max target length for seq2seq")
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
path: Optional[str] = Field(None, description="Local file path")
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
class TrainingArgsConfig(BaseModel):
|
| 41 |
"""Training arguments configuration."""
|
| 42 |
epochs: int = Field(default=3, ge=1, le=100)
|
|
@@ -56,18 +462,44 @@ class TrainingArgsConfig(BaseModel):
|
|
| 56 |
bf16: bool = Field(default=False)
|
| 57 |
gradient_checkpointing: bool = Field(default=False)
|
| 58 |
optimizer: str = Field(default="adamw_torch")
|
|
|
|
| 59 |
report_to: str = Field(default="none")
|
| 60 |
seed: int = Field(default=42)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
|
| 63 |
class PEFTConfig(BaseModel):
|
| 64 |
"""PEFT/LoRA configuration."""
|
| 65 |
enabled: bool = Field(default=True)
|
|
|
|
| 66 |
r: int = Field(default=16, ge=1, le=256, description="LoRA rank")
|
| 67 |
alpha: int = Field(default=32, ge=1, description="LoRA alpha")
|
| 68 |
dropout: float = Field(default=0.05, ge=0.0, le=0.5)
|
| 69 |
target_modules: List[str] = Field(default=["q_proj", "v_proj"])
|
| 70 |
bias: str = Field(default="none")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
|
| 73 |
class OutputConfig(BaseModel):
|
|
@@ -75,10 +507,12 @@ class OutputConfig(BaseModel):
|
|
| 75 |
push_to_hub: bool = Field(default=False)
|
| 76 |
hub_model_id: Optional[str] = Field(None)
|
| 77 |
private: bool = Field(default=False)
|
|
|
|
|
|
|
| 78 |
|
| 79 |
|
| 80 |
class TrainingRequest(BaseModel):
|
| 81 |
-
"""Full training request."""
|
| 82 |
name: str = Field(..., min_length=1, max_length=255)
|
| 83 |
description: Optional[str] = Field(None)
|
| 84 |
task_type: str = Field(default="causal-lm")
|
|
@@ -96,7 +530,8 @@ class TrainingRequest(BaseModel):
|
|
| 96 |
"causal-lm", "seq2seq", "token-classification",
|
| 97 |
"sequence-classification", "question-answering",
|
| 98 |
"summarization", "translation", "text-classification",
|
| 99 |
-
"masked-lm", "vision-classification", "audio-classification"
|
|
|
|
| 100 |
]
|
| 101 |
if v not in valid_types:
|
| 102 |
raise ValueError(f"Invalid task_type. Must be one of: {valid_types}")
|
|
@@ -132,6 +567,18 @@ class JobStatusResponse(BaseModel):
|
|
| 132 |
hub_model_id: Optional[str]
|
| 133 |
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
# Global queue instance
|
| 136 |
training_queue = None
|
| 137 |
|
|
@@ -144,6 +591,203 @@ def get_queue():
|
|
| 144 |
return training_queue
|
| 145 |
|
| 146 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
@router.post("/start", response_model=TrainingJobResponse)
|
| 148 |
async def start_training(
|
| 149 |
request: TrainingRequest,
|
|
@@ -164,7 +808,7 @@ async def start_training(
|
|
| 164 |
dataset_source=request.dataset.source,
|
| 165 |
dataset_name=request.dataset.name,
|
| 166 |
dataset_config=request.dataset.config,
|
| 167 |
-
dataset_split=request.dataset.
|
| 168 |
training_args=request.training_args.dict(),
|
| 169 |
peft_config=request.peft_config.dict() if request.peft_config else None,
|
| 170 |
status=JobStatus.PENDING.value,
|
|
@@ -175,7 +819,7 @@ async def start_training(
|
|
| 175 |
db.add(training_job)
|
| 176 |
await db.commit()
|
| 177 |
|
| 178 |
-
# Build config
|
| 179 |
config = {
|
| 180 |
"job_id": job_id,
|
| 181 |
"task_type": request.task_type,
|
|
@@ -210,11 +854,9 @@ async def get_job_status(
|
|
| 210 |
db: AsyncSession = Depends(get_db)
|
| 211 |
):
|
| 212 |
"""Get status of a training job."""
|
| 213 |
-
# Check queue first
|
| 214 |
queue = get_queue()
|
| 215 |
queue_status = await queue.get_status(job_id)
|
| 216 |
|
| 217 |
-
# Get from database
|
| 218 |
result = await db.execute(
|
| 219 |
select(TrainingJob).where(TrainingJob.job_id == job_id)
|
| 220 |
)
|
|
@@ -223,7 +865,6 @@ async def get_job_status(
|
|
| 223 |
if not job:
|
| 224 |
raise HTTPException(status_code=404, detail="Job not found")
|
| 225 |
|
| 226 |
-
# Merge queue status with database status
|
| 227 |
if queue_status:
|
| 228 |
job.status = queue_status.get("status", job.status)
|
| 229 |
if queue_status.get("progress"):
|
|
@@ -304,7 +945,6 @@ async def cancel_job(
|
|
| 304 |
if not cancelled:
|
| 305 |
raise HTTPException(status_code=400, detail="Cannot cancel job")
|
| 306 |
|
| 307 |
-
# Update database
|
| 308 |
result = await db.execute(
|
| 309 |
select(TrainingJob).where(TrainingJob.job_id == job_id)
|
| 310 |
)
|
|
|
|
| 1 |
"""
|
| 2 |
Training API Router - Endpoints for model training
|
| 3 |
+
Enhanced with dataset column mapping and prompt structure controls
|
| 4 |
"""
|
| 5 |
|
| 6 |
from fastapi import APIRouter, HTTPException, BackgroundTasks, Depends
|
| 7 |
from fastapi.responses import JSONResponse
|
| 8 |
from pydantic import BaseModel, Field, validator
|
| 9 |
+
from typing import Optional, Dict, List, Any, Union
|
| 10 |
from datetime import datetime
|
| 11 |
import uuid
|
| 12 |
import logging
|
| 13 |
+
import re
|
| 14 |
|
| 15 |
from app.services.queue_service import TrainingQueue, JobPriority
|
| 16 |
from app.database import get_db, TrainingJob, JobStatus, TaskType
|
|
|
|
| 22 |
router = APIRouter(prefix="/api/training", tags=["Training"])
|
| 23 |
|
| 24 |
|
| 25 |
+
# ============================================
|
| 26 |
+
# PROMPT TEMPLATE CONFIGURATION
|
| 27 |
+
# ============================================
|
| 28 |
+
|
| 29 |
+
class PromptSectionConfig(BaseModel):
|
| 30 |
+
"""Configuration for a single prompt section (system, user, assistant, etc.)"""
|
| 31 |
+
enabled: bool = Field(default=True, description="Whether to include this section")
|
| 32 |
+
template: str = Field(
|
| 33 |
+
default="",
|
| 34 |
+
description="Template string with {column} placeholders"
|
| 35 |
+
)
|
| 36 |
+
columns: List[str] = Field(
|
| 37 |
+
default_factory=list,
|
| 38 |
+
description="List of dataset columns used in this section"
|
| 39 |
+
)
|
| 40 |
+
prefix: str = Field(default="", description="Prefix before content (e.g., 'System: ')")
|
| 41 |
+
suffix: str = Field(default="", description="Suffix after content (e.g., '\n\n')")
|
| 42 |
+
strip_whitespace: bool = Field(default=True)
|
| 43 |
+
required: bool = Field(default=False, description="Raise error if columns missing")
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class PromptTemplateConfig(BaseModel):
|
| 47 |
+
"""Full prompt template configuration for training data formatting"""
|
| 48 |
+
# Chat-style formatting
|
| 49 |
+
use_chat_format: bool = Field(default=True, description="Use chat-style message format")
|
| 50 |
+
chat_template: Optional[str] = Field(
|
| 51 |
+
default=None,
|
| 52 |
+
description="Jinja2 chat template (auto-detect from tokenizer if None)"
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
# Message sections
|
| 56 |
+
system: Optional[PromptSectionConfig] = Field(
|
| 57 |
+
default=None,
|
| 58 |
+
description="System message configuration"
|
| 59 |
+
)
|
| 60 |
+
user: Optional[PromptSectionConfig] = Field(
|
| 61 |
+
default=None,
|
| 62 |
+
description="User message configuration"
|
| 63 |
+
)
|
| 64 |
+
context: Optional[PromptSectionConfig] = Field(
|
| 65 |
+
default=None,
|
| 66 |
+
description="Context/passages configuration"
|
| 67 |
+
)
|
| 68 |
+
reasoning: Optional[PromptSectionConfig] = Field(
|
| 69 |
+
default=None,
|
| 70 |
+
description="Reasoning/chain-of-thought configuration"
|
| 71 |
+
)
|
| 72 |
+
assistant: Optional[PromptSectionConfig] = Field(
|
| 73 |
+
default=None,
|
| 74 |
+
description="Assistant response configuration (target for training)"
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
# Custom sections for flexibility
|
| 78 |
+
custom_sections: Dict[str, PromptSectionConfig] = Field(
|
| 79 |
+
default_factory=dict,
|
| 80 |
+
description="Additional custom sections"
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
# Section ordering
|
| 84 |
+
section_order: List[str] = Field(
|
| 85 |
+
default=["system", "context", "user", "reasoning", "assistant"],
|
| 86 |
+
description="Order of sections in the prompt"
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
# Special tokens
|
| 90 |
+
bos_token: Optional[str] = Field(default=None, description="Beginning of sequence token")
|
| 91 |
+
eos_token: Optional[str] = Field(default=None, description="End of sequence token")
|
| 92 |
+
pad_token: Optional[str] = Field(default=None, description="Padding token")
|
| 93 |
+
|
| 94 |
+
# Separator configuration
|
| 95 |
+
section_separator: str = Field(default="\n\n", description="Separator between sections")
|
| 96 |
+
message_separator: str = Field(default="\n", description="Separator between messages")
|
| 97 |
+
|
| 98 |
+
# Instruction format (for instruction-tuned models)
|
| 99 |
+
instruction_format: str = Field(
|
| 100 |
+
default="none",
|
| 101 |
+
description="Preset format: none, alpaca, chatml, llama3, mistral, vicuna, phi3"
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
def get_template_for_format(self, format_name: str) -> Dict[str, Any]:
|
| 105 |
+
"""Get preset template configuration for known formats"""
|
| 106 |
+
presets = {
|
| 107 |
+
"none": {},
|
| 108 |
+
"alpaca": {
|
| 109 |
+
"system": PromptSectionConfig(
|
| 110 |
+
enabled=True,
|
| 111 |
+
template="Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.",
|
| 112 |
+
prefix="", suffix="\n\n"
|
| 113 |
+
),
|
| 114 |
+
"user": PromptSectionConfig(
|
| 115 |
+
enabled=True,
|
| 116 |
+
template="### Instruction:\n{instruction}\n\n### Input:\n{input}",
|
| 117 |
+
columns=["instruction", "input"],
|
| 118 |
+
prefix="", suffix="\n\n"
|
| 119 |
+
),
|
| 120 |
+
"assistant": PromptSectionConfig(
|
| 121 |
+
enabled=True,
|
| 122 |
+
template="### Response:\n{output}",
|
| 123 |
+
columns=["output"],
|
| 124 |
+
prefix="", suffix=""
|
| 125 |
+
),
|
| 126 |
+
"section_order": ["system", "user", "assistant"]
|
| 127 |
+
},
|
| 128 |
+
"chatml": {
|
| 129 |
+
"system": PromptSectionConfig(
|
| 130 |
+
enabled=True,
|
| 131 |
+
template="{system_message}",
|
| 132 |
+
columns=["system_message"],
|
| 133 |
+
prefix="<|im_start|>system\n", suffix="<|im_end|>\n"
|
| 134 |
+
),
|
| 135 |
+
"user": PromptSectionConfig(
|
| 136 |
+
enabled=True,
|
| 137 |
+
template="{user_message}",
|
| 138 |
+
columns=["user_message"],
|
| 139 |
+
prefix="<|im_start|>user\n", suffix="<|im_end|>\n"
|
| 140 |
+
),
|
| 141 |
+
"assistant": PromptSectionConfig(
|
| 142 |
+
enabled=True,
|
| 143 |
+
template="{assistant_message}",
|
| 144 |
+
columns=["assistant_message", "output", "response"],
|
| 145 |
+
prefix="<|im_start|>assistant\n", suffix="<|im_end|>"
|
| 146 |
+
),
|
| 147 |
+
"section_order": ["system", "user", "assistant"]
|
| 148 |
+
},
|
| 149 |
+
"llama3": {
|
| 150 |
+
"system": PromptSectionConfig(
|
| 151 |
+
enabled=True,
|
| 152 |
+
template="{system_message}",
|
| 153 |
+
columns=["system_message"],
|
| 154 |
+
prefix="<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\n",
|
| 155 |
+
suffix="<|eot_id|>"
|
| 156 |
+
),
|
| 157 |
+
"user": PromptSectionConfig(
|
| 158 |
+
enabled=True,
|
| 159 |
+
template="{user_message}",
|
| 160 |
+
columns=["user_message", "question"],
|
| 161 |
+
prefix="<|start_header_id|>user<|end_header_id|>\n\n",
|
| 162 |
+
suffix="<|eot_id|>"
|
| 163 |
+
),
|
| 164 |
+
"assistant": PromptSectionConfig(
|
| 165 |
+
enabled=True,
|
| 166 |
+
template="{assistant_message}",
|
| 167 |
+
columns=["assistant_message", "output", "response"],
|
| 168 |
+
prefix="<|start_header_id|>assistant<|end_header_id|>\n\n",
|
| 169 |
+
suffix="<|eot_id|>"
|
| 170 |
+
),
|
| 171 |
+
"section_order": ["system", "user", "assistant"]
|
| 172 |
+
},
|
| 173 |
+
"mistral": {
|
| 174 |
+
"system": PromptSectionConfig(
|
| 175 |
+
enabled=True,
|
| 176 |
+
template="{system_message}",
|
| 177 |
+
columns=["system_message"],
|
| 178 |
+
prefix="[INST] ", suffix=" "
|
| 179 |
+
),
|
| 180 |
+
"user": PromptSectionConfig(
|
| 181 |
+
enabled=True,
|
| 182 |
+
template="{user_message}",
|
| 183 |
+
columns=["user_message", "question"],
|
| 184 |
+
prefix="", suffix=" [/INST]"
|
| 185 |
+
),
|
| 186 |
+
"assistant": PromptSectionConfig(
|
| 187 |
+
enabled=True,
|
| 188 |
+
template="{assistant_message}",
|
| 189 |
+
columns=["assistant_message", "output"],
|
| 190 |
+
prefix="", suffix="</s>"
|
| 191 |
+
),
|
| 192 |
+
"section_order": ["system", "user", "assistant"]
|
| 193 |
+
},
|
| 194 |
+
"vicuna": {
|
| 195 |
+
"system": PromptSectionConfig(
|
| 196 |
+
enabled=True,
|
| 197 |
+
template="A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.",
|
| 198 |
+
prefix="", suffix=" "
|
| 199 |
+
),
|
| 200 |
+
"user": PromptSectionConfig(
|
| 201 |
+
enabled=True,
|
| 202 |
+
template="{user_message}",
|
| 203 |
+
columns=["user_message", "question"],
|
| 204 |
+
prefix="USER: ", suffix=" "
|
| 205 |
+
),
|
| 206 |
+
"assistant": PromptSectionConfig(
|
| 207 |
+
enabled=True,
|
| 208 |
+
template="{assistant_message}",
|
| 209 |
+
columns=["assistant_message", "output"],
|
| 210 |
+
prefix="ASSISTANT: ", suffix=""
|
| 211 |
+
),
|
| 212 |
+
"section_order": ["system", "user", "assistant"]
|
| 213 |
+
},
|
| 214 |
+
"phi3": {
|
| 215 |
+
"system": PromptSectionConfig(
|
| 216 |
+
enabled=True,
|
| 217 |
+
template="{system_message}",
|
| 218 |
+
columns=["system_message"],
|
| 219 |
+
prefix="<|system|>\n", suffix="<|end|>\n"
|
| 220 |
+
),
|
| 221 |
+
"user": PromptSectionConfig(
|
| 222 |
+
enabled=True,
|
| 223 |
+
template="{user_message}",
|
| 224 |
+
columns=["user_message", "question"],
|
| 225 |
+
prefix="<|user|>\n", suffix="<|end|>\n"
|
| 226 |
+
),
|
| 227 |
+
"assistant": PromptSectionConfig(
|
| 228 |
+
enabled=True,
|
| 229 |
+
template="{assistant_message}",
|
| 230 |
+
columns=["assistant_message", "output"],
|
| 231 |
+
prefix="<|assistant|>\n", suffix="<|end|>"
|
| 232 |
+
),
|
| 233 |
+
"section_order": ["system", "user", "assistant"]
|
| 234 |
+
},
|
| 235 |
+
"reasoning": {
|
| 236 |
+
"system": PromptSectionConfig(
|
| 237 |
+
enabled=True,
|
| 238 |
+
template="You are a helpful AI assistant that thinks step by step before responding.",
|
| 239 |
+
prefix="", suffix="\n\n"
|
| 240 |
+
),
|
| 241 |
+
"context": PromptSectionConfig(
|
| 242 |
+
enabled=True,
|
| 243 |
+
template="{context}",
|
| 244 |
+
columns=["context", "passages", "background"],
|
| 245 |
+
prefix="Context:\n", suffix="\n\n"
|
| 246 |
+
),
|
| 247 |
+
"user": PromptSectionConfig(
|
| 248 |
+
enabled=True,
|
| 249 |
+
template="{question}",
|
| 250 |
+
columns=["question", "query", "user_message"],
|
| 251 |
+
prefix="Question: ", suffix="\n\n"
|
| 252 |
+
),
|
| 253 |
+
"reasoning": PromptSectionConfig(
|
| 254 |
+
enabled=True,
|
| 255 |
+
template="{reasoning}",
|
| 256 |
+
columns=["reasoning", "thinking", "chain_of_thought"],
|
| 257 |
+
prefix="Reasoning:\n", suffix="\n\n"
|
| 258 |
+
),
|
| 259 |
+
"assistant": PromptSectionConfig(
|
| 260 |
+
enabled=True,
|
| 261 |
+
template="{answer}",
|
| 262 |
+
columns=["answer", "output", "response"],
|
| 263 |
+
prefix="Answer: ", suffix=""
|
| 264 |
+
),
|
| 265 |
+
"section_order": ["system", "context", "user", "reasoning", "assistant"]
|
| 266 |
+
}
|
| 267 |
+
}
|
| 268 |
+
return presets.get(format_name, {})
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
# ============================================
|
| 272 |
+
# COLUMN MAPPING CONFIGURATION
|
| 273 |
+
# ============================================
|
| 274 |
+
|
| 275 |
+
class ColumnMappingConfig(BaseModel):
|
| 276 |
+
"""Maps dataset columns to training roles"""
|
| 277 |
+
# Primary text columns
|
| 278 |
+
text_column: Optional[str] = Field(None, description="Main text column (for causal LM)")
|
| 279 |
+
input_column: Optional[str] = Field(None, description="Input text column")
|
| 280 |
+
output_column: Optional[str] = Field(None, description="Output/target column")
|
| 281 |
+
|
| 282 |
+
# Multi-column support
|
| 283 |
+
instruction_column: Optional[str] = Field(None, description="Instruction column")
|
| 284 |
+
question_column: Optional[str] = Field(None, description="Question column")
|
| 285 |
+
answer_column: Optional[str] = Field(None, description="Answer column")
|
| 286 |
+
context_column: Optional[str] = Field(None, description="Context/passages column")
|
| 287 |
+
reasoning_column: Optional[str] = Field(None, description="Reasoning/CoT column")
|
| 288 |
+
|
| 289 |
+
# Classification specific
|
| 290 |
+
label_column: Optional[str] = Field(None, description="Label column for classification")
|
| 291 |
+
label_mapping: Optional[Dict[str, int]] = Field(None, description="Label to ID mapping")
|
| 292 |
+
|
| 293 |
+
# NER/Token classification
|
| 294 |
+
tokens_column: Optional[str] = Field(None, description="Tokens column (for NER)")
|
| 295 |
+
tags_column: Optional[str] = Field(None, description="NER tags column")
|
| 296 |
+
ner_tags_mapping: Optional[Dict[str, str]] = Field(None, description="NER tag to label mapping")
|
| 297 |
+
|
| 298 |
+
# QA specific
|
| 299 |
+
title_column: Optional[str] = Field(None, description="Title column for QA context")
|
| 300 |
+
id_column: Optional[str] = Field(None, description="ID column")
|
| 301 |
+
answers_column: Optional[str] = Field(None, description="Answers column (list of answers)")
|
| 302 |
+
start_position_column: Optional[str] = Field(None, description="Start position column")
|
| 303 |
+
end_position_column: Optional[str] = Field(None, description="End position column")
|
| 304 |
+
|
| 305 |
+
# Additional context
|
| 306 |
+
metadata_columns: List[str] = Field(
|
| 307 |
+
default_factory=list,
|
| 308 |
+
description="Additional columns to include as metadata"
|
| 309 |
+
)
|
| 310 |
+
|
| 311 |
+
# Column transformations
|
| 312 |
+
column_transforms: Dict[str, str] = Field(
|
| 313 |
+
default_factory=dict,
|
| 314 |
+
description="Transformations to apply: column -> transform_type"
|
| 315 |
+
)
|
| 316 |
+
|
| 317 |
+
# Custom column aliases
|
| 318 |
+
column_aliases: Dict[str, str] = Field(
|
| 319 |
+
default_factory=dict,
|
| 320 |
+
description="Map custom column names to standard names"
|
| 321 |
+
)
|
| 322 |
+
|
| 323 |
+
def get_effective_column(self, role: str) -> Optional[str]:
|
| 324 |
+
"""Get the effective column name for a given role, considering aliases"""
|
| 325 |
+
role_to_column = {
|
| 326 |
+
"text": self.text_column,
|
| 327 |
+
"input": self.input_column,
|
| 328 |
+
"output": self.output_column,
|
| 329 |
+
"instruction": self.instruction_column,
|
| 330 |
+
"question": self.question_column,
|
| 331 |
+
"answer": self.answer_column,
|
| 332 |
+
"context": self.context_column,
|
| 333 |
+
"reasoning": self.reasoning_column,
|
| 334 |
+
"label": self.label_column,
|
| 335 |
+
"tokens": self.tokens_column,
|
| 336 |
+
"tags": self.tags_column,
|
| 337 |
+
"title": self.title_column,
|
| 338 |
+
"id": self.id_column,
|
| 339 |
+
"answers": self.answers_column,
|
| 340 |
+
}
|
| 341 |
+
column = role_to_column.get(role)
|
| 342 |
+
if column and column in self.column_aliases:
|
| 343 |
+
return self.column_aliases[column]
|
| 344 |
+
return column
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
# ============================================
|
| 348 |
+
# DATASET CONFIGURATION (ENHANCED)
|
| 349 |
+
# ============================================
|
| 350 |
+
|
| 351 |
+
class DatasetSplitConfig(BaseModel):
|
| 352 |
+
"""Configuration for a single dataset split"""
|
| 353 |
+
name: str = Field(default="train", description="Split name (train, validation, test)")
|
| 354 |
+
enabled: bool = Field(default=True)
|
| 355 |
+
max_samples: Optional[int] = Field(None, description="Maximum samples to use")
|
| 356 |
+
shuffle: bool = Field(default=True)
|
| 357 |
+
seed: int = Field(default=42)
|
| 358 |
+
stratify: bool = Field(default=False, description="Stratified sampling")
|
| 359 |
+
|
| 360 |
+
|
| 361 |
class DatasetConfig(BaseModel):
|
| 362 |
+
"""Enhanced dataset configuration with full control"""
|
| 363 |
+
# Source configuration
|
| 364 |
+
source: str = Field(default="huggingface", description="Dataset source: huggingface, local, upload")
|
| 365 |
name: Optional[str] = Field(None, description="HuggingFace dataset name")
|
| 366 |
+
config: Optional[str] = Field(None, description="Dataset config/subset name")
|
| 367 |
+
revision: Optional[str] = Field(None, description="Dataset revision/branch")
|
| 368 |
+
|
| 369 |
+
# Split configuration
|
| 370 |
+
splits: List[DatasetSplitConfig] = Field(
|
| 371 |
+
default_factory=lambda: [DatasetSplitConfig(name="train")],
|
| 372 |
+
description="Dataset splits to use"
|
| 373 |
+
)
|
| 374 |
+
train_split: str = Field(default="train", description="Training split name")
|
| 375 |
+
validation_split: Optional[str] = Field(None, description="Validation split name")
|
| 376 |
+
test_split: Optional[str] = Field(None, description="Test split name")
|
| 377 |
+
|
| 378 |
+
# Split generation
|
| 379 |
+
validation_split_ratio: float = Field(
|
| 380 |
+
default=0.1,
|
| 381 |
+
ge=0.0,
|
| 382 |
+
le=0.5,
|
| 383 |
+
description="Ratio for auto-generating validation split"
|
| 384 |
+
)
|
| 385 |
+
generate_validation: bool = Field(
|
| 386 |
+
default=True,
|
| 387 |
+
description="Auto-generate validation split if not provided"
|
| 388 |
+
)
|
| 389 |
+
|
| 390 |
+
# Column mapping
|
| 391 |
+
column_mapping: ColumnMappingConfig = Field(
|
| 392 |
+
default_factory=ColumnMappingConfig,
|
| 393 |
+
description="Map dataset columns to training roles"
|
| 394 |
+
)
|
| 395 |
+
|
| 396 |
+
# Prompt template
|
| 397 |
+
prompt_template: Optional[PromptTemplateConfig] = Field(
|
| 398 |
+
default=None,
|
| 399 |
+
description="Prompt structure configuration"
|
| 400 |
+
)
|
| 401 |
+
|
| 402 |
+
# Data processing
|
| 403 |
max_length: int = Field(default=512, description="Max sequence length")
|
| 404 |
max_target_length: int = Field(default=128, description="Max target length for seq2seq")
|
| 405 |
+
truncate: bool = Field(default=True, description="Truncate sequences exceeding max_length")
|
| 406 |
+
|
| 407 |
+
# Filtering
|
| 408 |
+
filter_conditions: List[Dict[str, Any]] = Field(
|
| 409 |
+
default_factory=list,
|
| 410 |
+
description="Filter conditions: [{column, operator, value}]"
|
| 411 |
+
)
|
| 412 |
+
min_text_length: Optional[int] = Field(None, description="Minimum text length")
|
| 413 |
+
max_text_length: Optional[int] = Field(None, description="Maximum text length")
|
| 414 |
+
|
| 415 |
+
# Augmentation
|
| 416 |
+
augmentation_enabled: bool = Field(default=False)
|
| 417 |
+
augmentation_config: Dict[str, Any] = Field(
|
| 418 |
+
default_factory=dict,
|
| 419 |
+
description="Data augmentation configuration"
|
| 420 |
+
)
|
| 421 |
+
|
| 422 |
+
# Streaming (for large datasets)
|
| 423 |
+
streaming: bool = Field(
|
| 424 |
+
default=False,
|
| 425 |
+
description="Use streaming mode for large datasets"
|
| 426 |
+
)
|
| 427 |
+
streaming_buffer_size: int = Field(default=10000)
|
| 428 |
+
|
| 429 |
+
# Caching
|
| 430 |
+
cache_dir: Optional[str] = Field(None, description="Cache directory")
|
| 431 |
+
num_proc: int = Field(default=4, description="Number of processes for data loading")
|
| 432 |
+
|
| 433 |
+
# Local file support
|
| 434 |
path: Optional[str] = Field(None, description="Local file path")
|
| 435 |
+
file_type: Optional[str] = Field(None, description="File type: json, jsonl, csv, parquet, text")
|
| 436 |
+
|
| 437 |
+
# Data validation
|
| 438 |
+
validate_data: bool = Field(default=True, description="Validate data before training")
|
| 439 |
+
validation_sample_size: int = Field(default=100, description="Samples to validate")
|
| 440 |
|
| 441 |
|
| 442 |
+
# ============================================
|
| 443 |
+
# TRAINING ARGUMENTS CONFIGURATION
|
| 444 |
+
# ============================================
|
| 445 |
+
|
| 446 |
class TrainingArgsConfig(BaseModel):
|
| 447 |
"""Training arguments configuration."""
|
| 448 |
epochs: int = Field(default=3, ge=1, le=100)
|
|
|
|
| 462 |
bf16: bool = Field(default=False)
|
| 463 |
gradient_checkpointing: bool = Field(default=False)
|
| 464 |
optimizer: str = Field(default="adamw_torch")
|
| 465 |
+
lr_scheduler_type: str = Field(default="cosine")
|
| 466 |
report_to: str = Field(default="none")
|
| 467 |
seed: int = Field(default=42)
|
| 468 |
+
|
| 469 |
+
# Advanced options
|
| 470 |
+
eval_strategy: str = Field(default="steps")
|
| 471 |
+
load_best_model_at_end: bool = Field(default=True)
|
| 472 |
+
metric_for_best_model: str = Field(default="eval_loss")
|
| 473 |
+
greater_is_better: bool = Field(default=False)
|
| 474 |
+
|
| 475 |
+
# Memory optimization
|
| 476 |
+
optim: str = Field(default="adamw_torch")
|
| 477 |
+
ddp_find_unused_parameters: bool = Field(default=False)
|
| 478 |
+
|
| 479 |
|
| 480 |
|
| 481 |
class PEFTConfig(BaseModel):
|
| 482 |
"""PEFT/LoRA configuration."""
|
| 483 |
enabled: bool = Field(default=True)
|
| 484 |
+
method: str = Field(default="lora", description="lora, adalora, ia3, prefix_tuning, prompt_tuning")
|
| 485 |
r: int = Field(default=16, ge=1, le=256, description="LoRA rank")
|
| 486 |
alpha: int = Field(default=32, ge=1, description="LoRA alpha")
|
| 487 |
dropout: float = Field(default=0.05, ge=0.0, le=0.5)
|
| 488 |
target_modules: List[str] = Field(default=["q_proj", "v_proj"])
|
| 489 |
bias: str = Field(default="none")
|
| 490 |
+
modules_to_save: List[str] = Field(default_factory=list)
|
| 491 |
+
|
| 492 |
+
# AdaLoRA specific
|
| 493 |
+
init_r: int = Field(default=12)
|
| 494 |
+
t_init: int = Field(default=200)
|
| 495 |
+
t_final: int = Field(default=1000)
|
| 496 |
+
|
| 497 |
+
# Prefix Tuning specific
|
| 498 |
+
num_virtual_tokens: int = Field(default=20)
|
| 499 |
+
|
| 500 |
+
# Prompt Tuning specific
|
| 501 |
+
num_tokens: int = Field(default=20)
|
| 502 |
+
token_init: bool = Field(default=True)
|
| 503 |
|
| 504 |
|
| 505 |
class OutputConfig(BaseModel):
|
|
|
|
| 507 |
push_to_hub: bool = Field(default=False)
|
| 508 |
hub_model_id: Optional[str] = Field(None)
|
| 509 |
private: bool = Field(default=False)
|
| 510 |
+
save_strategy: str = Field(default="steps")
|
| 511 |
+
output_dir: Optional[str] = Field(None)
|
| 512 |
|
| 513 |
|
| 514 |
class TrainingRequest(BaseModel):
|
| 515 |
+
"""Full training request with all configuration options."""
|
| 516 |
name: str = Field(..., min_length=1, max_length=255)
|
| 517 |
description: Optional[str] = Field(None)
|
| 518 |
task_type: str = Field(default="causal-lm")
|
|
|
|
| 530 |
"causal-lm", "seq2seq", "token-classification",
|
| 531 |
"sequence-classification", "question-answering",
|
| 532 |
"summarization", "translation", "text-classification",
|
| 533 |
+
"masked-lm", "vision-classification", "audio-classification",
|
| 534 |
+
"reasoning"
|
| 535 |
]
|
| 536 |
if v not in valid_types:
|
| 537 |
raise ValueError(f"Invalid task_type. Must be one of: {valid_types}")
|
|
|
|
| 567 |
hub_model_id: Optional[str]
|
| 568 |
|
| 569 |
|
| 570 |
+
class DatasetPreviewResponse(BaseModel):
|
| 571 |
+
"""Dataset preview with detected columns and sample data."""
|
| 572 |
+
dataset_name: str
|
| 573 |
+
config: Optional[str]
|
| 574 |
+
splits: List[str]
|
| 575 |
+
columns: List[Dict[str, Any]]
|
| 576 |
+
sample_data: List[Dict[str, Any]]
|
| 577 |
+
total_rows: Optional[int]
|
| 578 |
+
detected_task_types: List[str]
|
| 579 |
+
suggested_column_mapping: ColumnMappingConfig
|
| 580 |
+
|
| 581 |
+
|
| 582 |
# Global queue instance
|
| 583 |
training_queue = None
|
| 584 |
|
|
|
|
| 591 |
return training_queue
|
| 592 |
|
| 593 |
|
| 594 |
+
# ============================================
|
| 595 |
+
# DATASET PREVIEW AND COLUMN DETECTION
|
| 596 |
+
# ============================================
|
| 597 |
+
|
| 598 |
+
@router.get("/dataset/preview/{dataset_name:path}", response_model=DatasetPreviewResponse)
|
| 599 |
+
async def preview_dataset(
|
| 600 |
+
dataset_name: str,
|
| 601 |
+
config: Optional[str] = None,
|
| 602 |
+
split: str = "train",
|
| 603 |
+
rows: int = 10
|
| 604 |
+
):
|
| 605 |
+
"""Preview a dataset with detected columns and suggested mappings."""
|
| 606 |
+
try:
|
| 607 |
+
from datasets import load_dataset
|
| 608 |
+
|
| 609 |
+
# Load dataset
|
| 610 |
+
if config:
|
| 611 |
+
ds = load_dataset(dataset_name, config, split=split, trust_remote_code=True, streaming=False)
|
| 612 |
+
else:
|
| 613 |
+
ds = load_dataset(dataset_name, split=split, trust_remote_code=True, streaming=False)
|
| 614 |
+
|
| 615 |
+
# Get column info
|
| 616 |
+
columns = []
|
| 617 |
+
for col_name, col_type in ds.features.items():
|
| 618 |
+
col_info = {
|
| 619 |
+
"name": col_name,
|
| 620 |
+
"type": str(col_type),
|
| 621 |
+
"dtype": type(col_type).__name__,
|
| 622 |
+
}
|
| 623 |
+
# Detect if it's a label column
|
| 624 |
+
if hasattr(col_type, 'names'):
|
| 625 |
+
col_info["labels"] = col_type.names
|
| 626 |
+
col_info["num_labels"] = len(col_type.names)
|
| 627 |
+
columns.append(col_info)
|
| 628 |
+
|
| 629 |
+
# Get sample data
|
| 630 |
+
sample_data = []
|
| 631 |
+
for i in range(min(rows, len(ds))):
|
| 632 |
+
sample_data.append(ds[i])
|
| 633 |
+
|
| 634 |
+
# Detect task type and suggest column mapping
|
| 635 |
+
detected_tasks, suggested_mapping = detect_task_and_mapping(ds, columns)
|
| 636 |
+
|
| 637 |
+
return DatasetPreviewResponse(
|
| 638 |
+
dataset_name=dataset_name,
|
| 639 |
+
config=config,
|
| 640 |
+
splits=list(ds.info.splits.keys()) if hasattr(ds, 'info') and ds.info.splits else [split],
|
| 641 |
+
columns=columns,
|
| 642 |
+
sample_data=sample_data,
|
| 643 |
+
total_rows=len(ds),
|
| 644 |
+
detected_task_types=detected_tasks,
|
| 645 |
+
suggested_column_mapping=suggested_mapping
|
| 646 |
+
)
|
| 647 |
+
|
| 648 |
+
except Exception as e:
|
| 649 |
+
raise HTTPException(status_code=400, detail=f"Error loading dataset: {str(e)}")
|
| 650 |
+
|
| 651 |
+
|
| 652 |
+
def detect_task_and_mapping(dataset, columns: List[Dict]) -> tuple:
|
| 653 |
+
"""Detect suitable task types and suggest column mappings."""
|
| 654 |
+
col_names = [c["name"].lower() for c in columns]
|
| 655 |
+
col_names_original = [c["name"] for c in columns]
|
| 656 |
+
detected_tasks = []
|
| 657 |
+
mapping = ColumnMappingConfig()
|
| 658 |
+
|
| 659 |
+
# Check for text classification
|
| 660 |
+
label_cols = [c for c in columns if "label" in c["name"].lower() or c.get("labels")]
|
| 661 |
+
text_cols = [c for c in columns if "text" in c["name"].lower()]
|
| 662 |
+
|
| 663 |
+
if label_cols and text_cols:
|
| 664 |
+
detected_tasks.append("text-classification")
|
| 665 |
+
mapping.text_column = text_cols[0]["name"]
|
| 666 |
+
mapping.label_column = label_cols[0]["name"]
|
| 667 |
+
if label_cols[0].get("labels"):
|
| 668 |
+
mapping.label_mapping = {name: i for i, name in enumerate(label_cols[0]["labels"])}
|
| 669 |
+
|
| 670 |
+
# Check for QA
|
| 671 |
+
question_cols = [c for c in columns if "question" in c["name"].lower()]
|
| 672 |
+
answer_cols = [c for c in columns if "answer" in c["name"].lower()]
|
| 673 |
+
context_cols = [c for c in columns if "context" in c["name"].lower()]
|
| 674 |
+
|
| 675 |
+
if question_cols and answer_cols:
|
| 676 |
+
detected_tasks.append("question-answering")
|
| 677 |
+
mapping.question_column = question_cols[0]["name"]
|
| 678 |
+
mapping.answer_column = answer_cols[0]["name"]
|
| 679 |
+
if context_cols:
|
| 680 |
+
mapping.context_column = context_cols[0]["name"]
|
| 681 |
+
|
| 682 |
+
# Check for instruction/output
|
| 683 |
+
instruction_cols = [c for c in columns if "instruction" in c["name"].lower() or "prompt" in c["name"].lower()]
|
| 684 |
+
output_cols = [c for c in columns if "output" in c["name"].lower() or "response" in c["name"].lower()]
|
| 685 |
+
|
| 686 |
+
if instruction_cols and output_cols:
|
| 687 |
+
detected_tasks.append("causal-lm")
|
| 688 |
+
mapping.instruction_column = instruction_cols[0]["name"]
|
| 689 |
+
mapping.output_column = output_cols[0]["name"]
|
| 690 |
+
|
| 691 |
+
# Check for input/target (seq2seq)
|
| 692 |
+
input_cols = [c for c in columns if "input" in c["name"].lower() or "source" in c["name"].lower()]
|
| 693 |
+
target_cols = [c for c in columns if "target" in c["name"].lower() or "summary" in c["name"].lower()]
|
| 694 |
+
|
| 695 |
+
if input_cols and target_cols:
|
| 696 |
+
detected_tasks.append("seq2seq")
|
| 697 |
+
mapping.input_column = input_cols[0]["name"]
|
| 698 |
+
mapping.output_column = target_cols[0]["name"]
|
| 699 |
+
|
| 700 |
+
# Check for NER
|
| 701 |
+
tokens_cols = [c for c in columns if "token" in c["name"].lower() or "word" in c["name"].lower()]
|
| 702 |
+
tags_cols = [c for c in columns if "tag" in c["name"].lower() or "ner" in c["name"].lower()]
|
| 703 |
+
|
| 704 |
+
if tokens_cols and tags_cols:
|
| 705 |
+
detected_tasks.append("token-classification")
|
| 706 |
+
mapping.tokens_column = tokens_cols[0]["name"]
|
| 707 |
+
mapping.tags_column = tags_cols[0]["name"]
|
| 708 |
+
|
| 709 |
+
# Default to causal LM if we have any text
|
| 710 |
+
if not detected_tasks:
|
| 711 |
+
text_like = [c for c in columns if c["dtype"] in ["Value", "LargeString"]]
|
| 712 |
+
if text_like:
|
| 713 |
+
detected_tasks.append("causal-lm")
|
| 714 |
+
mapping.text_column = text_like[0]["name"]
|
| 715 |
+
|
| 716 |
+
return detected_tasks, mapping
|
| 717 |
+
|
| 718 |
+
|
| 719 |
+
# ============================================
|
| 720 |
+
# PROMPT TEMPLATE ENDPOINTS
|
| 721 |
+
# ============================================
|
| 722 |
+
|
| 723 |
+
@router.get("/prompt-templates")
|
| 724 |
+
async def get_prompt_templates():
|
| 725 |
+
"""Get available prompt template presets."""
|
| 726 |
+
return {
|
| 727 |
+
"presets": [
|
| 728 |
+
{
|
| 729 |
+
"id": "none",
|
| 730 |
+
"name": "None (Raw Text)",
|
| 731 |
+
"description": "Use dataset text directly without formatting"
|
| 732 |
+
},
|
| 733 |
+
{
|
| 734 |
+
"id": "alpaca",
|
| 735 |
+
"name": "Alpaca Format",
|
| 736 |
+
"description": "Instruction-Input-Output format for instruction tuning"
|
| 737 |
+
},
|
| 738 |
+
{
|
| 739 |
+
"id": "chatml",
|
| 740 |
+
"name": "ChatML",
|
| 741 |
+
"description": "ChatML format used by various models"
|
| 742 |
+
},
|
| 743 |
+
{
|
| 744 |
+
"id": "llama3",
|
| 745 |
+
"name": "Llama 3",
|
| 746 |
+
"description": "Llama 3 instruction format"
|
| 747 |
+
},
|
| 748 |
+
{
|
| 749 |
+
"id": "mistral",
|
| 750 |
+
"name": "Mistral",
|
| 751 |
+
"description": "Mistral/Vicuna instruction format"
|
| 752 |
+
},
|
| 753 |
+
{
|
| 754 |
+
"id": "vicuna",
|
| 755 |
+
"name": "Vicuna",
|
| 756 |
+
"description": "Vicuna chat format"
|
| 757 |
+
},
|
| 758 |
+
{
|
| 759 |
+
"id": "phi3",
|
| 760 |
+
"name": "Phi-3",
|
| 761 |
+
"description": "Microsoft Phi-3 format"
|
| 762 |
+
},
|
| 763 |
+
{
|
| 764 |
+
"id": "reasoning",
|
| 765 |
+
"name": "Reasoning/CoT",
|
| 766 |
+
"description": "Chain-of-thought reasoning format with explicit thinking"
|
| 767 |
+
}
|
| 768 |
+
]
|
| 769 |
+
}
|
| 770 |
+
|
| 771 |
+
|
| 772 |
+
@router.get("/prompt-templates/{template_id}")
|
| 773 |
+
async def get_prompt_template(template_id: str):
|
| 774 |
+
"""Get specific prompt template configuration."""
|
| 775 |
+
config = PromptTemplateConfig(instruction_format=template_id)
|
| 776 |
+
preset = config.get_template_for_format(template_id)
|
| 777 |
+
|
| 778 |
+
if not preset and template_id != "none":
|
| 779 |
+
raise HTTPException(status_code=404, detail="Template not found")
|
| 780 |
+
|
| 781 |
+
return {
|
| 782 |
+
"id": template_id,
|
| 783 |
+
"config": preset
|
| 784 |
+
}
|
| 785 |
+
|
| 786 |
+
|
| 787 |
+
# ============================================
|
| 788 |
+
# TRAINING JOB ENDPOINTS
|
| 789 |
+
# ============================================
|
| 790 |
+
|
| 791 |
@router.post("/start", response_model=TrainingJobResponse)
|
| 792 |
async def start_training(
|
| 793 |
request: TrainingRequest,
|
|
|
|
| 808 |
dataset_source=request.dataset.source,
|
| 809 |
dataset_name=request.dataset.name,
|
| 810 |
dataset_config=request.dataset.config,
|
| 811 |
+
dataset_split=request.dataset.train_split,
|
| 812 |
training_args=request.training_args.dict(),
|
| 813 |
peft_config=request.peft_config.dict() if request.peft_config else None,
|
| 814 |
status=JobStatus.PENDING.value,
|
|
|
|
| 819 |
db.add(training_job)
|
| 820 |
await db.commit()
|
| 821 |
|
| 822 |
+
# Build full config
|
| 823 |
config = {
|
| 824 |
"job_id": job_id,
|
| 825 |
"task_type": request.task_type,
|
|
|
|
| 854 |
db: AsyncSession = Depends(get_db)
|
| 855 |
):
|
| 856 |
"""Get status of a training job."""
|
|
|
|
| 857 |
queue = get_queue()
|
| 858 |
queue_status = await queue.get_status(job_id)
|
| 859 |
|
|
|
|
| 860 |
result = await db.execute(
|
| 861 |
select(TrainingJob).where(TrainingJob.job_id == job_id)
|
| 862 |
)
|
|
|
|
| 865 |
if not job:
|
| 866 |
raise HTTPException(status_code=404, detail="Job not found")
|
| 867 |
|
|
|
|
| 868 |
if queue_status:
|
| 869 |
job.status = queue_status.get("status", job.status)
|
| 870 |
if queue_status.get("progress"):
|
|
|
|
| 945 |
if not cancelled:
|
| 946 |
raise HTTPException(status_code=400, detail="Cannot cancel job")
|
| 947 |
|
|
|
|
| 948 |
result = await db.execute(
|
| 949 |
select(TrainingJob).where(TrainingJob.job_id == job_id)
|
| 950 |
)
|