Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
81b92e1
feat(rerank): add cross-encoder reranking recipe
oliverholworthy Apr 10, 2026
2dfc409
fix(rerank): export compat, NIM eval, and model rename
oliverholworthy Apr 10, 2026
15cc1ac
feat(rerank): add sdg and prep commands delegating to embed
oliverholworthy Apr 10, 2026
5eccaae
feat(rerank): add sdg/prep commands, update automodel and transformers
oliverholworthy Apr 10, 2026
e8e844d
fix(rerank): use port 8000 default, consistent with embed
oliverholworthy Apr 10, 2026
1dc05ab
refactor(rerank): renumber stages, add sdg/prep configs, fix SDG dedup
oliverholworthy Apr 10, 2026
bc23241
feat(rerank): add NIM vs finetuned comparison output in eval
oliverholworthy Apr 13, 2026
30de667
fix(rerank): apply prompt template in local eval to match NIM formatting
oliverholworthy Apr 13, 2026
756e4f2
feat(rerank): launch finetune with torchrun for multi-GPU support
oliverholworthy Apr 14, 2026
45ea89c
fix(embed): support JSONL format in convert_to_retriever_data
oliverholworthy Apr 14, 2026
7744769
fix(rerank): separate retrieval and reranker batch sizes in eval
oliverholworthy Apr 14, 2026
b01c0a0
Merge branch 'main' into oholworthy/rerank-recipe-v1
shan-nvidia Apr 15, 2026
6772ba0
fix: exclude torch from uv resolution, supply via --with at runtime
oliverholworthy Apr 16, 2026
2b0b110
feat(rerank): add multi-GPU support for eval retrieval and reranking
oliverholworthy Apr 16, 2026
c30729d
feat(rerank): add tqdm progress bar for reranker scoring in eval
oliverholworthy Apr 16, 2026
0f58b53
fix(rerank): reduce default top_k and k_values for faster eval
oliverholworthy Apr 16, 2026
ccde7a0
fix(rerank): use bf16 precision and left padding in eval
oliverholworthy Apr 16, 2026
8b9032d
refactor(rerank): remove deprecated encode_multi_process
oliverholworthy Apr 16, 2026
5a80e2f
refactor: extract shared uv_local helper to avoid CUDA mismatch in co…
oliverholworthy Apr 16, 2026
e6e8a32
Add rerank recipe readme (#151)
shan-nvidia Apr 16, 2026
cda44d5
Fix rerank finetune optimizer precision
shan-nvidia May 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/nemotron/cli/bin/nemotron.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,13 @@ def _register_groups() -> None:
from nemotron.cli.commands.super3 import super3_app
from nemotron.cli.kit import kit_app
from nemotron.cli.commands.embed import embed_app
from nemotron.cli.commands.rerank import rerank_app

app.add_typer(nano3_app, name="nano3")
app.add_typer(super3_app, name="super3")
app.add_typer(kit_app, name="kit")
app.add_typer(embed_app, name="embed")
app.add_typer(rerank_app, name="rerank")


# Register groups on import
Expand Down
33 changes: 10 additions & 23 deletions src/nemotron/cli/commands/embed/eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,33 +101,20 @@ def _execute_eval(cfg: RecipeConfig, *, experiment=None):

def _execute_uv_local(train_path: Path, passthrough: list[str]) -> None:
"""Execute eval locally via UV isolated environment."""
import os
import shutil
import subprocess

uv_cmd = shutil.which("uv")
if not uv_cmd:
typer.echo("Error: 'uv' command not found. Please install uv.", err=True)
raise typer.Exit(1)
from nemotron.kit.uv_local import execute_uv_local

script_abs = SPEC.script_path
stage_dir = script_abs.parent
repo_root = SPEC.script_path.parents[len(Path(SCRIPT_PATH).parts) - 1]
cmd = [
uv_cmd, "run",
"--with", str(repo_root),
"--project", str(stage_dir),
"python", str(script_abs),
"--config", str(train_path),
*passthrough,
]

env = os.environ.copy()
env.pop("VIRTUAL_ENV", None)

typer.echo(f"Executing with uv isolated environment: {' '.join(cmd)}")
result = subprocess.run(cmd, env=env)
raise typer.Exit(result.returncode)

rc = execute_uv_local(
script_path=str(script_abs),
stage_dir=stage_dir,
repo_root=repo_root,
train_path=train_path,
passthrough=passthrough,
)
raise typer.Exit(rc)


def _execute_remote(
Expand Down
41 changes: 13 additions & 28 deletions src/nemotron/cli/commands/embed/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,41 +102,26 @@ def _execute_uv_local(train_path: Path, passthrough: list[str], job_config) -> N

Conditionally includes TensorRT dependency based on config.
"""
import os
import shutil
import subprocess

uv_cmd = shutil.which("uv")
if not uv_cmd:
typer.echo("Error: 'uv' command not found. Please install uv.", err=True)
raise typer.Exit(1)
from nemotron.kit.uv_local import execute_uv_local

script_abs = SPEC.script_path
stage_dir = script_abs.parent
repo_root = SPEC.script_path.parents[len(Path(SCRIPT_PATH).parts) - 1]
cmd = [
uv_cmd, "run",
"--with", str(repo_root),
"--project", str(stage_dir),
]

# Conditionally include TensorRT dependency
extra_with = []
export_to_trt = job_config.get("export_to_trt", False)
if export_to_trt:
cmd += ["--extra", "tensorrt"]

cmd += [
"python", str(script_abs),
"--config", str(train_path),
*passthrough,
]

env = os.environ.copy()
env.pop("VIRTUAL_ENV", None)

typer.echo(f"Executing with uv isolated environment: {' '.join(cmd)}")
result = subprocess.run(cmd, env=env)
raise typer.Exit(result.returncode)
extra_with.append("tensorrt")

rc = execute_uv_local(
script_path=str(script_abs),
stage_dir=stage_dir,
repo_root=repo_root,
train_path=train_path,
passthrough=passthrough,
extra_with=extra_with,
)
raise typer.Exit(rc)


def _execute_remote(
Expand Down
33 changes: 10 additions & 23 deletions src/nemotron/cli/commands/embed/finetune.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,33 +99,20 @@ def _execute_finetune(cfg: RecipeConfig, *, experiment=None):

def _execute_uv_local(train_path: Path, passthrough: list[str]) -> None:
"""Execute finetune locally via UV isolated environment."""
import os
import shutil
import subprocess

uv_cmd = shutil.which("uv")
if not uv_cmd:
typer.echo("Error: 'uv' command not found. Please install uv.", err=True)
raise typer.Exit(1)
from nemotron.kit.uv_local import execute_uv_local

script_abs = SPEC.script_path
stage_dir = script_abs.parent
repo_root = SPEC.script_path.parents[len(Path(SCRIPT_PATH).parts) - 1]
cmd = [
uv_cmd, "run",
"--with", str(repo_root),
"--project", str(stage_dir),
"python", str(script_abs),
"--config", str(train_path),
*passthrough,
]

env = os.environ.copy()
env.pop("VIRTUAL_ENV", None)

typer.echo(f"Executing with uv isolated environment: {' '.join(cmd)}")
result = subprocess.run(cmd, env=env)
raise typer.Exit(result.returncode)

rc = execute_uv_local(
script_path=str(script_abs),
stage_dir=stage_dir,
repo_root=repo_root,
train_path=train_path,
passthrough=passthrough,
)
raise typer.Exit(rc)


def _execute_remote(
Expand Down
33 changes: 10 additions & 23 deletions src/nemotron/cli/commands/embed/prep.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,33 +100,20 @@ def _execute_prep(cfg: RecipeConfig, *, experiment=None):

def _execute_uv_local(train_path: Path, passthrough: list[str]) -> None:
"""Execute data prep locally via UV isolated environment."""
import os
import shutil
import subprocess

uv_cmd = shutil.which("uv")
if not uv_cmd:
typer.echo("Error: 'uv' command not found. Please install uv.", err=True)
raise typer.Exit(1)
from nemotron.kit.uv_local import execute_uv_local

script_abs = SPEC.script_path
stage_dir = script_abs.parent
repo_root = SPEC.script_path.parents[len(Path(SCRIPT_PATH).parts) - 1]
cmd = [
uv_cmd, "run",
"--with", str(repo_root),
"--project", str(stage_dir),
"python", str(script_abs),
"--config", str(train_path),
*passthrough,
]

env = os.environ.copy()
env.pop("VIRTUAL_ENV", None)

typer.echo(f"Executing with uv isolated environment: {' '.join(cmd)}")
result = subprocess.run(cmd, env=env)
raise typer.Exit(result.returncode)

rc = execute_uv_local(
script_path=str(script_abs),
stage_dir=stage_dir,
repo_root=repo_root,
train_path=train_path,
passthrough=passthrough,
)
raise typer.Exit(rc)


def _execute_remote(
Expand Down
33 changes: 10 additions & 23 deletions src/nemotron/cli/commands/embed/sdg.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,33 +107,20 @@ def _execute_sdg(cfg: RecipeConfig, *, experiment=None):

def _execute_uv_local(train_path: Path, passthrough: list[str]) -> None:
"""Execute SDG locally via UV isolated environment."""
import os
import shutil
import subprocess

uv_cmd = shutil.which("uv")
if not uv_cmd:
typer.echo("Error: 'uv' command not found. Please install uv.", err=True)
raise typer.Exit(1)
from nemotron.kit.uv_local import execute_uv_local

script_abs = SPEC.script_path
stage_dir = script_abs.parent
repo_root = SPEC.script_path.parents[len(Path(SCRIPT_PATH).parts) - 1]
cmd = [
uv_cmd, "run",
"--with", str(repo_root),
"--project", str(stage_dir),
"python", str(script_abs),
"--config", str(train_path),
*passthrough,
]

env = os.environ.copy()
env.pop("VIRTUAL_ENV", None)

typer.echo(f"Executing with uv isolated environment: {' '.join(cmd)}")
result = subprocess.run(cmd, env=env)
raise typer.Exit(result.returncode)

rc = execute_uv_local(
script_path=str(script_abs),
stage_dir=stage_dir,
repo_root=repo_root,
train_path=train_path,
passthrough=passthrough,
)
raise typer.Exit(rc)


def _execute_remote(
Expand Down
22 changes: 22 additions & 0 deletions src/nemotron/cli/commands/rerank/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Rerank CLI command group.

Exports the rerank_app typer group.
"""

from nemotron.cli.commands.rerank._typer_group import rerank_app

__all__ = ["rerank_app"]
87 changes: 87 additions & 0 deletions src/nemotron/cli/commands/rerank/_typer_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Rerank Typer group.

Contains the rerank command group with subcommands for cross-encoder
reranking model fine-tuning workflow:
- sdg: Generate synthetic Q&A pairs from documents
- prep: Prepare training data (convert, mine, unroll)
- finetune: Fine-tune the cross-encoder reranking model
- eval: Evaluate models on reranking metrics
- export: Export model to ONNX/TensorRT for optimized inference
- deploy: Deploy NIM container with custom model
"""

from __future__ import annotations

from rich.console import Console

from nemotron.cli.commands.rerank.sdg import META as SDG_META, sdg
from nemotron.cli.commands.rerank.prep import META as PREP_META, prep
from nemotron.cli.commands.rerank.finetune import META as FINETUNE_META, finetune
from nemotron.cli.commands.rerank.eval import META as EVAL_META, eval as eval_cmd
from nemotron.cli.commands.rerank.export import META as EXPORT_META, export
from nemotron.cli.commands.rerank.deploy import META as DEPLOY_META, deploy
from nemotron.cli.commands.rerank.run import run as run_cmd
from nemo_runspec.recipe_typer import RecipeTyper

console = Console()

# Create rerank app using RecipeTyper
rerank_app = RecipeTyper(
name="rerank",
help="Cross-encoder reranking model fine-tuning recipe",
no_args_is_help=True,
rich_markup_mode="rich",
)


@rerank_app.command(name="info")
def info() -> None:
"""Display rerank workspace information."""
console.print("[bold green]Rerank Workspace[/bold green]")
console.print(" Fine-tune cross-encoder reranking models for domain-adapted re-ranking.")
console.print()
console.print("[bold]Workflow Stages:[/bold]")
console.print(" 1. [cyan]sdg[/] - Generate synthetic Q&A pairs from documents")
console.print(" 2. [cyan]prep[/] - Prepare training data (convert, mine, unroll)")
console.print(" 3. [cyan]finetune[/] - Fine-tune the cross-encoder reranking model")
console.print(" 4. [cyan]eval[/] - Evaluate base vs fine-tuned rerankers")
console.print(" 5. [cyan]export[/] - Export model to ONNX/TensorRT")
console.print(" 6. [cyan]deploy[/] - Deploy NIM with custom model")
console.print()
console.print("[bold]Key Components:[/bold]")
console.print(" - retriever-sdg (synthetic data generation)")
console.print(" - Automodel (cross-encoder model training)")
console.print(" - BEIR (reranking evaluation framework)")
console.print()
console.print("[bold]Base Model:[/bold]")
console.print(" - nvidia/llama-nemotron-rerank-1b-v2")


# Register stage commands
rerank_app.add_recipe_command(sdg, meta=SDG_META, rich_help_panel="Data")
rerank_app.add_recipe_command(prep, meta=PREP_META, rich_help_panel="Data")
rerank_app.add_recipe_command(finetune, meta=FINETUNE_META, rich_help_panel="Training")
rerank_app.add_recipe_command(eval_cmd, meta=EVAL_META, rich_help_panel="Evaluation")
rerank_app.add_recipe_command(export, meta=EXPORT_META, rich_help_panel="Deployment")
rerank_app.add_recipe_command(deploy, meta=DEPLOY_META, rich_help_panel="Deployment")

# Register run (pipeline) command
rerank_app.command(
"run",
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
rich_help_panel="Pipeline",
)(run_cmd)
Loading