Stage overview
Pre-training → Fine-tuning → SFT or RL → Inference
↑
Hyperparameter Tuning (feeds best config back to Stage 1)
All stages run on 2× NVIDIA H200 GPUs via PyTorch DDP (torchrun --nproc_per_node=2).
Stage 1 — Pre-training (src/training/pretrain.py)
Goal: Learn vision-language alignment on a large, noisy dataset.
Dataset: Conceptual Captions 3M (conceptual_captions on HuggingFace Hub).
Loss: Standard cross-entropy next-token prediction (teacher-forcing).
Key settings (see configs/pretrain.yaml):
| Param | Value | Rationale |
|---|---|---|
| LR | 1e-4 | AdamW with cosine decay |
| Effective batch | 256 | 64/GPU × 2 steps accum × 2 GPUs |
| BF16 | true | H200 native BF16 tensor cores |
| Warmup | 2000 steps | Stabilises early training |
| Epochs | 30 | Large noisy dataset needs more passes |
Optimizer: AdamW with parameter-group weight decay: biases and LayerNorm parameters are exempt from weight decay.
Scheduler: Cosine annealing with linear warmup, decaying to 10% of peak LR.
Stage 2 — Fine-tuning (src/training/finetune.py)
Goal: Adapt the pre-trained model to a high-quality captioning dataset.
Dataset: COCO Captions 2017 (coco_captions on HuggingFace Hub, ~120k training images, 5 captions each).
Key difference from pre-training: The vision encoder trains at 10× lower LR (vision_encoder_lr_scale: 0.1) to prevent catastrophic forgetting of visual representations.
Key settings (see configs/finetune.yaml):
| Param | Value |
|---|---|
| LR | 2e-5 |
| Vision encoder LR | 2e-6 |
| Effective batch | 256 |
| Epochs | 10 |
Stage 3a — SFT (src/training/rl_sft_trainer.py, mode: sft)
Supervised fine-tuning on a curated subset. Structurally identical to Stage 2 but with a lower LR (5e-6) and dedicated config section. Use this stage to specialise the model on a specific caption style or domain.
Stage 3b — RL with REINFORCE (src/training/rl_sft_trainer.py, mode: rl)
Goal: Optimise non-differentiable caption quality metrics (CIDEr, BLEU-4) directly.
Algorithm: REINFORCE with a running-mean variance-reduction baseline.
Per-step procedure:
- Rollout: For each image in the batch, sample
K=4candidate captions using nucleus sampling (temperature=1.0, top-p=0.9). - Reward: Score each candidate with CIDEr (or BLEU-4) against the ground-truth captions.
- Advantage:
A = reward − baselinewhere baseline is an exponential moving average of past rewards. - Policy gradient loss:
L_PG = −mean(A × log_prob_of_sampled_tokens) - KL penalty:
L_KL = KL(π_θ ‖ π_ref)— penalises divergence from the frozen reference model copy loaded at init. Prevents reward hacking. - Entropy bonus:
L_ent = −H(π_θ)— encourages exploration to avoid caption collapse.
Total loss:
L = L_PG + kl_coeff × L_KL − entropy_coeff × L_ent
Stage 4 — Inference (src/inference/generator.py)
Three decoding strategies:
| Strategy | Description | When to use |
|---|---|---|
| Greedy | argmax at every step | Fastest; lowest quality |
| Beam search | Keep top-K partial sequences | Best for deterministic evaluation (e.g., CIDEr benchmark) |
| Nucleus sampling | Top-p / top-k with temperature | Diversity; also used for RL rollouts |
All three strategies are implemented with @torch.no_grad() and are GPU-accelerated. Beam search processes one image at a time for readability; batch beam search can be added as an optimisation.
Stage 5 — Hyperparameter Tuning (src/tuning/hyperparameter_tuning.py)
Uses Optuna with a MedianPruner to search over:
- Learning rate (log-uniform 1e-5 to 5e-4)
- Batch size (16 / 32 / 64)
- Number of encoder/decoder layers (4–8)
- Hidden dimension (256 / 384 / 512)
- Dropout (0.0–0.2)
- Weight decay (log-uniform 1e-4 to 1e-1)
- Warmup steps (100–2000)
Each trial runs for 3 epochs on 20k training samples. The pruner stops unpromising trials after the first epoch. Results are stored in an SQLite database for resumption across sessions.
Distributed training
All multi-GPU stages use PyTorch DDP launched with torchrun. Key points:
DistributedSamplerensures each GPU sees a non-overlapping subset of the data.- Gradient synchronisation happens automatically via DDP's all-reduce.
- Logging, checkpointing, and WandB calls are gated on
rank == 0. ipc: hostin docker-compose enables shared-memory for DataLoader workers.
Checkpointing
A checkpoint is saved every save_interval steps and at the end of each epoch. The checkpoint with the lowest validation loss is also saved as checkpoint_best.pt. The last keep_last_n_checkpoints checkpoints are retained; older ones are removed to save disk space.