Training + evaluation repo for sparse retrieval models, including:
The repo supports Hydra-driven training, sparse corpus encoding + inverted indexing, retrieval/reranking evaluation, NanoBEIR/MTEB proxy benchmarks, and model-creation utilities.
pip install -r requirements.txt
All entrypoints use Hydra configs under config/.
model=splade_v2_doc, dataset=beir/trec-covid,
training=splade_v2.training.use_cpu=true,
training.resume_checkpoint_path=..., testing.checkpoint_path=...,
encoding.checkpoint_path=....log/.../no_tag by default; set tag=... to replace no_tag.Datasets default to the Hugging Face Hub; dataset configs live in
config/dataset/.
Common entrypoints:
script/train.py: trainingscript/encode.py: sparse corpus encodingscript/index.py: inverted-index buildscript/evaluate.py: retrieval, reranking, and benchmark dispatchscript/model_creation/lens/*.py: LENS artifact build pipelinedoc/analysis/refactor_architecture.mddoc/analysis/migration_notes.mddoc/analysis/command_cookbook.mddoc/analysis/debug_runbook.mddoc/analysis/baseline_command_matrix.mddoc/embeddinggemma_lsr_build_pipeline.mdBuild the default 4k-cluster LENS artifacts:
python script/model_creation/lens/build_pipeline.py \
--config config/model_creation/lens/pipeline_4k.yaml
Build the 8k-cluster variant:
python script/model_creation/lens/build_pipeline.py \
--config config/model_creation/lens/pipeline_8k.yaml
The pipeline prepares a self-contained Hugging Face backbone with LENS special
tokens (<instruct>, <query>, <response>) and then writes a clustered
splade_compact_head.pt. The default outputs are:
outputs/model_creation/lens/hf_backboneoutputs/model_creation/lens/mistral_cluster4koutputs/model_creation/lens/mistral_cluster8kThe bundled LENS training configs expect the clustered artifact directories
above unless you override model.huggingface_name.
Train a SPLADE model and write checkpoints/logs:
python script/train.py training=splade_v1 model=splade_v1
Train the default LENS setup:
python script/train.py --config-name train_lens_mistral
train_lens_mistral uses:
model=lens_mistral_cluster4ktraining=lens_mistralSwitch to the 8k clustered head with:
python script/train.py --config-name train_lens_mistral \
model=lens_mistral_cluster8k
The default LENS presets use bidirectional Mistral, max-pooling over activated LM logits, and LoRA/PEFT adapters.
Use MS MARCO (HF Hub) for both train/val:
python script/train.py \
dataset@train_dataset=msmarco \
dataset@val_dataset=msmarco
Train with triplet meta rows + scored hard negatives (margin-MSE distillation):
python script/train.py \
training=splade_v2_distill \
model=splade_v2_hf \
dataset@train_dataset=msmarco_triplet_scores \
dataset@val_dataset=msmarco
Train with MSMARCO dev hard negatives for reranking-style validation
(antoinelouis/msmarco-dev-small-negatives) while keeping NanoBEIR enabled:
python script/train.py \
--config-name train_devneg
Train SPLADE v2++ with google/embeddinggemma-300m as backbone
(requires gated-model access and HF_TOKEN in .env):
python script/train.py \
model=splade_v2_pp_embeddinggemma_300m \
training=splade_v2_pp
Build an English-focused pruned EmbeddingGemma backbone (drops non-English vocabulary tokens and image-special tokens):
python script/preprocess/prune_embeddinggemma_vocab.py \
--input-model google/embeddinggemma-300m \
--output-dir data/model/trained_embeddinggemma_300m_pruned \
--overwrite
Train SPLADE v2++ with the pruned EmbeddingGemma backbone:
python script/train.py \
model=splade_v2_pp_embeddinggemma_300m_pruned \
training=splade_v2_pp
Train directly from scored distillation rows (no triplet join):
python script/train.py \
training=splade_v2_distill \
dataset@train_dataset=msmarco_distill_scores \
dataset@val_dataset=msmarco
Train the fixed-vocabulary pretrained diffusion-backbone ablation with UDLM
(kuleshov-group/udlm-lm1b):
python script/train.py --config-name train_pretrained_diffusion_splade
Train the paired UDLM + MDLM-aux arm:
python script/train.py --config-name train_pretrained_diffusion_mdlm_splade
These UDLM presets pin query/document length to 128 to match the upstream
checkpoint context length. The Hugging Face checkpoint also uses
trust_remote_code and currently imports einops and flash_attn, so those
packages must be available in the runtime environment.
Train directly from a multi-teacher score dataset
(teacher_scores + per-model columns):
python script/train.py \
training=splade_v2_distill \
dataset@train_dataset=msmarco_multi_teacher_scores \
dataset@val_dataset=msmarco
Override the scored dataset or number of negatives per query:
python script/train.py \
dataset@train_dataset=msmarco_triplet_scores \
train_dataset.score_hf_name=Hyukkyu/msmarco-spladev2-hard-negatives-scores \
train_dataset.score_negatives_per_query=1
Disable MLflow logging for quick debug runs (optional):
python script/train.py tag=debug
Train with explicit MLflow tracking settings:
python script/train.py \
training.mlflow.tracking_uri=https://mlflow.hyukkyu.com
MLflow GPU system metrics use NVML via nvidia-ml-py. If GPU utilization or
memory metrics are missing, refresh the environment with pip install -r requirements.txt.
Resume an interrupted training run from a Lightning checkpoint:
python script/train.py \
training.resume_checkpoint_path=log/train/splade_v2/no_tag/checkpoints/last.ckpt
Checkpoint options for training:
training.init_checkpoint_path: initialize model weights only (fresh optimizer,
scheduler, and global step).training.resume_checkpoint_path: resume full Lightning state (model + optimizer
Encode documents into sparse shards (for retrieval indexing):
python script/encode.py \
encoding.checkpoint_path=log/train/splade_v2/no_tag/checkpoints/last.ckpt \
dataset=beir/trec-covid \
encoding.encode_dir=log/encode
For clustered LENS artifacts, encoded dimensions are latent cluster ids rather than tokenizer ids. Encode and index manifests record that output-space alignment automatically.
Build a sparse inverted index from encoded shards:
python script/index.py \
encoding.encode_dir=log/encode \
encoding.index_dir=log/index
Index scoring supports testing.scoring_method=full|wand|bmw. Block-Max WAND
(bmw) requires bounds stored in the index. If you enable bmw or change
encoding.wand_block_size, rebuild the index with script/index.py.
Index-based retrieval evaluation:
python script/evaluate.py \
evaluation.type=retrieval \
testing.checkpoint_path=log/train/splade_v2/no_tag/checkpoints/last.ckpt \
dataset=beir/trec-covid \
encoding.index_dir=log/index
Reranking evaluation (no index required):
python script/evaluate.py \
evaluation.type=reranking \
testing.checkpoint_path=log/train/splade_v2/no_tag/checkpoints/last.ckpt \
dataset=beir/msmarco
Quick proxy evaluation without full-corpus encoding:
python script/evaluate.py --benchmark nanobeir \
testing.checkpoint_path=log/train/splade_v2/no_tag/checkpoints/last.ckpt \
nanobeir.datasets='[msmarco, nfcorpus, nq]' \
nanobeir.save_json=true
Run the shared sparse benchmark path through the MTEB entrypoint:
python script/evaluate.py --benchmark mteb \
--config-name evaluate_mteb \
testing.checkpoint_path=log/train/splade_v2/no_tag/checkpoints/last.ckpt
Use HF weights instead of a checkpoint:
python script/evaluate.py --benchmark nanobeir \
nanobeir.use_huggingface_model=true \
model.huggingface_name=naver/splade_v2
Evaluate a LENS checkpoint on the native sparse benchmark adapter:
python script/evaluate.py --benchmark nanobeir \
--config-name evaluate_nanobeir \
model=lens_mistral_cluster4k \
testing.checkpoint_path=log/train/lens_mistral_cluster4k/no_tag/checkpoints/last.ckpt
Benchmark routing is backend-aware:
This repo also supports patent document retrieval evaluation with:
Hyukkyu/patent-us-smallHyukkyu/patent-us-corpus-smallThe label dataset stores document-to-document relevance pairs:
question_id: patent document id used as the query document idlabel_id: list of relevant patent document idsQueries are reconstructed by looking up each question_id in the patent corpus
and rendering the same document text template used for corpus encoding:
Title: {title}
Abstract: {abstract}
Claims: {claims}
Description: {description}
The checked-in patent_us_corpus_small and patent_us_small_eval presets
expect the small patent corpus parquet shards under
.cache/hf/patent-us-corpus-small/data/*.parquet. The artifact builder and
dataset loader both use HF_TOKEN / HUGGINGFACE_HUB_TOKEN from .env when
accessing Hugging Face datasets.
Build local queries.parquet and qrels.parquet artifacts for the test split:
python script/preprocess/patent/build_patent_us_eval_artifacts.py \
--benchmark-repo Hyukkyu/patent-us-small \
--benchmark-split test \
--corpus-repo Hyukkyu/patent-us-corpus-small \
--corpus-split train \
--output-dir data/eval/patent_us_small
Two long-document encoding modes are supported for learned sparse models such as SPLADE:
encoding.long_doc_strategy=truncate: encode only the first max_doc_length
tokens from the templated patent textencoding.long_doc_strategy=sliding_window: encode the full templated patent
text in non-overlapping windows and aggregate the windowsUse the same mode on both the corpus side and the query side.
Encode the patent corpus with naver/splade-v3 in truncate mode:
python script/encode.py \
model=splade_v3_naver \
dataset=patent_us_corpus_small \
tag=patent_us_small_splade_v3_truncate \
encoding.batch_size=160 \
encoding.max_windows_per_forward=160
Encode the same corpus in sliding_window mode:
python script/encode.py \
model=splade_v3_naver \
dataset=patent_us_corpus_small \
tag=patent_us_small_splade_v3_sliding \
encoding.batch_size=160 \
encoding.long_doc_strategy=sliding_window \
encoding.max_windows_per_forward=160
The current encode defaults already include the optimized settings used for the patent corpus benchmarks:
encoding.prefetch_factor=4encoding.torch_compile=trueencoding.torch_compile_mode=defaultencoding.torch_compile_ddp_safe_mode=falseencoding.distributed_sampler_strategy=row_group_interleavedBuild the inverted index from the encoded shards:
python script/index.py \
model=splade_v3_naver \
dataset=patent_us_corpus_small \
tag=patent_us_small_splade_v3_truncate
Evaluate on the test split of Hyukkyu/patent-us-small:
python script/evaluation.py \
model=splade_v3_naver \
dataset=patent_us_small_eval \
testing=patent_us_small_eval \
tag=patent_us_small_splade_v3_truncate \
dataset.query_long_doc_strategy=truncate
For sliding_window, switch both sides together:
python script/evaluation.py \
model=splade_v3_naver \
dataset=patent_us_small_eval \
testing=patent_us_small_eval \
tag=patent_us_small_splade_v3_sliding \
dataset.query_long_doc_strategy=sliding_window
The testing=patent_us_small_eval preset reports only retrieval metrics for:
MRR@1MRR@5MRR@10MRR@16MRR@32MRR@63MRR@150MRR@300Recall@1Recall@5Recall@10Recall@16Recall@32Recall@63Recall@150Recall@300It also enables testing.exclude_self_match=true, which is important because
each query is itself a patent document from the corpus.
If you want to evaluate a trained checkpoint instead of raw Hugging Face model weights, pass the same checkpoint to both steps:
encoding.checkpoint_path=/path/to/checkpoint.ckpttesting.checkpoint_path=/path/to/checkpoint.ckptMine hard negatives with a trained checkpoint:
python script/preprocess/mine_hard_negative.py \
mining.checkpoint_path=log/train/splade_v2/no_tag/checkpoints/last.ckpt \
mining.output_dir=data/hard_negatives \
mining.output_format=triplet
Score candidate pairs with a cross-encoder:
python script/preprocess/mine_distillation_score.py \
dataset@score_dataset=msmarco \
scoring.model_name=cross-encoder/ms-marco-MiniLM-L-12-v2
Score MS MARCO hard negatives (merge all neg keys):
python script/preprocess/mine_distillation_score.py \
--config-name score_cross_encoder_msmarco_hard_negatives \
scoring.max_rows=10
The source-of-truth ANNA implementation now lives under:
src/tokenization/anna_tokenizer.py
src/preprocess/anna_conversion_utils.py
The files under script/preprocess/anna/ remain compatibility wrappers for the
older script layout.
Convert ANNA checkpoints to Hugging Face artifacts with:
python script/preprocess/anna/convert_to_hf.py \
--input-dir data/model/anna \
--output-dir data/model/anna_large_hf \
--overwrite
Backwards-compatible wrapper (same behavior):
python script/preprocess/convert_anna_to_hf.py --help
Build the Rust-backed ANNA tokenizer extension (Linux):
cd script/preprocess/anna/native/anna_fast_rs
maturin build --release -i python
pip install --force-reinstall target/wheels/*.whl
Quick throughput benchmark (slow vs fast):
python script/experiment/benchmark_anna_tokenizer_speed.py \
--model-dir data/model/anna_large_hf \
--local-files-only
Runtime code loads tokenizer/model through Hugging Face standard APIs
(AutoTokenizer.from_pretrained, AutoModelForMaskedLM.from_pretrained).
Run the end-to-end CPU smoke path before the first real LENS training run:
python script/etc/lens_cpu_smoke.py \
--output-dir outputs/smoke/lens_cpu_smoke \
--cluster-count 8
This builds a tiny local Mistral artifact, runs the LENS backbone + clustered head pipeline, checks query/doc encoding, and executes a one-step train/validation loop on CPU.
This pipeline uses sentence-transformers/msmarco-hard-negatives, builds a
balanced 100-negatives/query dataset, scores 5 cross-encoders, normalizes and
rescales scores, trims to 8 negatives per query, and uploads the final dataset
to Hyukkyu/msmarco-spladev3-scores. Training then uses existing configs.
python script/preprocess/extract_hard_negatives.py \
--config-name extract_hard_negatives
export RANKT53B_CHECKPOINT_PATH=/path/to/trecdl22-crossencoder-rankT53b-repro/pytorch_model.bin
python script/preprocess/score_cross_encoder_ensemble.py \
--config-name score_cross_encoder_ensemble
train split with 5 score
columns + teacher_scores) and optionally upload to HF:export HF_TOKEN=...
python script/preprocess/build_multi_teacher_scores_dataset.py \
--config-name build_multi_teacher_scores_dataset \
upload.enabled=true \
upload.repo_id=YOUR_USER/msmarco-hardneg-100-multi-teacher-scores
export HF_TOKEN=...
python script/preprocess/finalize_distill_dataset.py \
--config-name finalize_distill_dataset
python script/train.py --config-name train_splade_v3 \
training.init_checkpoint_path=/path/to/splade_pp_selfdistil.ckpt
Use training.init_checkpoint_path here for warm-starting weights. Use
training.resume_checkpoint_path=/path/to/last.ckpt instead when you need to
continue an interrupted run.
Logit distribution experiment (writes JSON + PNG):
python script/experiment/logit_stats.py --output_dir script/experiment/output
GPU burn utility:
python script/etc/gpu_burn.py --devices all --dtype float16
docker build -f docker/Dockerfile -t splade-repro .
docker run --gpus all -v "$PWD:/workspace" -it splade-repro bash
training.regularization.paper_faithful=true|falsetraining=splade_v1|splade_v2|splade_v2_doctraining.distill.enabled=trueencoding.torch_compile=true (compiles encoder + sparsify core)Training + evaluation repo for sparse retrieval models, including:
The repo supports Hydra-driven training, sparse corpus encoding + inverted indexing, retrieval/reranking evaluation, NanoBEIR/MTEB proxy benchmarks, and model-creation utilities.
pip install -r requirements.txt
All entrypoints use Hydra configs under config/.
model=splade_v2_doc, dataset=beir/trec-covid,
training=splade_v2.training.use_cpu=true,
training.resume_checkpoint_path=..., testing.checkpoint_path=...,
encoding.checkpoint_path=....log/.../no_tag by default; set tag=... to replace no_tag.Datasets default to the Hugging Face Hub; dataset configs live in
config/dataset/.
Common entrypoints:
script/train.py: trainingscript/encode.py: sparse corpus encodingscript/index.py: inverted-index buildscript/evaluate.py: retrieval, reranking, and benchmark dispatchscript/model_creation/lens/*.py: LENS artifact build pipelinedoc/analysis/refactor_architecture.mddoc/analysis/migration_notes.mddoc/analysis/command_cookbook.mddoc/analysis/debug_runbook.mddoc/analysis/baseline_command_matrix.mddoc/embeddinggemma_lsr_build_pipeline.mdBuild the default 4k-cluster LENS artifacts:
python script/model_creation/lens/build_pipeline.py \
--config config/model_creation/lens/pipeline_4k.yaml
Build the 8k-cluster variant:
python script/model_creation/lens/build_pipeline.py \
--config config/model_creation/lens/pipeline_8k.yaml
The pipeline prepares a self-contained Hugging Face backbone with LENS special
tokens (<instruct>, <query>, <response>) and then writes a clustered
splade_compact_head.pt. The default outputs are:
outputs/model_creation/lens/hf_backboneoutputs/model_creation/lens/mistral_cluster4koutputs/model_creation/lens/mistral_cluster8kThe bundled LENS training configs expect the clustered artifact directories
above unless you override model.huggingface_name.
Train a SPLADE model and write checkpoints/logs:
python script/train.py training=splade_v1 model=splade_v1
Train the default LENS setup:
python script/train.py --config-name train_lens_mistral
train_lens_mistral uses:
model=lens_mistral_cluster4ktraining=lens_mistralSwitch to the 8k clustered head with:
python script/train.py --config-name train_lens_mistral \
model=lens_mistral_cluster8k
The default LENS presets use bidirectional Mistral, max-pooling over activated LM logits, and LoRA/PEFT adapters.
Use MS MARCO (HF Hub) for both train/val:
python script/train.py \
dataset@train_dataset=msmarco \
dataset@val_dataset=msmarco
Train with triplet meta rows + scored hard negatives (margin-MSE distillation):
python script/train.py \
training=splade_v2_distill \
model=splade_v2_hf \
dataset@train_dataset=msmarco_triplet_scores \
dataset@val_dataset=msmarco
Train with MSMARCO dev hard negatives for reranking-style validation
(antoinelouis/msmarco-dev-small-negatives) while keeping NanoBEIR enabled:
python script/train.py \
--config-name train_devneg
Train SPLADE v2++ with google/embeddinggemma-300m as backbone
(requires gated-model access and HF_TOKEN in .env):
python script/train.py \
model=splade_v2_pp_embeddinggemma_300m \
training=splade_v2_pp
Build an English-focused pruned EmbeddingGemma backbone (drops non-English vocabulary tokens and image-special tokens):
python script/preprocess/prune_embeddinggemma_vocab.py \
--input-model google/embeddinggemma-300m \
--output-dir data/model/trained_embeddinggemma_300m_pruned \
--overwrite
Train SPLADE v2++ with the pruned EmbeddingGemma backbone:
python script/train.py \
model=splade_v2_pp_embeddinggemma_300m_pruned \
training=splade_v2_pp
Train directly from scored distillation rows (no triplet join):
python script/train.py \
training=splade_v2_distill \
dataset@train_dataset=msmarco_distill_scores \
dataset@val_dataset=msmarco
Train the fixed-vocabulary pretrained diffusion-backbone ablation with UDLM
(kuleshov-group/udlm-lm1b):
python script/train.py --config-name train_pretrained_diffusion_splade
Train the paired UDLM + MDLM-aux arm:
python script/train.py --config-name train_pretrained_diffusion_mdlm_splade
These UDLM presets pin query/document length to 128 to match the upstream
checkpoint context length. The Hugging Face checkpoint also uses
trust_remote_code and currently imports einops and flash_attn, so those
packages must be available in the runtime environment.
Train directly from a multi-teacher score dataset
(teacher_scores + per-model columns):
python script/train.py \
training=splade_v2_distill \
dataset@train_dataset=msmarco_multi_teacher_scores \
dataset@val_dataset=msmarco
Override the scored dataset or number of negatives per query:
python script/train.py \
dataset@train_dataset=msmarco_triplet_scores \
train_dataset.score_hf_name=Hyukkyu/msmarco-spladev2-hard-negatives-scores \
train_dataset.score_negatives_per_query=1
Disable MLflow logging for quick debug runs (optional):
python script/train.py tag=debug
Train with explicit MLflow tracking settings:
python script/train.py \
training.mlflow.tracking_uri=https://mlflow.hyukkyu.com
MLflow GPU system metrics use NVML via nvidia-ml-py. If GPU utilization or
memory metrics are missing, refresh the environment with pip install -r requirements.txt.
Resume an interrupted training run from a Lightning checkpoint:
python script/train.py \
training.resume_checkpoint_path=log/train/splade_v2/no_tag/checkpoints/last.ckpt
Checkpoint options for training:
training.init_checkpoint_path: initialize model weights only (fresh optimizer,
scheduler, and global step).training.resume_checkpoint_path: resume full Lightning state (model + optimizer
Encode documents into sparse shards (for retrieval indexing):
python script/encode.py \
encoding.checkpoint_path=log/train/splade_v2/no_tag/checkpoints/last.ckpt \
dataset=beir/trec-covid \
encoding.encode_dir=log/encode
For clustered LENS artifacts, encoded dimensions are latent cluster ids rather than tokenizer ids. Encode and index manifests record that output-space alignment automatically.
Build a sparse inverted index from encoded shards:
python script/index.py \
encoding.encode_dir=log/encode \
encoding.index_dir=log/index
Index scoring supports testing.scoring_method=full|wand|bmw. Block-Max WAND
(bmw) requires bounds stored in the index. If you enable bmw or change
encoding.wand_block_size, rebuild the index with script/index.py.
Index-based retrieval evaluation:
python script/evaluate.py \
evaluation.type=retrieval \
testing.checkpoint_path=log/train/splade_v2/no_tag/checkpoints/last.ckpt \
dataset=beir/trec-covid \
encoding.index_dir=log/index
Reranking evaluation (no index required):
python script/evaluate.py \
evaluation.type=reranking \
testing.checkpoint_path=log/train/splade_v2/no_tag/checkpoints/last.ckpt \
dataset=beir/msmarco
Quick proxy evaluation without full-corpus encoding:
python script/evaluate.py --benchmark nanobeir \
testing.checkpoint_path=log/train/splade_v2/no_tag/checkpoints/last.ckpt \
nanobeir.datasets='[msmarco, nfcorpus, nq]' \
nanobeir.save_json=true
Run the shared sparse benchmark path through the MTEB entrypoint:
python script/evaluate.py --benchmark mteb \
--config-name evaluate_mteb \
testing.checkpoint_path=log/train/splade_v2/no_tag/checkpoints/last.ckpt
Use HF weights instead of a checkpoint:
python script/evaluate.py --benchmark nanobeir \
nanobeir.use_huggingface_model=true \
model.huggingface_name=naver/splade_v2
Evaluate a LENS checkpoint on the native sparse benchmark adapter:
python script/evaluate.py --benchmark nanobeir \
--config-name evaluate_nanobeir \
model=lens_mistral_cluster4k \
testing.checkpoint_path=log/train/lens_mistral_cluster4k/no_tag/checkpoints/last.ckpt
Benchmark routing is backend-aware:
This repo also supports patent document retrieval evaluation with:
Hyukkyu/patent-us-smallHyukkyu/patent-us-corpus-smallThe label dataset stores document-to-document relevance pairs:
question_id: patent document id used as the query document idlabel_id: list of relevant patent document idsQueries are reconstructed by looking up each question_id in the patent corpus
and rendering the same document text template used for corpus encoding:
Title: {title}
Abstract: {abstract}
Claims: {claims}
Description: {description}
The checked-in patent_us_corpus_small and patent_us_small_eval presets
expect the small patent corpus parquet shards under
.cache/hf/patent-us-corpus-small/data/*.parquet. The artifact builder and
dataset loader both use HF_TOKEN / HUGGINGFACE_HUB_TOKEN from .env when
accessing Hugging Face datasets.
Build local queries.parquet and qrels.parquet artifacts for the test split:
python script/preprocess/patent/build_patent_us_eval_artifacts.py \
--benchmark-repo Hyukkyu/patent-us-small \
--benchmark-split test \
--corpus-repo Hyukkyu/patent-us-corpus-small \
--corpus-split train \
--output-dir data/eval/patent_us_small
Two long-document encoding modes are supported for learned sparse models such as SPLADE:
encoding.long_doc_strategy=truncate: encode only the first max_doc_length
tokens from the templated patent textencoding.long_doc_strategy=sliding_window: encode the full templated patent
text in non-overlapping windows and aggregate the windowsUse the same mode on both the corpus side and the query side.
Encode the patent corpus with naver/splade-v3 in truncate mode:
python script/encode.py \
model=splade_v3_naver \
dataset=patent_us_corpus_small \
tag=patent_us_small_splade_v3_truncate \
encoding.batch_size=160 \
encoding.max_windows_per_forward=160
Encode the same corpus in sliding_window mode:
python script/encode.py \
model=splade_v3_naver \
dataset=patent_us_corpus_small \
tag=patent_us_small_splade_v3_sliding \
encoding.batch_size=160 \
encoding.long_doc_strategy=sliding_window \
encoding.max_windows_per_forward=160
The current encode defaults already include the optimized settings used for the patent corpus benchmarks:
encoding.prefetch_factor=4encoding.torch_compile=trueencoding.torch_compile_mode=defaultencoding.torch_compile_ddp_safe_mode=falseencoding.distributed_sampler_strategy=row_group_interleavedBuild the inverted index from the encoded shards:
python script/index.py \
model=splade_v3_naver \
dataset=patent_us_corpus_small \
tag=patent_us_small_splade_v3_truncate
Evaluate on the test split of Hyukkyu/patent-us-small:
python script/evaluation.py \
model=splade_v3_naver \
dataset=patent_us_small_eval \
testing=patent_us_small_eval \
tag=patent_us_small_splade_v3_truncate \
dataset.query_long_doc_strategy=truncate
For sliding_window, switch both sides together:
python script/evaluation.py \
model=splade_v3_naver \
dataset=patent_us_small_eval \
testing=patent_us_small_eval \
tag=patent_us_small_splade_v3_sliding \
dataset.query_long_doc_strategy=sliding_window
The testing=patent_us_small_eval preset reports only retrieval metrics for:
MRR@1MRR@5MRR@10MRR@16MRR@32MRR@63MRR@150MRR@300Recall@1Recall@5Recall@10Recall@16Recall@32Recall@63Recall@150Recall@300It also enables testing.exclude_self_match=true, which is important because
each query is itself a patent document from the corpus.
If you want to evaluate a trained checkpoint instead of raw Hugging Face model weights, pass the same checkpoint to both steps:
encoding.checkpoint_path=/path/to/checkpoint.ckpttesting.checkpoint_path=/path/to/checkpoint.ckptMine hard negatives with a trained checkpoint:
python script/preprocess/mine_hard_negative.py \
mining.checkpoint_path=log/train/splade_v2/no_tag/checkpoints/last.ckpt \
mining.output_dir=data/hard_negatives \
mining.output_format=triplet
Score candidate pairs with a cross-encoder:
python script/preprocess/mine_distillation_score.py \
dataset@score_dataset=msmarco \
scoring.model_name=cross-encoder/ms-marco-MiniLM-L-12-v2
Score MS MARCO hard negatives (merge all neg keys):
python script/preprocess/mine_distillation_score.py \
--config-name score_cross_encoder_msmarco_hard_negatives \
scoring.max_rows=10
The source-of-truth ANNA implementation now lives under:
src/tokenization/anna_tokenizer.py
src/preprocess/anna_conversion_utils.py
The files under script/preprocess/anna/ remain compatibility wrappers for the
older script layout.
Convert ANNA checkpoints to Hugging Face artifacts with:
python script/preprocess/anna/convert_to_hf.py \
--input-dir data/model/anna \
--output-dir data/model/anna_large_hf \
--overwrite
Backwards-compatible wrapper (same behavior):
python script/preprocess/convert_anna_to_hf.py --help
Build the Rust-backed ANNA tokenizer extension (Linux):
cd script/preprocess/anna/native/anna_fast_rs
maturin build --release -i python
pip install --force-reinstall target/wheels/*.whl
Quick throughput benchmark (slow vs fast):
python script/experiment/benchmark_anna_tokenizer_speed.py \
--model-dir data/model/anna_large_hf \
--local-files-only
Runtime code loads tokenizer/model through Hugging Face standard APIs
(AutoTokenizer.from_pretrained, AutoModelForMaskedLM.from_pretrained).
Run the end-to-end CPU smoke path before the first real LENS training run:
python script/etc/lens_cpu_smoke.py \
--output-dir outputs/smoke/lens_cpu_smoke \
--cluster-count 8
This builds a tiny local Mistral artifact, runs the LENS backbone + clustered head pipeline, checks query/doc encoding, and executes a one-step train/validation loop on CPU.
This pipeline uses sentence-transformers/msmarco-hard-negatives, builds a
balanced 100-negatives/query dataset, scores 5 cross-encoders, normalizes and
rescales scores, trims to 8 negatives per query, and uploads the final dataset
to Hyukkyu/msmarco-spladev3-scores. Training then uses existing configs.
python script/preprocess/extract_hard_negatives.py \
--config-name extract_hard_negatives
export RANKT53B_CHECKPOINT_PATH=/path/to/trecdl22-crossencoder-rankT53b-repro/pytorch_model.bin
python script/preprocess/score_cross_encoder_ensemble.py \
--config-name score_cross_encoder_ensemble
train split with 5 score
columns + teacher_scores) and optionally upload to HF:export HF_TOKEN=...
python script/preprocess/build_multi_teacher_scores_dataset.py \
--config-name build_multi_teacher_scores_dataset \
upload.enabled=true \
upload.repo_id=YOUR_USER/msmarco-hardneg-100-multi-teacher-scores
export HF_TOKEN=...
python script/preprocess/finalize_distill_dataset.py \
--config-name finalize_distill_dataset
python script/train.py --config-name train_splade_v3 \
training.init_checkpoint_path=/path/to/splade_pp_selfdistil.ckpt
Use training.init_checkpoint_path here for warm-starting weights. Use
training.resume_checkpoint_path=/path/to/last.ckpt instead when you need to
continue an interrupted run.
Logit distribution experiment (writes JSON + PNG):
python script/experiment/logit_stats.py --output_dir script/experiment/output
GPU burn utility:
python script/etc/gpu_burn.py --devices all --dtype float16
docker build -f docker/Dockerfile -t splade-repro .
docker run --gpus all -v "$PWD:/workspace" -it splade-repro bash
training.regularization.paper_faithful=true|falsetraining=splade_v1|splade_v2|splade_v2_doctraining.distill.enabled=trueencoding.torch_compile=true (compiles encoder + sparsify core)