Repository navigation
docs(tutorials): add Ministral-3-3B text embedding fine-tuning tutorial - #4154
taran-dev4u wants to merge 4 commits into
Conversation
aec57ea to
affcd32
Compare
|
Thanks @taran-dev4u for the tutorial! The @NVIDIA-NeMo/automodel_retriever_maintainers will review and provide feedback, but please allow 5-10 working days, due to conflicting priorities. Thank you. |
affcd32 to
52e8cbf
Compare
jgerh
left a comment
There was a problem hiding this comment.
Completed tech pubs review of docs/model-coverage/embedding/index.mdx and provided a few comments
There was a problem hiding this comment.
| | [Nemotron end-to-end text bi-encoder recipe](https://github.com/NVIDIA-NeMo/Nemotron/tree/main/docs/nemotron/embed) | Adapt an embedding model to your document corpus with synthetic data generation, data preparation, NeMo AutoModel fine-tuning, evaluation, export when required, and deployment. | | |
| | [llama3_2_1b.yaml](https://github.com/NVIDIA-NeMo/Automodel/blob/main/examples/retrieval/bi_encoder/llama3_2_1b.yaml) | Bi-encoder, Llama 3.2 1B embedding model. | | |
| | [llama_embed_nemotron_8b.yaml](https://github.com/NVIDIA-NeMo/Automodel/blob/main/examples/retrieval/bi_encoder/llama_embed_nemotron_8b/llama_embed_nemotron_8b.yaml) | Bi-encoder, Llama-Embed-Nemotron-8B reproduction recipe. | | |
| | [ministral3_3b_instruct.yaml](https://github.com/NVIDIA-NeMo/Automodel/blob/main/examples/retrieval/bi_encoder/ministral3_3b_instruct.yaml) | Bi-encoder, Ministral3-3B recipe. | |
There was a problem hiding this comment.
Please check duplicate link in the Optimized Backbones table in index12.mdx, on two adjacent lines:
- Line 19: "Llama (Bidirectional)" links to /model-coverage/embedding-models/nvidia/llama-embed-nemotron-8b.
- Line 20: "Llama Nemotron VL" links to the same URL.
If Llama Nemotron VL has its own coverage page, the link on line 20 is the one to change.
There was a problem hiding this comment.
Good catch — updated line 20 in 70e5fd5 to point to /model-coverage/embedding-models/nvidia/llama-nemotron-embed-vl-1b-v2.
|
@taran-dev4u thanks for this MR. There is already an end-to-end pipeline for biencoder training & deployment. It covers synthetic data generation, hard neg mining, training, deployment, thus, I'd recommend you
|
Signed-off-by: Taran Mamidala <mamidalataran6333@gmail.com>
Signed-off-by: Taran Mamidala <mamidalataran6333@gmail.com>
…te title Signed-off-by: Taran Mamidala <mamidalataran6333@gmail.com>
5df164a to
59a3014
Compare
HuiyingLi
left a comment
There was a problem hiding this comment.
Reviewed all five changed files at 59a3014e352b97680b9f2ec0a3214c1134bb7565, including the executable notebook, YAML, sample data, and their production callers. Reconciled the independent correctness and adversarial-test audits. The inline findings identify reproducible failures or concrete data-flow/configuration defects.
Validation:
- 187 native CPU tests passed. Supplemental exact-tutorial checks produced 8 failing contract assertions and 11 passing controls, including corrected scheduler/batch inputs, real tiny-backbone inference/backward, and independently calculated loss objectives.
- The original single-GPU CLI configuration failed LR-scheduler setup; only its output directory was overridden. Unchanged notebook execution on H100 reproduced the inference AttributeError.
- Separate native full-depth smoke runs on one H100 and two H100s with FSDP2 completed a training step and consolidated checkpoint export using
max_steps=1,lr_warmup_steps=0, and isolated output directories. These controls do not validate the original schedule.
Execution used this PR's source, Torch 2.14.0a0+4fdf77b940.nv26.08, Transformers 5.18.0, and Hub checkpoint revision b6d637bef2393152b3da2b2fde72eecdee30557e. CI currently provides passing DCO/title checks only. No eight-rank execution, 100-step convergence, 24-GB feasibility, HF numerical parity, checkpoint-resume correctness, or post-initialization loaded-tensor audit is claimed.
| local_batch_size: 4 | ||
| ckpt_every_steps: 50 | ||
| val_every_steps: 25 | ||
| max_steps: 100 |
There was a problem hiding this comment.
[P2] Set the epoch budget so the default recipe can initialize
The documented single-GPU command fails before its first training step. With the eight bundled rows, local batch 4 and global batch 16, the loader has two microbatches and the step scheduler has one optimizer step per epoch. Omitting num_epochs keeps StepSchedulerConfig's default of 10, so the LR builder computes min(10 * 1, 100) = 10 decay steps. The configured ten-step warmup then triggers assert lr_warmup_steps < lr_decay_steps. I reproduced this through the native CLI with only the output directory overridden. Set an epoch budget consistent with 100 steps; num_epochs: null passed the production scheduler control and yielded 100 windows.
| "with torch.no_grad():\n", | ||
| " q_embeds = model(**q_inputs).sentence_embedding # [N_queries, D]\n", |
There was a problem hiding this comment.
[P2] Use the bi-encoder's mapping-in, tensor-out API
Executing the unchanged notebook on H100 reaches this cell and raises AttributeError: 'NoneType' object has no attribute 'sentence_embedding'. BiEncoderModel.forward(input_dict=None, **kwargs) reads the positional mapping; expanding tokenizer fields leaves input_dict=None, so it returns None. With the correct input it returns the embedding tensor itself. Use q_embeds = model(q_inputs) and p_embeds = model(p_inputs). The same original expressions failed with a real tiny backbone, while the positional-call control produced finite normalized embeddings and finite nonzero gradients.
| "automodel tutorials/embedding-biencoder/ministral3_3b_embed_config.yaml --nproc-per-node 1\n", | ||
| "\n", | ||
| "# Multi-GPU training with FSDP2:\n", | ||
| "automodel tutorials/embedding-biencoder/ministral3_3b_embed_config.yaml --nproc-per-node 8\n", |
There was a problem hiding this comment.
[P2] Supply batch sizes compatible with the eight-GPU command
This command uses eight DP ranks (tp_size=cp_size=1) with global batch 16 and local batch 4. The production scheduler rejects it with ValueError: global_batch_size (16) must be divisible by local_batch_size * dp_size (4 * 8), independently of the LR-schedule problem. The actual YAML/config builder reproduced this failure on CPU; DP1/DP2 and controls with DP8/local1 or DP8/global32 constructed successfully. Add appropriate batch overrides to the advertised command or ship compatible defaults. This is a deterministic configuration check, not an eight-rank execution result.
| "# recipe = TrainBiEncoderRecipe(cfg)\n", | ||
| "# recipe.setup()\n", | ||
| "# recipe.run()" |
There was a problem hiding this comment.
[P2] Make the advertised Python training block executable
Uncommenting this block first raises NameError because the notebook never imports TrainBiEncoderRecipe. After supplying the import, recipe.run() still does not exist; the native entry point calls run_train_validation_loop(). The notebook also reads its YAML from the tutorial directory, whereas that YAML's dataset path is repository-root-relative: building its dataset from the notebook directory reproduced FileNotFoundError. Import the recipe, use its supported loop method, and make the working directory/data-path handling consistent so this advertised alternative can actually start training.
| "from nemo_automodel import NeMoAutoModelBiEncoder, NeMoAutoTokenizer\n", | ||
| "\n", | ||
| "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", | ||
| "model_name = \"mistralai/Ministral-3-3B-Instruct-2512-BF16\"\n", |
There was a problem hiding this comment.
[P2] Verify the saved training result instead of reloading the base model
Step 4 is presented as verification after fine-tuning, but this value is passed directly to both loaders and always loads the original Hub checkpoint. No training output path reaches from_pretrained, so even after fixing the inference call, the displayed rankings cannot reflect any of the training updates. Resolve a selected consolidated checkpoint from Step 3 and use it for verification, with loading options appropriate to the exported text backbone rather than the original VLM. The full notebook data flow contains no checkpoint selection or override.
| "from sentence_transformers import SentenceTransformer\n", | ||
| "\n", | ||
| "# Load trained model directly\n", | ||
| "embedder = SentenceTransformer(\"./output/ministral3_3b_embed_tutorial/checkpoints/model/consolidated\")\n", |
There was a problem hiding this comment.
[P2] Include the selected epoch/step directory in the export path
The native recipe never writes the directory passed here. BaseRecipe.save_checkpoint first appends epoch_<epoch>_step_<step> to checkpoint_dir, then the checkpointer writes model/consolidated underneath it. Both the one- and two-H100 native smoke runs exported to <checkpoint_dir>/epoch_0_step_0/model/consolidated; <checkpoint_dir>/model/consolidated was absent. Select an actual saved step directory for this loader and update the path stated above, otherwise the deployment example cannot load the exported checkpoint even after training succeeds.
| attn_implementation: flash_attention_2 | ||
| extract_submodel: language_model | ||
| do_distributed_inbatch_negative: false | ||
|
|
There was a problem hiding this comment.
[P2] Enable the in-batch negatives advertised by the tutorial
Despite its name, setting this flag to false also disables local in-batch negatives. TrainBiEncoderRecipe then scores each query only against its own two passages; other queries' passages never enter the loss. The README advertises training with both in-batch and hard negatives. I exercised the real recipe loss method with two queries and independently enumerated cross-entropy: false matched the own-passage objective, while true included cross-query passages and matched the in-batch objective without a distributed process group. Enable the existing flag for the advertised objective, or explicitly describe this recipe as hard-negative-only training.
Set num_epochs: null in ministral3_3b_embed_config.yaml so StepSchedulerConfig derives epochs from max_steps, and enable do_distributed_inbatch_negative. Add batch overrides to the 8-GPU CLI command. In finetune.ipynb, import TrainBiEncoderRecipe with run_train_validation_loop(), use model(inputs) in similarity evaluation, and resolve the saved step consolidated checkpoint for verification and deployment. Signed-off-by: Taran Mamidala <mamidalataran6333@gmail.com>
|
Thanks for the thorough review! Updated in 2d00b0b:
|
What does this PR do ?
Adds a tutorial notebook (
Text Embedding Model Fine-Tuning with NeMo AutoModel) and reference configuration for contrastive fine-tuning ofmistralai/Ministral-3-3B-Instruct-2512-BF16as a text embedding bi-encoder usingTrainBiEncoderRecipe.Fixes #2388.
Changelog
tutorials/embedding-biencoder/finetune.ipynb: walkthrough covering contrastive learning concepts, data preparation, YAML config creation, launch command, cosine similarity evaluation, and consolidated safetensors export formistralai/Ministral-3-3B-Instruct-2512-BF16.tutorials/embedding-biencoder/sample_retrieval_data.jsonl: sample query-positive-hard-negative triples.tutorials/embedding-biencoder/ministral3_3b_embed_config.yaml: reference configuration formistralai/Ministral-3-3B-Instruct-2512-BF16withextract_submodel: language_model, average pooling, L2 normalization, and bidirectional attention.tutorials/README.mdanddocs/model-coverage/embedding/index.mdxwith links to the tutorial.Before your PR is "Ready for review"
Pre checks:
Additional Information