153 lines
5.9 KiB
Markdown
153 lines
5.9 KiB
Markdown
# Training Developer Notes
|
|
|
|
This file keeps internal training notes that are useful for maintainers but too
|
|
noisy for the top-level reproduction README.
|
|
|
|
## Core Files
|
|
|
|
| File | Description |
|
|
|------|-------------|
|
|
| `train_contrastors.py` | Primary training script for the reproduced embedding fine-tune. Uses InfoNCE, hard negatives, GradCache, and optional ViT LoRA. |
|
|
| `mine_hard_negatives.py` | Mines near-miss documents with the base model for hard-negative training. |
|
|
| `filter_hard_negatives_vqa.py` | Filters mined retrieval candidates with a VLM so false negatives are not used as hard negatives. |
|
|
| `run_filter_hard_negatives_chunks.py` | Runs VQA hard-negative filtering in fixed-size chunks, useful for large JSONL files. |
|
|
| `verify_embeddings.py` | Compares base vs fine-tuned embeddings with similarity margins and retrieval metrics. |
|
|
| `tests/test_grad_equivalence.py` | Single-GPU gradient correctness tests for GradCache math. |
|
|
| `tests/test_grad_multi_gpu.py` | Multi-GPU DDP/gather/loss-scaling gradient correctness tests. |
|
|
|
|
Legacy / secondary scripts:
|
|
|
|
- `train_colpali.py` — HF Trainer-based path. Simpler, but not the reproduced training run.
|
|
- `train_swift.py` — ms-swift alternative.
|
|
- `train.py`, `model.py`, `dataset.py`, `evaluate.py` — older local training/eval code.
|
|
|
|
## Data Formats
|
|
|
|
Basic JSONL format:
|
|
|
|
```json
|
|
{"query": "What is the population of Tokyo?", "chunk_path": "/path/to/chunk.png"}
|
|
```
|
|
|
|
Hard-negative format:
|
|
|
|
```json
|
|
{"query": "...", "chunk_path": "...", "neg_chunk_paths": ["/path/to/neg1.png", "/path/to/neg2.png"]}
|
|
```
|
|
|
|
Hard-negative-with-retrieval-candidates format, used before VQA filtering:
|
|
|
|
```json
|
|
{"query": "...", "chunk_path": "...", "neg_chunk_paths": ["..."], "retrieve_top20": [{"rank": 1, "path": "...", "score": 0.61}]}
|
|
```
|
|
|
|
Notes:
|
|
|
|
- `chunk_path` is resolved relative to the JSONL file when paths are relative.
|
|
- Training images are screenshot chunks; last chunks can be smaller than the common tile size.
|
|
- `neg_chunk_paths` should contain mined hard negatives, not random negatives.
|
|
|
|
## Training Pipeline
|
|
|
|
1. Data loading pre-validates images at init so all DDP ranks process the same number of batches.
|
|
2. `BiQwen3Processor` handles text tokenization and image preprocessing with visual-token resolution control.
|
|
3. `BiQwen3` embeds the text query and image document into single L2-normalized vectors.
|
|
4. InfoNCE is computed over the similarity matrix.
|
|
5. With hard negatives, docs are interleaved as `[pos, neg1, neg2, pos, neg1, neg2, ...]`.
|
|
6. Multi-GPU training uses `gather_with_grad` so document embeddings from other ranks contribute gradients.
|
|
7. GradCache keeps activation memory tied to `--grad-cache-chunk` rather than the full effective batch.
|
|
|
|
## VQA Filtering For Hard Negatives
|
|
|
|
`filter_hard_negatives_vqa.py` removes false negatives from mined retrieval candidates:
|
|
|
|
1. Read `retrieve_top20`.
|
|
2. Skip the positive `chunk_path`.
|
|
3. Check up to the first `K` non-positive candidates (`--candidate-k`, default `10`).
|
|
4. Ask the VLM to answer the query from each candidate image.
|
|
5. Judge that answer on the same image.
|
|
6. If verdict is `CORRECT`, treat the candidate as a false negative and skip it.
|
|
7. If verdict is `WRONG` or `CANNOT_ANSWER`, keep it as a hard negative.
|
|
8. Stop after collecting `--num-hard-negatives` hard negatives.
|
|
9. Skip the example if not enough hard negatives are found within the first `K` candidates.
|
|
|
|
Example:
|
|
|
|
```bash
|
|
OPENAI_API_KEY=... python filter_hard_negatives_vqa.py \
|
|
--input /tmp/sample_100_hn.jsonl \
|
|
--output /tmp/sample_100_hn_v2.jsonl \
|
|
--reviews-output /tmp/sample_100_hn_v2.reviews.jsonl \
|
|
--summary-output /tmp/sample_100_hn_v2.summary.json \
|
|
--candidate-k 10 \
|
|
--num-hard-negatives 2 \
|
|
--concurrency 8
|
|
```
|
|
|
|
For large files:
|
|
|
|
```bash
|
|
OPENAI_API_KEY=... python run_filter_hard_negatives_chunks.py \
|
|
--input training/data/lite-query-v2-full-filtered-hn.jsonl \
|
|
--output-dir training/data/lite-query-v2-full-filtered-hn-v2-chunks \
|
|
--chunk-size 10000 \
|
|
--candidate-k 10 \
|
|
--num-hard-negatives 2 \
|
|
--concurrency 8 \
|
|
--skip-existing
|
|
```
|
|
|
|
Each chunk folder contains:
|
|
|
|
- `filtered_hn.jsonl`
|
|
- `candidate_reviews.jsonl`
|
|
- `summary.json`
|
|
|
|
`summary.json` is updated incrementally and tracks missing-path ratios for positive paths,
|
|
reviewed candidate paths, and all checked paths combined.
|
|
|
|
## Dataset Packaging
|
|
|
|
For regenerating or uploading a Hugging Face dataset:
|
|
|
|
```bash
|
|
# Prepare HF dataset folder: convert absolute paths to relative paths and hardlink images.
|
|
python prepare_hf_dataset.py \
|
|
--split-dir training/data/lite-query-v2-full-filtered-hn-v2-chunks/split \
|
|
--image-root /opt/dlami/nvme/kiwix_tiles \
|
|
--output-dir hf_dataset_export/screenshot-training
|
|
|
|
# Package images into tar shards.
|
|
python package_hf_image_shards.py \
|
|
--source-dir hf_dataset_export/screenshot-training \
|
|
--output-dir hf_dataset_export_sharded/screenshot-training
|
|
|
|
# Upload to Hugging Face.
|
|
python upload_hf_dataset.py \
|
|
--local-dir hf_dataset_export_sharded/screenshot-training
|
|
```
|
|
|
|
Upload requires a Hugging Face token with write permission.
|
|
|
|
## Test Commands
|
|
|
|
```bash
|
|
# Single-GPU: GradCache math + RandContext + clip_loss + rope_deltas
|
|
CUDA_VISIBLE_DEVICES=0 uv run python tests/test_grad_equivalence.py
|
|
|
|
# Multi-GPU: DDP + gather + loss scaling + gradient sync
|
|
CUDA_VISIBLE_DEVICES=0,1 uv run torchrun --nproc_per_node=2 tests/test_grad_multi_gpu.py
|
|
```
|
|
|
|
## Archived / Experimental Mode
|
|
|
|
`train_contrastors.py` still has a `query-side-tune` mode that trains only the
|
|
query tower while keeping the doc/image tower frozen, so datastore embeddings do
|
|
not change. This is not part of the reproduction path in `README.md`.
|
|
|
|
Important caveats:
|
|
|
|
- Query-side retrieval eval depends on an external search API that accepts pre-computed `embedding` queries.
|
|
- `--query-side-backward direct` was the stable path in smoke tests.
|
|
- `--query-side-backward gradcache` was experimental and may hang in multi-GPU real-data runs.
|