Skip to main content

Introduction

This cookbook walks you through creating a fine-tuning dataset from the PubMedQA corpus and running an end-to-end job on Nscale. The goal is a pair of CSV files with columns question and answer that you can upload to Nscale and use to fine-tune a base model.

Requirements

  • Python 3.9+
  • Packages: datasets, pandas
  • Nscale service token and organization ID
Install dependencies:
pip install datasets pandas

Generate the dataset CSVs

To use Nscale to fine-tune a model, you need a dataset in the required CSV format. The script below converts the PubMedQA dataset to a simple Q/A CSV format. You can adapt the same approach for other datasets.
  • Input prompt: question
  • Output target: long_answer when present, otherwise the categorical final_decision (e.g., yes/no/maybe)
It creates two CSVs: train and validation. For simplicity, it first combines all available splits of the selected PubMedQA configuration (e.g., train/validation/test) into one pool, then splits that pool using a configurable train ratio (default 0.8 → 80/20) with a fixed seed for reproducibility. This ensures all examples in the config are included exactly once.
from typing import Dict, Iterable, Optional

import pandas as pd
from datasets import Dataset, DatasetDict, load_dataset


def example_to_qa_row(ex: Dict) -> Optional[Dict[str, str]]:
    """Convert one PubMedQA pqa_artificial example to a question/answer row.

    - question: use the provided question string (omit context entirely).
    - answer: prefer `long_answer` if non-empty; otherwise use `final_decision`.
    """
    question = str(ex.get("question", "")).strip()
    long_answer = ex.get("long_answer")
    final_decision = ex.get("final_decision")

    la = (str(long_answer).strip() if long_answer is not None else "")
    fd = (str(final_decision).strip() if final_decision is not None else "")

    answer = la if la else fd
    return {"question": question, "answer": answer}


def dataset_to_csv(ds_split: Iterable[Dict], out_path: str) -> None:
    rows = []
    for ex in ds_split:
        row = example_to_qa_row(ex)
        if row:
            rows.append(row)
    pd.DataFrame(rows).to_csv(out_path, index=False)


def main() -> None:
    """Build an 80/20 train/validation Q/A split from PubMedQA (pqa_artificial).

    - Loads `qiaojin/PubMedQA` with config `pqa_artificial` (train split).
    - Randomly splits into 80% train and 20% validation (seed=42).
    - Writes two CSVs with columns: `question`, `answer`.
    - `answer` is `long_answer` if present, else `final_decision`.
    """
    ds_all: DatasetDict = load_dataset("qiaojin/PubMedQA", "pqa_artificial")

    if "train" not in ds_all:
        raise RuntimeError("Expected a 'train' split in PubMedQA pqa_artificial")

    base: Dataset = ds_all["train"]
    split = base.train_test_split(test_size=0.2, seed=42, shuffle=True)
    train_ds: Dataset = split["train"]
    val_ds: Dataset = split["test"]

    dataset_to_csv(train_ds, "pubmedqa_pqa_artificial_train_qa.csv")
    dataset_to_csv(val_ds, "pubmedqa_pqa_artificial_validation_qa.csv")
    print(
        "Wrote pubmedqa_pqa_artificial_train_qa.csv ({} examples) and "
        "pubmedqa_pqa_artificial_validation_qa.csv ({} examples)".format(
            len(train_ds), len(val_ds)
        )
    )


if __name__ == "__main__":
    main()
Save the script as convert.py, then run it (80/20 split):
python convert.py 
The script produces two files:
  • pubmedqa_pqa_labeled_train_qa.csv
  • pubmedqa_pqa_labeled_validation_qa.csv
Each CSV has headers question,answer.

Create a fine-tuning job

You can create the fine-tuning job in the Nscale Console or via API. Below shows how to do it with the Nscale Fine-tuning API.

1. Upload files and create a dataset

