| """ |
| Financial Options & Market Prediction Expert Model β T4 Optimized |
| """ |
| import os |
| import sys |
| import traceback |
|
|
| |
| os.environ["PYTHONUNBUFFERED"] = "1" |
|
|
| def log(msg): |
| print(msg, flush=True) |
|
|
| try: |
| import torch |
| from datasets import load_dataset, concatenate_datasets |
| from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig |
| from peft import LoraConfig, prepare_model_for_kbit_training |
| from trl import SFTTrainer, SFTConfig |
| log("β
All imports successful") |
| except Exception as e: |
| log(f"β Import error: {e}") |
| sys.exit(1) |
|
|
| |
| MODEL_ID = "mistralai/Mistral-7B-Instruct-v0.3" |
| HUB_MODEL_ID = "Saksham7772/FinOptions-Mistral-7B" |
| OUTPUT_DIR = "./finopt-mistral-7b-qlora" |
|
|
| SYSTEM_PROMPT = ( |
| "You are a quantitative financial analyst and options trading expert. " |
| "For every analysis you provide:\n" |
| "1. Identify which input data features are most influential " |
| "(e.g., implied volatility, volume, earnings, macro indicators, sentiment)\n" |
| "2. Explain the directional impact of each feature on the prediction " |
| "(bullish/bearish/neutral and why)\n" |
| "3. Provide your market prediction or options strategy recommendation with clear reasoning\n" |
| "4. Express your confidence level and key risk factors\n" |
| "Think step by step before answering." |
| ) |
|
|
| |
| |
| |
| log("=" * 60) |
| log("STEP 1/5: Loading datasets...") |
| log("=" * 60) |
|
|
| try: |
| ds_sujet = load_dataset("sujet-ai/Sujet-Finance-Instruct-177k", split="train") |
| log(f" Sujet Finance: {len(ds_sujet)} rows") |
|
|
| def convert_sujet(example): |
| system = (example.get("system_prompt") or "").strip() |
| if not system: |
| system = SYSTEM_PROMPT |
| user = (example.get("user_prompt") or "").strip() |
| answer = (example.get("answer") or "").strip() |
| if not user or not answer: |
| return {"messages": None} |
| return {"messages": [ |
| {"role": "system", "content": system}, |
| {"role": "user", "content": user}, |
| {"role": "assistant", "content": answer}, |
| ]} |
|
|
| ds_sujet = ds_sujet.map(convert_sujet, remove_columns=ds_sujet.column_names, num_proc=4) |
| ds_sujet = ds_sujet.filter(lambda x: x["messages"] is not None, num_proc=4) |
| log(f" Sujet after conversion: {len(ds_sujet)} rows") |
|
|
| ds_alpaca = load_dataset("gbharti/finance-alpaca", split="train") |
| log(f" Finance Alpaca: {len(ds_alpaca)} rows") |
|
|
| def convert_alpaca(example): |
| instruction = (example.get("instruction") or "").strip() |
| inp = (example.get("input") or "").strip() |
| output = (example.get("output") or "").strip() |
| if not instruction or not output: |
| return {"messages": None} |
| user_content = instruction + (f"\n\n{inp}" if inp else "") |
| return {"messages": [ |
| {"role": "system", "content": SYSTEM_PROMPT}, |
| {"role": "user", "content": user_content}, |
| {"role": "assistant", "content": output}, |
| ]} |
|
|
| ds_alpaca = ds_alpaca.map(convert_alpaca, remove_columns=ds_alpaca.column_names, num_proc=4) |
| ds_alpaca = ds_alpaca.filter(lambda x: x["messages"] is not None, num_proc=4) |
| log(f" Alpaca after conversion: {len(ds_alpaca)} rows") |
|
|
| ds_500k = load_dataset("Josephgflowers/Finance-Instruct-500k", split="train") |
| log(f" Finance 500K: {len(ds_500k)} rows") |
|
|
| def convert_500k(example): |
| system = (example.get("system") or "").strip() |
| if not system: |
| system = SYSTEM_PROMPT |
| user = (example.get("user") or "").strip() |
| assistant = (example.get("assistant") or "").strip() |
| if not user or not assistant: |
| return {"messages": None} |
| return {"messages": [ |
| {"role": "system", "content": system}, |
| {"role": "user", "content": user}, |
| {"role": "assistant", "content": assistant}, |
| ]} |
|
|
| ds_500k = ds_500k.map(convert_500k, remove_columns=ds_500k.column_names, num_proc=4) |
| ds_500k = ds_500k.filter(lambda x: x["messages"] is not None, num_proc=4) |
| log(f" 500K after conversion: {len(ds_500k)} rows") |
|
|
| combined = concatenate_datasets([ds_sujet, ds_alpaca, ds_500k]) |
| combined = combined.shuffle(seed=42) |
| split = combined.train_test_split(test_size=0.01, seed=42) |
| train_dataset = split["train"] |
| eval_dataset = split["test"] |
| log(f"β
COMBINED: {len(combined)} rows | Train: {len(train_dataset)} | Eval: {len(eval_dataset)}") |
|
|
| except Exception as e: |
| log(f"β Dataset error: {e}") |
| traceback.print_exc() |
| sys.exit(1) |
|
|
| |
| |
| |
| log("=" * 60) |
| log("STEP 2/5: Loading model...") |
| log("=" * 60) |
|
|
| try: |
| bnb_config = BitsAndBytesConfig( |
| load_in_4bit=True, |
| bnb_4bit_quant_type="nf4", |
| bnb_4bit_use_double_quant=True, |
| bnb_4bit_compute_dtype=torch.float16, |
| ) |
|
|
| model = AutoModelForCausalLM.from_pretrained( |
| MODEL_ID, |
| quantization_config=bnb_config, |
| device_map="auto", |
| torch_dtype=torch.float16, |
| trust_remote_code=True, |
| ) |
| model = prepare_model_for_kbit_training(model) |
| model.config.use_cache = False |
|
|
| tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True) |
| if tokenizer.pad_token is None: |
| tokenizer.pad_token = tokenizer.eos_token |
| tokenizer.padding_side = "right" |
|
|
| log(f"β
Model loaded: {MODEL_ID} ({model.num_parameters():,} params)") |
|
|
| |
| if torch.cuda.is_available(): |
| mem = torch.cuda.memory_allocated() / 1024**3 |
| total = torch.cuda.get_device_properties(0).total_memory / 1024**3 |
| log(f" GPU memory: {mem:.1f}GB / {total:.1f}GB used") |
| except Exception as e: |
| log(f"β Model loading error: {e}") |
| traceback.print_exc() |
| sys.exit(1) |
|
|
| |
| |
| |
| log("=" * 60) |
| log("STEP 3/5: Configuring training...") |
| log("=" * 60) |
|
|
| try: |
| peft_config = LoraConfig( |
| r=64, lora_alpha=128, |
| target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"], |
| lora_dropout=0.05, bias="none", task_type="CAUSAL_LM", |
| ) |
|
|
| sft_config = SFTConfig( |
| output_dir=OUTPUT_DIR, |
| num_train_epochs=1, |
| per_device_train_batch_size=1, |
| per_device_eval_batch_size=1, |
| gradient_accumulation_steps=16, |
| optim="paged_adamw_8bit", |
| learning_rate=2e-4, |
| max_grad_norm=0.3, |
| weight_decay=0.001, |
| warmup_ratio=0.03, |
| lr_scheduler_type="cosine", |
| |
| bf16=False, |
| fp16=False, |
| max_length=2048, |
| packing=False, |
| gradient_checkpointing=True, |
| gradient_checkpointing_kwargs={"use_reentrant": False}, |
| eval_strategy="steps", |
| eval_steps=2000, |
| save_strategy="steps", |
| save_steps=2000, |
| save_total_limit=3, |
| load_best_model_at_end=True, |
| metric_for_best_model="eval_loss", |
| disable_tqdm=True, |
| logging_strategy="steps", |
| logging_steps=10, |
| logging_first_step=True, |
| report_to="none", |
| push_to_hub=True, |
| hub_model_id=HUB_MODEL_ID, |
| hub_strategy="every_save", |
| seed=42, |
| dataloader_num_workers=2, |
| ) |
| log("β
Config created") |
| except Exception as e: |
| log(f"β Config error: {e}") |
| traceback.print_exc() |
| sys.exit(1) |
|
|
| |
| |
| |
| log("=" * 60) |
| log("STEP 4/5: Creating trainer and tokenizing...") |
| log("=" * 60) |
|
|
| try: |
| trainer = SFTTrainer( |
| model=model, |
| args=sft_config, |
| train_dataset=train_dataset, |
| eval_dataset=eval_dataset, |
| peft_config=peft_config, |
| processing_class=tokenizer, |
| ) |
| log(f"β
Trainer created") |
| log(f" Trainable params: {sum(p.numel() for p in trainer.model.parameters() if p.requires_grad):,}") |
| log(f" Total steps: {trainer.state.max_steps if hasattr(trainer.state, 'max_steps') else 'unknown'}") |
|
|
| |
| dtype_counts = {} |
| for name, p in trainer.model.named_parameters(): |
| dt = str(p.dtype) |
| dtype_counts[dt] = dtype_counts.get(dt, 0) + 1 |
| log(f" Param dtypes: {dtype_counts}") |
|
|
| if torch.cuda.is_available(): |
| mem = torch.cuda.memory_allocated() / 1024**3 |
| total = torch.cuda.get_device_properties(0).total_memory / 1024**3 |
| log(f" GPU memory after trainer: {mem:.1f}GB / {total:.1f}GB") |
|
|
| except Exception as e: |
| log(f"β Trainer creation error: {e}") |
| traceback.print_exc() |
| sys.exit(1) |
|
|
| |
| |
| |
| log("=" * 60) |
| log("STEP 5/5: TRAINING...") |
| log(f" Model: {MODEL_ID}") |
| log(f" Dataset: {len(train_dataset)} examples") |
| log(f" Epochs: 1 | Batch: 1 | GradAccum: 16 | EffBatch: 16") |
| log(f" LoRA r=64 Ξ±=128 | LR: 2e-4 | fp32 training") |
| log(f" Push to: {HUB_MODEL_ID}") |
| log("=" * 60) |
|
|
| try: |
| trainer.train() |
| log("β
Training complete!") |
| except Exception as e: |
| log(f"β Training error: {e}") |
| traceback.print_exc() |
| |
| try: |
| log("Attempting to save partial model...") |
| trainer.save_model(OUTPUT_DIR) |
| tokenizer.save_pretrained(OUTPUT_DIR) |
| log("Partial model saved") |
| except: |
| log("Could not save partial model") |
| sys.exit(1) |
|
|
| |
| |
| |
| log("Saving final model...") |
| trainer.save_model(OUTPUT_DIR) |
| tokenizer.save_pretrained(OUTPUT_DIR) |
| log(f"β
DONE! Model at: https://huggingface.co/{HUB_MODEL_ID}") |
|
|