Data Tools

Soup includes powerful CLI tools for preparing training datasets. Full augmentation guide: Data augmentation.

Inspect

bash
soup data inspect ./data/train.jsonl

Shows dataset statistics: sample count, token distribution, field analysis. For vision datasets, automatically shows image statistics (count, formats, missing files). --rows N (-r) sets how many sample rows it prints; the default is 5.

Validate

bash
soup data validate ./data/train.jsonl
soup data validate ./data/train.jsonl --format alpaca
soup data validate ./data/train.jsonl --min-valid-fraction 0.9

Checks for missing fields, encoding issues, and format compliance. Auto-detects format when --format is not specified.

It agrees with the loader now, and it did not before v0.75.0. The validator used to judge a row by which top-level keys were present, while load_dataset runs the real converters and drops any row that comes back empty. Those are two different notions of valid, and they disagreed in two directions: the six formats with no key signature at all — prm, pre_tokenized, input_output, video, multimodal and raft — skipped validation entirely and reported every row valid regardless of content, and for the formats that were checked, key presence passed rows a converter drops on a value, such as a null field or a non-dict message. A file could validate clean and then fail in the middle of a training run.

The valid-row count is now computed by running the real conversion path per row, so the agreement is structural rather than a second, weaker check that drifts. When a row is dropped, the converter's own reason is surfaced for the first few offenders, row 3: chatml message must be a dict, so the report says which rows and why rather than only a count. On upstream's own datasets this shifted 120 of 360 file-by-format verdicts, every one of them an over-count on a file that genuinely had zero valid rows in that format.

The exit codes matter, because this is the first step of the PR gate soup ci init writes:

ExitMeaning
0At least one row is usable, and any --min-valid-fraction is met. A partially valid dataset still exits 0 when no minimum is given
1An input error: a missing file, or a format that cannot be detected. Also an unknown --format
2A non-empty dataset with no usable rows, or one below --min-valid-fraction

--min-valid-fraction takes a float in [0.0, 1.0] and defaults to 0.0, meaning no minimum. Two of those three behaviours are new in v0.75.0: before it, a file with zero usable rows exited 0, and --format bogus exited 0 while reporting "2/2 rows valid for bogus format", which quietly turned the CI gate into a pass for any file. An unknown format is an input error rather than a failed gate, deliberately, so a typo cannot be mistaken for a real rejection.

The accepted values for --format are auto plus the eighteen convertible formats: alpaca, sharegpt, chatml, dpo, kto, llava, sharegpt4v, plaintext, embedding, audio, tool-calling, prm, pre_tokenized, input_output, video, multimodal, raft and asr. The flag's own --help text lists only seven of them and is stale; this list is the allowlist the code checks against.

Convert

bash
soup data convert ./data/train.jsonl --to sharegpt --output converted.jsonl

Transform between alpaca, sharegpt, and chatml formats.

Merge

bash
soup data merge data1.jsonl data2.jsonl --output merged.jsonl --shuffle

Combine multiple datasets with optional shuffling.

Deduplicate

bash
# Requires: pip install "soup-cli[data]"
soup data dedup ./data/train.jsonl --threshold 0.8

# Semantic near-dup removal over embedding cosine (v0.71.36, needs [train]):
soup data dedup ./data/train.jsonl --semantic -o clean.jsonl

Remove near-duplicate samples using MinHash, or add --semantic to catch reworded duplicates MinHash's shingling scores as distinct. See Data Moat II for the semantic path, plus soup data topics (coverage map) and soup data canary (memorization probe). --output defaults to <input>_deduped.jsonl and --field limits what is hashed or embedded to one field. --device (auto, cpu or cuda) and --embed-model (default sentence-transformers/all-MiniLM-L6-v2) apply to --semantic only.

Extended Statistics

bash
soup data stats ./data/train.jsonl

Length distribution with histograms, token counts, and language detection.

Synthetic Data Generation

bash
# Generate using OpenAI API
soup data generate --prompt "Create math word problems" --count 100 --format alpaca

# Use a different model
soup data generate --prompt "Medical Q&A pairs" --model gpt-4o --count 500

# Deduplicate against existing data
soup data generate --prompt "..." --count 200 --dedup-with existing.jsonl

# Use seed examples to guide style
soup data generate --prompt "..." --seed examples.jsonl --count 100

# Use a local server (soup serve, Ollama, etc.)
soup data generate --prompt "..." --provider server --api-base http://localhost:11434/v1

--api-base follows one rule for the providers that take a URL (openai, server and vllm): plain http only for loopback (localhost, 127.0.0.1 or ::1), https for a remote server, and, since v0.75.2, never an IP address in a private, link-local or reserved range, on either scheme (the error reads api_base: private/link-local/reserved IP hosts are not allowed (SSRF protection)). Reach a server on your network by its hostname over HTTPS. 0.0.0.0 is not loopback, so use localhost. The ollama provider accepts loopback only.

Multi-Provider Support (v0.20.0+)

bash
# Generate via local Ollama instance
soup data generate --prompt "..." --provider ollama --model llama3.1
soup data generate --prompt "..." --ollama-model llama3.1  # shorthand

# Generate via Anthropic Claude API (set ANTHROPIC_API_KEY env var)
soup data generate --prompt "..." --provider anthropic --model claude-3-haiku-20240307

