fix(rerank) Fix rerank finetune optimizer precision#208
Conversation
Add a new `nemotron rerank` recipe for fine-tuning cross-encoder
reranking models, following the same stage-based pattern as the
embed recipe.
Stages:
- finetune: Fine-tune using TrainCrossEncoderRecipe from nemo-automodel
- eval: Evaluate reranking quality via BEIR (first-stage retrieval + re-rank)
- export: Export to ONNX/TensorRT using nemo-export reranker adapter
- deploy: Deploy NIM reranker container
The recipe consumes training data from the embed prep stage directly —
the same {query, pos_doc[], neg_doc[]} format works for both biencoder
and cross-encoder training via nemo-automodel's model_type parameter.
Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
- Monkeypatch create_bidirectional_mask during ONNX export to avoid transformers masking_utils tracing incompatibility - Cap lr_warmup_steps to total_steps-1 for small datasets - Add eval_nim code path with NIM reranker /v1/ranking API support - Use SentenceTransformer for first-stage retrieval (trust_remote_code) - Pass trust_remote_code=True to CrossEncoder in eval - Rename model from llama-3.2-nv-rerankqa-1b-v2 to llama-nemotron-rerank-1b-v2 across all configs and code - Pin NIM image to nvcr.io/nim/nvidia/llama-nemotron-rerank-1b-v2:1.10.0 - Pin onnx<1.20 and add ml_dtypes compat, add UV environments constraint - Set trust_remote_code in crossencoder_base.yaml for model and tokenizer - Pin transformers>=5.3.0,<5.4.0 for nemo-automodel compat in finetune Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
Add sdg and prep subcommands to the rerank recipe that delegate to the embed implementations. This lets users run the full pipeline end-to-end with `nemotron rerank run` without needing to know about the embed recipe. The pipeline now runs: sdg → prep → finetune → eval (→ export → deploy). Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
- Add sdg and prep subcommands delegating to embed implementations, so users can run the full pipeline with `nemotron rerank run` - Pipeline now runs: sdg → prep → finetune → eval (→ export → deploy) - Update nemo-automodel to 897ebedf - Use transformers 5.5.x via uv override-dependencies Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
- Renumber rerank stages to match 6-stage layout: stage0_sdg, stage1_prep, stage2_finetune, stage3_eval, stage4_export, stage5_deploy - Add rerank-specific config for sdg and prep stages that write to output/rerank/ instead of output/embed/ - Create proper CLI commands for sdg/prep that use embed scripts with rerank config directories - Fix retriever-sdg deduplication to use data-designer 0.5.3+ API (generate_text_embeddings instead of removed _router.embedding) - Bump data-designer minimum to >=0.5.3 - Update all output paths to use new stage numbering Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
Add NIM vs finetuned metrics comparison in eval stage output, with accuracy threshold checks matching embed recipe pattern. Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
Local evaluation was feeding raw (query, passage) pairs to the
cross-encoder without the "question:{query} \n \n passage:{passage}"
template used during training and by NIM internally. This caused local
scores to underreport by ~10% NDCG, making it impossible to compare
fine-tuned checkpoints against NIM baselines.
Replace sentence_transformers CrossEncoder with direct
AutoModelForSequenceClassification + AutoTokenizer so we control
input formatting and apply the prompt template consistently.
Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
Use torch.distributed.run with --nproc_per_node=gpu so training automatically uses all available GPUs (works correctly with 1 GPU too). Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
Detect whether the input file is a JSON array or JSONL (one object per line) by peeking at the first character, so both formats are handled. Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
Add retrieval_batch_size (default 32) for first-stage retrieval encoding, keeping batch_size (128) for reranker scoring. The embedding model needs a smaller batch size due to longer sequence processing. Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
Torch was being locked to cu130 wheels in uv.lock, causing GPU to not be used on CUDA 12.x systems. Instead, exclude torch from dependency resolution and supply it explicitly via `--with torch` in the CLI commands, with UV_TORCH_BACKEND=auto to resolve the correct CUDA variant. Applies to rerank (finetune, eval, export, prep) and embed (prep) stages. Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
Use SentenceTransformer multi-process pool for first-stage retrieval encoding and DataParallel for cross-encoder reranking when multiple GPUs are available. Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
Show scoring progress with batch count and size during evaluation. Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
Reduce top_k from 100 to 10 and k_values from [1,5,10,100] to [1,5,10]. This gives ~10x speedup on the cross-encoder scoring step since fewer candidates are re-ranked per query. Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
Load cross-encoder with torch_dtype=bfloat16 and padding_side="left" to align with nemo-retriever-research evaluation defaults. Reduces memory usage and matches the reference implementation's tokenization. Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
sentence-transformers now handles multi-GPU automatically in encode(). The explicit multi-process pool and encode_multi_process calls are deprecated and no longer needed. Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
…ntainers When torch is already importable (e.g., inside an NVIDIA container), create a venv with --system-site-packages and exclude torch from UV resolution. This avoids the CUDA version mismatch where UV's torch-backend=auto detects the kernel driver CUDA version (via nvidia-smi) but the container's libcuda.so is a different version. When torch is NOT importable (bare machine), fall back to the existing uv run --with torch approach with UV_TORCH_BACKEND=auto. Consolidates duplicated _execute_uv_local logic from 10 CLI commands into nemotron.kit.uv_local.execute_uv_local. Signed-off-by: Oliver Holworthy <1216955+oliverholworthy@users.noreply.github.com>
* add rerank recipe readme Signed-off-by: Steve Han <sthan@nvidia.com> * fix wrong repo url Signed-off-by: Steve Han <sthan@nvidia.com> --------- Signed-off-by: Steve Han <sthan@nvidia.com>
Signed-off-by: Steve Han <sthan@nvidia.com>
a2b6436 to
cda44d5
Compare
e6e8a32 to
15f8009
Compare
| @@ -71,6 +72,7 @@ optimizer: | |||
| weight_decay: 0.01 | |||
| betas: [0.9, 0.999] | |||
| eps: 1.0e-8 | |||
| fused: true | |||
|
|
|||
There was a problem hiding this comment.
@shan-nvidia @oliverholworthy we should set "master_weights: true" here. that will give you proper loss curve. If it is False by default, you might get lower accuracy.
"master_weights: true" tells Transformer Engine’s FusedAdam optimizer to keep a separate higher-precision copy of the model weights, usually FP32, inside the optimizer.
I see you also changed the target: transformer_engine.pytorch.optimizers.fused_adam.FusedAdam to target: torch.optim.AdamW . In that case we would not need master_weights: true, but was that really necessary?
Please see the example config: https://github.com/NVIDIA-NeMo/Automodel/blob/main/examples/retrieval/cross_encoder/llama3_2_1b.yaml#L115C3-L115C17
There was a problem hiding this comment.
Thanks @rnyak. Automodel uses TE Adam whereas recipe uses torch Adam. I think torch Adam doesn't have a master_weights flag. So the fix is sort of mimicking the master_weights with FP32 behavior.
I tried to switch to TE Adam for recipe but got dep version issue (on rapids lab server) on local mode. Here are some details:
transformer-engine[pytorch]<=2.11.0 (added on this branch) cannot install in your local uv environment because no prebuilt TE wheel exists for torch 2.10. I verified the actual assets on the TE GitHub release pages:
| TE version | Available CUDA‑12 wheels (cp312, x86_64) |
|---|---|
| v2.11 | +cu12torch2.8.0+cu129 only |
| v2.12 | +cu12torch2.8.0+cu129 only |
| v2.13 | +cu12torch2.8.0+cu129 only |
| v2.14 / v2.14.1 | +cu12torch2.8.0+cu129 only |
| v2.15 | +cu12torch2.8.0+cu129 only |
| What's happening at install time: |
_execute_with_uv_torch in src/nemotron/kit/uv_local.py runs uv run --with torch ... with UV_TORCH_BACKEND=auto. Your driver advertises CUDA 12.9, so uv resolves torch to 2.10.0+cu129.
TE 2.11's setup.py then guesses the wheel URL transformer_engine_torch-2.11.0+cu12torch2.10.0+cu128cxx11abiTRUE-...whl — that file doesn't exist (correct asset is +cu12torch2.8.0+cu129).
It falls back to source build, which immediately fails on cudnn.h: No such file or directory because cuDNN dev headers aren't on your bare host (they live in the nvcr.io/nvidia/pytorch:25.12-py3 container the recipe declares).
The mismatch isn't really cudnn — it's that TE 2.11 only ships a wheel for torch 2.8.0, while uv's auto backend pulled torch 2.10.0. The cudnn error is just the second symptom after the wheel‑lookup misses.
nemo-automodel @ 897ebedf declares torch>=2.6.0,<=2.10.0 and transformer-engine[pytorch]<=2.11.0, so it has the same shape; nemo-automodel relies on the NGC container to either ship TE prebuilt or to provide cudnn for source builds.
@oliverholworthy let me know if there is a better fix.
420c0ac to
d4696b9
Compare
Summary
Fix the reranker fine-tune recipe so local torch AdamW training preserves fp32 optimizer-state behavior and can resume without regressing optimizer metadata to bf16.
The investigation found that the recipe path could create Adam moment state from bf16 model parameters, while the reference POC behavior kept optimizer state in fp32. This PR makes that behavior explicit for the local recipe path and adds regression coverage for resume.
What Changed
force_fp32_parameters=true.torch.optim.AdamW(fused=True)for local mode instead of requiring Transformer Engine.torch_dtype: float32and configure an fp32 FSDPMixedPrecisionPolicy.add_eos_token=false).Root Cause
AdamW moment tensors (
exp_avg,exp_avg_sq, andstep) followed the dtype of the parameters used to construct/load optimizer state. With bf16 parameters, the optimizer checkpoint metadata recorded bf16 moment state, which changed training behavior relative to the fp32-state POC.Resume had a second failure mode: casting parameters after the normal Automodel restore path was too late, because optimizer state had already been loaded. The new restore hook makes the load order explicit.
Validation
Manual fine-tune/eval runs confirmed that the fixed train curve and eval results match the reference POC behavior.