# Duplodocus CLI High-performance exact and fuzzy (MinHash) document deduplication tool, natively implemented in Rust for processing large-scale JSONL datasets.
## Table of Contents - [Overview](#overview) - [Theory/Primer](#theory) - [Installation](#installation) - [Quick Start](#quick-start) - [Deduplication Methods](#deduplication-methods) - [Examples](#examples) - [Configuration](#configuration) - [System Requirements](#system-requirements) ## Overview This tool provides four deduplication strategies optimized for different dataset sizes and requirements: | Method | Storage | Best For | |--------|---------|----------| | **Exact + Memory** | In-memory | Small datasets (<10GB), simple exact matching | | **Exact + Disk** | Disk-based | Large datasets, exact matching, distributed processing | | **MinHash + Memory** | In-memory | Small datasets (<10GB), fuzzy matching | | **MinHash + Disk** | Disk-based | Large datasets, fuzzy matching, distributed processing | ### Key Features - **Exact Deduplication**: Removes documents with identical content using fast hash-based matching - **Fuzzy Deduplication**: Identifies near-duplicates using MinHash LSH based on [Lee et al. 2021](https://arxiv.org/abs/2107.06499) - **Scalable**: Memory-based for simplicity or disk-based for datasets that don't fit in RAM - **Distributed**: Disk-based methods support parallel processing across multiple machines - **Flexible**: Annotate duplicates or remove them entirely ## Theory Some notes on the theory behind this tooling and some details about the internals are contained in the [primer](primer.md). ## Installation ### Prerequisites - Rust toolchain (1.70+) - Git ### AWS EC2 Setup (Optional) For large-scale processing on AWS i4i/i7i instances with NVMe drives: ```bash # Configure RAID0 array from NVMe drives sudo yum install mdadm -y sudo mdadm --create /dev/md0 --level=0 --raid-devices=8 \ /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1 /dev/nvme4n1 \ /dev/nvme5n1 /dev/nvme6n1 /dev/nvme7n1 /dev/nvme8n1 sudo mkfs.xfs /dev/md0 sudo mkdir /mnt/raid0 sudo mount /dev/md0 /mnt/raid0 sudo chown -R $USER /mnt/raid0 # Install build dependencies sudo yum install gcc cmake openssl-devel g++ htop git -y # Install s5cmd for fast S3 transfers wget https://github.com/peak/s5cmd/releases/download/v2.2.2/s5cmd_2.2.2_Linux-64bit.tar.gz tar -xvzf s5cmd_2.2.2_Linux-64bit.tar.gz sudo mv s5cmd /usr/local/bin ``` ### Build from Source ```bash # Install Rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source ~/.bashrc # Clone and build git clone git@github.com:allenai/duplodocus.git cd dedup-tool cargo build --release # Binary will be at: ./target/release/dedup-tool ``` ### Download Data (if using S3) ```bash # Configure AWS credentials aws configure # Download JSONL files s5cmd cp -sp s3://your-bucket/path/to/data/* /mnt/raid0/input_data/ ``` ## Quick Start ### Exact Deduplication (Small Dataset) Remove documents with identical content: ```bash cargo run --release -- exact-dedup-memory \ --input-dir /data/documents \ --output-dir /data/unique \ --text-key "content" ``` ### Fuzzy Deduplication (Small Dataset) Find and remove near-duplicates: ```bash cargo run --release -- minhash-memory \ --input-dir /data/documents \ --storage-dir /tmp/work \ --output-dir /data/deduped \ --text-key "text" \ --num-buckets 20 \ --bucket-size 5 \ --remove-duplicates true \ --cleanup-storage ``` ## Deduplication Methods ### Exact Deduplication #### Memory-Based (Simple) Best for datasets under 100GB. Processes everything in one pass: ```bash cargo run --release -- exact-dedup-memory \ --input-dir /data/docs \ --output-dir /data/unique \ --text-key "content" \ --annotate-key "duplicate_info" # Optional: annotate instead of remove ``` **Options:** - `--hash-key`: Use pre-computed hash field instead of hashing text - `--hash-bits`: Number of bits for hash (default: 128) - `--annotate-key`: Add duplicate metadata instead of removing documents #### Disk-Based (Distributed) For large datasets or distributed processing: **Step 1: Group documents by hash** ```bash cargo run --release -- exact-dedup-disk-group \ --input-dir /data/docs \ --storage-dir /scratch/work \ --hash-key "doc_hash" \ --num-bins 100 ``` **Step 2: Remove duplicates** ```bash cargo run --release -- exact-dedup-disk-prune \ --storage-dir /scratch/work \ --output-dir /data/unique \ --hash-key "doc_hash" ``` ### Fuzzy Deduplication (MinHash) #### Memory-Based (Simple) All-in-one fuzzy deduplication for smaller datasets: ```bash cargo run --release -- minhash-memory \ --input-dir /data/docs \ --storage-dir /tmp/work \ --output-dir /data/deduped \ --text-key "text" \ --num-buckets 20 \ --bucket-size 5 \ --ngram-size 5 \ --remove-duplicates true \ --cleanup-storage ``` **Key Parameters:** - `--num-buckets`: Number of LSH bands (more = stricter matching, default: 20) - `--bucket-size`: Hashes per band (more = stricter matching, default: 5) - `--ngram-size`: N-gram size for document shingling (default: 5) - `--tokenizer`: Options: "cl100k", "p50k", "uniseg", or character-level - `--config`: Optional YAML config file for all parameters #### Disk-Based (Distributed) For large-scale distributed processing across multiple machines: **Step 1: Build file map** (run once) ```bash cargo run --release -- mh-build-file-map \ --input-dir /data/docs \ --storage-dir /shared/work ``` **Step 2: Hash documents** (parallel across workers) ```bash # Worker 0 cargo run --release -- mh-hash-docs \ --local-input /data/docs \ --storage-dir /shared/work \ --text-key "text" \ --path-chunk 0 \ --num-path-chunks 10 \ --num-buckets 20 \ --bucket-size 5 # Worker 1 cargo run --release -- mh-hash-docs \ --local-input /data/docs \ --storage-dir /shared/work \ --text-key "text" \ --path-chunk 1 \ --num-path-chunks 10 \ --num-buckets 20 \ --bucket-size 5 # ... repeat for workers 2-9 ``` **Step 3: Gather edges** (run once, requires all signatures) ```bash cargo run --release -- mh-gather-edges \ --storage-dir /shared/work ``` **Step 4: Build Union-Find** (run once on single machine) ```bash cargo run --release -- mh-build-uf \ --storage-dir /shared/work \ --num-path-chunks 10 ``` **Step 5: Clean files** (parallel across workers) ```bash # Worker 0 cargo run --release -- mh-clean-files \ --input-dir /data/docs \ --storage-dir /shared/work \ --output-dir /data/deduped \ --path-chunk 0 \ --num-path-chunks 10 \ --remove-duplicates true # Repeat for other workers... ``` ## Examples Detailed examples with step-by-step instructions are available in the `examples/` directory: - `examples/exact_simple/` - Simple exact deduplication - `examples/exact_multi/` - Distributed exact deduplication - `examples/fuzzy_simple/` - Simple fuzzy deduplication - `examples/fuzzy_multi/` - Distributed fuzzy deduplication - `examples/essential/` - Essential patterns and best practices ## Configuration ### YAML Configuration (Optional) For complex setups, you can use a YAML config file: ```yaml # minhash_config.yaml minhash_params: num_buckets: 26 bucket_size: 11 ngram_size: 5 permutation_seed: 42 tokenizer: "cl100k_base" eng_params: num_docs: 1000000 max_lines_per_path: 100000 num_sig_chunks: 8 output_params: annotate: false annotate_key: metadata.minhash # minhash output data location remove_duplicates: true # just annotate, don't remove delete_while_cleaning: false ``` Use with: ```bash cargo run --release -- minhash-memory \ --input-dir /data/docs \ --storage-dir /tmp/work \ --output-dir /data/deduped \ --text-key "text" \ --config minhash_config.yaml ``` ## System Requirements ### Memory-Based Methods - RAM: Dataset size + 2-3GB overhead - Storage: Input size + output size - Best for: Datasets under 100GB ### Disk-Based Methods - RAM: ~8-16GB minimum - Storage: 3-5x input dataset size (for intermediate files) - Fast local storage strongly recommended (NVMe/SSD) - Best for: Datasets over 100GB or distributed processing ### Recommended Instances (AWS) - **Small jobs**: Any instance with enough memory to fit the dataset in RAM. - **Large jobs**: i4i.32xlarge or larger (NVMe storage) - **Distributed**: Multiple i4i.32xlarge instances ## Design Principles - **No remote I/O in Rust**: All S3 interaction happens outside Rust (use s5cmd, boto3, etc.) - **Fast local storage**: Assumes fast disk for intermediate files - **Small file assumption**: Individual JSONL files should fit in memory - **Unique basenames**: Input files must have unique basenames within input directory ## Performance Tips 1. **Use RAID0 for NVMe drives** on cloud instances for maximum I/O throughput 2. **Adjust `--num-path-chunks`** based on available workers 3. **Monitor disk space** - intermediate files can be 3-5x input size 4. **Use `--cleanup-storage`** carefully in distributed settings 5. **Set appropriate `--num-buckets` and `--bucket-size`** for your similarity threshold ## Troubleshooting **Out of memory errors**: Use disk-based methods instead of memory-based **Slow performance**: Ensure you're using fast local storage (NVMe/SSD), not network storage **Missing intermediate files**: Ensure all parallel steps complete before running sequential steps