# Generate via local vLLM server
soup data generate --prompt "..." --provider vllm --model meta-llama/Llama-3.1-8B-Instruct

Domain Templates (v0.20.0+)

bash
# Code instruction pairs (Python, JS, Go, Rust, Java)
soup data generate --prompt "..." --template code --language Python --task-type function

# Multi-turn conversations
soup data generate --prompt "..." --template conversation --turns 6 --topic "science"

# QA from context document
soup data generate --prompt "..." --template qa --context document.txt

# Preference data (DPO/KTO/ORPO)
soup data generate --prompt "..." --template preference --pref-task dpo

# Chain-of-thought reasoning (GRPO)
soup data generate --prompt "..." --template reasoning --domain math

The template flags default to --language Python, --task-type function (also debug, explain, refactor, test), --turns 4 (2 to 10), --topic "general knowledge", --pref-task dpo (also kto, orpo) and --domain math (also logic, code). The options that apply to every template:

FlagDefaultWhat it does
--output, -ogenerated.jsonlWhere the rows are written
--format, -falpacaRow shape: alpaca, sharegpt or chatml
--batch-size5Examples requested per API call
--temperature, -t0.8Sampling temperature
--requests-per-minute, --rpm60Rate limit on API calls
--api-keyOPENAI_API_KEYRead from that environment variable when the flag is omitted; prefer the variable
--trust-remote-codeoffLocal provider only: allow a model that ships custom Python (default deny)

Quality Pipeline (v0.20.0+)

bash
# Auto-validate after generation (remove malformed entries)
soup data generate --prompt "..." --validate

# Auto-filter by quality (coherence scoring)
soup data generate --prompt "..." --filter

# Auto-dedup (MinHash, requires: pip install "soup-cli[data]")
soup data generate --prompt "..." --dedup

# Full quality pipeline: validate + filter + dedup
soup data generate --prompt "..." --quality-pipeline

Quality Filter

bash
# Filter by coherence score
soup data filter ./data/train.jsonl --coherence 0.3

# Filter by perplexity + coherence
soup data filter ./data/train.jsonl --perplexity 500 --coherence 0.3

# Add scores without removing samples
soup data filter ./data/train.jsonl --score-only

Uses perplexity + coherence scoring to identify low-quality samples.

Data Sampling (v0.23.0+)

bash
# Random sample
soup data sample ./data/train.jsonl --strategy random -n 1000

# Diverse sample (TF-IDF clustering)
soup data sample ./data/train.jsonl --strategy diverse -n 500

# Hard examples (by length)
soup data sample ./data/train.jsonl --strategy hard -n 500

Give --n (a row count) or --pct (a percentage from 0 to 100). With neither the command exits 1, and when both are given --pct wins. --seed makes random and diverse reproducible; hard is deterministic and ignores it. The output defaults to <input>_sampled_<strategy>.jsonl beside the input (the flag's own help text says <input>_sampled.jsonl and is stale), and an explicit --output must sit under the current directory.

Data Splitting (v0.23.0+)

bash
# Split into train/val/test. --val and --test are integer PERCENTAGES,
# not fractions; the train remainder is implied, and --train is accepted
# for command parity rather than read.
soup data split ./data/train.jsonl --val 10 --test 10

# Absolute row counts instead of percentages, with a reproducible seed
soup data split ./data/train.jsonl --val 500 --test 500 --absolute --seed 42

# Stratified split. --stratify takes the field to stratify on, as a value.
soup data split ./data/train.jsonl --val 10 --stratify category

# Semantic stratification (v0.73.2): cluster rows by TF-IDF + K-Means, then
# split within each cluster. Cannot be combined with --stratify.
soup data split ./data/train.jsonl --val 10 --stratify-semantic --num-clusters 8

--stratify-semantic is lexical, not an embedding model: it clusters a TF-IDF matrix of each row's text with K-Means (5 clusters unless --num-clusters says otherwise), needs scikit-learn from pip install "soup-cli[data]", and refuses a dataset over 50,000 rows. --num-clusters without --stratify-semantic only prints a warning.

HuggingFace Dataset Hub (v0.24.0+)

bash
# Search for datasets
soup data search "math reasoning"

# Preview remote dataset metadata
soup data preview tatsu-lab/alpaca

# Download to local JSONL
soup data download tatsu-lab/alpaca --output ./data/alpaca.jsonl --samples 1000

Dataset Registry (v0.24.0+)

Keep a local, named record of datasets (name, path and format) in ~/.soup/datasets.json:

bash
# Register a dataset (the path must sit under the current directory)
soup data register my-chat-data --path ./data/chat.jsonl --format chatml

# List registered datasets
soup data registry

# Remove an entry
soup data unregister my-chat-data

The registry is a lookup you list with soup data registry. soup train does not resolve registered names. data.train: registry:my-chat-data and data.train: my-chat-data are both treated as Hugging Face dataset ids (the console prints Loading from HuggingFace: ...), because neither has a file suffix, and both fail there. Put the file path in data.train. (soup data register --help still says "for use in soup.yaml"; that text is out of date.)

Soup is free and Apache-2.0. If it saved you a training run, starring the repo costs nothing and helps most. You can also fund the GPU time behind the work a 4 GB laptop cannot reach.