Foundry/dedup
David Herrera 775df8ab31 Initial public release 2026-10-19 13:24:09 -05:00
..
README.md Initial public release 2026-10-19 13:24:09 -05:00
dedup.h Initial public release 2026-10-19 13:24:09 -05:00

README.md

Dedup

ASIC-accelerated training data deduplication using MinHash/LSH.

Why This Module?

Every major LLM training pipeline deduplicates data:

  • Removes exact duplicates (wasted compute)
  • Removes near-duplicates (memorization, overfitting)
  • Improves model quality
  • Reduces training cost

Current approaches use GPU clusters. We use $30 ASIC miners.

How MinHash Deduplication Works

Document: "the quick brown fox jumps over the lazy dog"
    │
    ▼
┌─────────────────────────────────────────┐
│  1. Shingling (create n-grams)          │
│     "the quick brown"                   │
│     "quick brown fox"                   │
│     "brown fox jumps"                   │
│     ... (k shingles)                    │
└─────────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────────┐
│  2. MinHash (for each permutation i)    │  ← ASIC accelerates this
│     sig[i] = min(hash(shingle, i)       │
│              for shingle in shingles)   │
│                                         │
│     Result: 128-dimensional signature   │
└─────────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────────┐
│  3. LSH Bucketing                       │
│     Split signature into b bands        │
│     Hash each band → bucket             │
│     Documents in same bucket = candidates│
└─────────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────────┐
│  4. Candidate Verification              │
│     For each candidate pair:            │
│     Jaccard = |A ∩ B| / |A ∪ B|        │
│     If Jaccard > threshold → duplicate  │
└─────────────────────────────────────────┘

Why ASIC Helps

The bottleneck is step 2: computing MinHash signatures.

For each document:

  • ~1000 shingles (13-grams)
  • × 128 permutations
  • = 128,000 hashes per document

For Common Crawl (3B documents):

  • 3B × 128K = 384 trillion hashes
Hardware Hashes/sec Time for 384T hashes
CPU (1 core) 10M 1.2 years
CPU (100 cores) 1B 4.4 days
GPU cluster 100B 1 hour
ASIC array (100×) 1T+ 6 minutes

API Overview

Types

fd_shingle      // Single n-gram shingle
fd_signature    // MinHash signature (128+ dimensions)
fd_index        // LSH index for candidate detection
fd_pipeline     // Complete dedup pipeline
fd_result       // Deduplication results
fd_config       // Configuration

Core Functions

// Shingling
fd_shingle* fd_shingle_text(const char *text, int n, size_t *count);

// MinHash signature
fd_signature* fd_minhash(fd_shingle *shingles, size_t n, fd_config *config);

// LSH indexing
int fd_index_add(fd_index *idx, uint64_t doc_id, fd_signature *sig);
fd_candidates* fd_index_query(fd_index *idx, fd_signature *sig);

// Full pipeline
fd_pipeline* fd_pipeline_new(fd_config *config);
int fd_pipeline_add_file(fd_pipeline *pipe, const char *path);
fd_result* fd_pipeline_run(fd_pipeline *pipe);

// ASIC backend
void fd_pipeline_set_backend(fd_pipeline *pipe, asic_device *dev);

Usage

Basic Deduplication

#include "dedup/dedup.h"
#include "../lib/asic/asic.h"

int main() {
    // Open ASIC (falls back to CPU if unavailable)
    asic_device *dev = asic_open_auto();

    // Configure pipeline
    fd_config config = fd_config_default();
    config.shingle_size = 13;           // 13-character n-grams
    config.num_permutations = 128;      // MinHash dimensions
    config.num_bands = 8;               // LSH bands
    config.similarity_threshold = 0.8;  // Jaccard threshold

    // Create pipeline
    fd_pipeline *pipe = fd_pipeline_new(&config);
    fd_pipeline_set_backend(pipe, dev);

    // Add input files (JSONL format)
    fd_pipeline_add_file(pipe, "data/crawl-001.jsonl");
    fd_pipeline_add_file(pipe, "data/crawl-002.jsonl");

    // Run deduplication
    fd_result *result = fd_pipeline_run(pipe);

    // Print results
    printf("Documents processed: %zu\n", result->total_docs);
    printf("Duplicates found:    %zu\n", result->duplicates);
    printf("Unique documents:    %zu\n", result->unique);
    printf("Total hashes:        %zu\n", result->hashes_computed);
    printf("Throughput:          %.2f docs/sec\n", result->docs_per_sec);
    printf("Hash throughput:     %.2f MH/sec\n", result->hashes_per_sec / 1e6);

    // Write deduplicated output
    fd_pipeline_write_output(pipe, "data/clean.jsonl");

    // Cleanup
    fd_result_free(result);
    fd_pipeline_free(pipe);
    asic_close(dev);

    return 0;
}

Streaming Large Datasets

// For datasets that don't fit in memory
fd_config config = fd_config_default();
config.streaming = true;
config.batch_size = 10000;  // Process 10K docs at a time
config.index_path = "/tmp/dedup-index";  // Memory-mapped index

fd_pipeline *pipe = fd_pipeline_new(&config);

// Add files (will be streamed, not loaded into memory)
for (int i = 0; i < num_files; i++) {
    fd_pipeline_add_file(pipe, files[i]);
}

// Progress callback
fd_pipeline_on_progress(pipe, progress_callback, NULL);

// Run (streams through data)
fd_result *result = fd_pipeline_run(pipe);

Input Format

JSONL with text field:

{"id": "doc1", "text": "The quick brown fox..."}
{"id": "doc2", "text": "Another document here..."}

Or specify custom field:

config.text_field = "content";  // Use "content" instead of "text"

Output Format

JSONL with duplicate annotations:

{"id": "doc1", "text": "...", "duplicate_of": null}
{"id": "doc2", "text": "...", "duplicate_of": "doc1"}

Or filtered (only unique documents):

config.output_mode = FD_OUTPUT_UNIQUE_ONLY;

Performance Targets

Dataset Size CPU Time ASIC Time Speedup
1M docs 10 min 10 sec 60x
100M docs 16 hours 15 min 64x
1B docs 7 days 2.5 hours 67x

Comparison with Existing Tools

Tool Language ASIC Support Streaming
datasketch Python No No
text-dedup Python No Partial
fd_dedup C Yes Yes

File Format

Index Format (.fdi)

[Header: 16 bytes]
  magic: "FDI1"
  version: uint16
  num_docs: uint64
  config_hash: uint32

[Config: variable]
  shingle_size: uint8
  num_permutations: uint16
  num_bands: uint8
  band_size: uint8

[Signatures: num_docs × sig_size]
  For each doc:
    doc_id: uint64
    signature: uint32[num_permutations]

[LSH Buckets: num_bands × variable]
  For each band:
    num_buckets: uint32
    For each bucket:
      bucket_hash: uint32
      doc_ids: uint64[]

Dependencies

  • ../lib/asic/ - ASIC device abstraction (from hash-hdc)
  • Standard C library only