Export your Nscale token and organization ID, then upload both CSVs. Save the returned file ids for the next step.
export NSCALE_API_TOKEN="<your_service_token>"
export ORGANIZATION_ID="<your_organization_id>"

curl -X POST "https://fine-tuning.api.nscale.com/api/v1/organizations/$ORGANIZATION_ID/files" \
  -H "Authorization: Bearer $NSCALE_API_TOKEN" \
  -H 'Content-Type: multipart/form-data' \
  -H 'Accept: application/json' \
  -F 'file=@pubmedqa_pqa_artificial_train_qa.csv'

curl -X POST "https://fine-tuning.api.nscale.com/api/v1/organizations/$ORGANIZATION_ID/files" \
  -H "Authorization: Bearer $NSCALE_API_TOKEN" \
  -H 'Content-Type: multipart/form-data' \
  -H 'Accept: application/json' \
  -F 'file=@pubmedqa_pqa_artificial_validation_qa.csv'

2. Create the dataset

Use the two file ids returned above. Save returned dataset id to create a fine-tuning job:
curl -X POST "https://fine-tuning.api.nscale.com/api/v1/organizations/$ORGANIZATION_ID/datasets" \
  -H "Authorization: Bearer $NSCALE_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -d '{
    "name": "pubmedqa-pqa-labeled-qa",
    "training_file_id": "<train_file_id>",
    "validation_file_id": "<validation_file_id>"
  }'

3. Create and monitor a fine-tuning job

Ensure you have enough credits in your account. First, list available base models and pick one. You can find a list of avaialble model also (here)[docs/cookbooks/pubmedqa-f ine-tuning.mdx]
curl -X GET "https://fine-tuning.api.nscale.com/api/v1/organizations/$ORGANIZATION_ID/base-models" \
  -H "Authorization: Bearer $NSCALE_API_TOKEN" \
  -H 'Accept: application/json'
Create a job that maps our CSV columns to the job’s expected inputs. Here, question is the prompt and answer is the target output.
curl -X POST "https://fine-tuning.api.nscale.com/api/v1/organizations/$ORGANIZATION_ID/jobs" \
  -H "Authorization: Bearer $NSCALE_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -d '{
    "name": "pubmedqa-finetune",
    "base_model_id": "e082db90-cf4a-4be9-a0e6-b2f8067c1cf6",
    "dataset": {
      "id": "<YOUR_DADASET_ID>",
      "prompt_column": "question",
      "answer_column": "answer"
    },
    "hyperparameters": {
    "batch_size": 32,
    "best_checkpoints": false,
    "learning_rate": 0.00001,
    "lora": {
      "alpha": 32,
      "dropout": 0.05,
      "enabled": true,
      "r": 16,
      "trainable_modules": []
    },
    "n_epochs": 1,
    "n_evals": 2,
    "warmup_ratio": 0.1,
    "weight_decay": 0.01,
    "mask_prompt_labels": true
  }
  }'
List jobs and poll a specific job for status:
curl -X GET "https://fine-tuning.api.nscale.com/api/v1/organizations/$ORGANIZATION_ID/jobs" \
  -H "Authorization: Bearer $NSCALE_API_TOKEN" \
  -H 'Accept: application/json'

curl -X GET "https://fine-tuning.api.nscale.com/api/v1/organizations/$ORGANIZATION_ID/jobs/<job_id>" \
  -H "Authorization: Bearer $NSCALE_API_TOKEN" \
  -H 'Accept: application/json'
Retrieve training metrics (e.g., train_loss, eval_loss, perplexity):
curl -X GET "https://fine-tuning.api.nscale.com/api/v1/organizations/$ORGANIZATION_ID/jobs/<job_id>/metrics" \
  -H "Authorization: Bearer $NSCALE_API_TOKEN" \
  -H 'Accept: application/json'
When the job completes, export to Hugging Face or download the model as needed. See the Fine-tuning guide for details.
I