torch-vault v0.1.0

torch-vault

Embedded Rust engine for zero-copy PyTorch checkpoint management and SQL-driven dataset streaming using safetensors, SQLite WAL mode, and DuckDB OLAP predicate pushdown.

Core Guarantee: Replaces insecure pickle-based torch.save with memory-mapped safetensors binary files indexed inside atomic SQLite WAL transactions.

Installation

Installing the Python Package & CLI

pip install torch-vault

Building from Source (Rust & PyO3)

Building from source requires Python >= 3.10 and a Rust toolchain (cargo and rustc):

git clone https://github.com/ethqnol/torch-vault.git
cd torch-vault
pip install maturin
maturin develop --release

Quickstart

import torch
import torch.nn as nn
from torch_vault import Vault, Segment

model = nn.Linear(10, 2)
optimizer = torch.optim.Adam(model.parameters())
vault = Vault("./checkpoints")

# Save weight checkpoint ONLY IF val_loss improves, while continuously logging metrics to SQLite
ckpt_id = vault.save(
    model,
    optimizer,
    metrics={"val_loss": 0.342, "accuracy": 0.912},
    save_if_better="val_loss",
    mode="min"
)

# Export SQLite metrics history directly to a Pandas DataFrame
df = vault.to_dataframe()

# Load best model weights automatically from SQLite index
best = vault.load(metric="val_loss", mode="min")
model.load_state_dict(best["model"])

CLI & Interactive WebApp UI Inspector

# Launch interactive browser WebApp inspector at http://127.0.0.1:8000/
torch-vault ui ./checkpoints --port 8000

# Print terminal table of all checkpoints and logged metrics
torch-vault list ./checkpoints

About & Architecture Deep-Dive

torch-vault replaces un-indexed, pickle-based PyTorch state dict saving with a compiled Rust engine linked to Python via PyO3 bindings.

Checkpoint Subsystem (safetensors + SQLite WAL)

Model weight tensors are serialized into binary safetensors format using zero-copy memory mapping (memmap2). Checkpoint metadata (epoch, step, metrics, tags) is stored in an embedded SQLite index database (vault_index.db) configured with Write-Ahead Logging (WAL) for concurrent read access during training.

Dataset & Streaming Subsystem (DuckDB + Arrow C Stream)

Dataset filtering executes directly on NVMe disk inside an embedded DuckDB instance before crossing into Python memory. Filtered record batches cross the PyO3 boundary via zero-copy C-ABI Arrow memory pointers (__arrow_c_stream__ PyCapsule) into PyTorch DataLoaders.

Architectural Comparison Matrix

Dimension Standard PyTorch / Python Data Loaders torch-vault (Rust Core)
Checkpoint Security Uses pickle (torch.save), vulnerable to arbitrary code execution. Uses zero-copy safetensors binary format. Safe and mmap-backed.
Crash Resilience Simple atomic file renames can leave orphan corrupted files. Write operations run inside atomic SQLite WAL transactions.
Metric Indexing Manually parse filenames or JSON files in Python loops. Embedded SQLite index with json_extract() query support.
Conditional Saving Custom Python logic to track prior loss and compare floats. save_if_better="val_loss" records metrics continuously, writing weight files only on improvement.
Train/Val/Test Splitting Requires manual index slicing or full dataset loading in RAM. to_dataloader(split=(0.8, 0.1, 0.1), seed=42) checks splits BEFORE running row transforms.
Dataset Filtering Loads full dataset into Python RAM or uses slow Pandas filters. DuckDB SQL predicate pushdown runs in C++/Rust on disk.

End-to-End Production Showcase (PyTorch LSTM Sentiment Classifier)

import torch
import torch.nn as nn
from torch_vault import Vault, Segment
from torch_vault.transforms import (
    Compose, LabelEncoder, Lambda, MinMaxScaler, RenameKey, SelectKeys
)

class SentimentLSTM(nn.Module):
    def __init__(self, vocab_size: int, embed_dim: int = 64, hidden_dim: int = 128):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
        self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True)
        self.fc = nn.Linear(hidden_dim, 2)

    def forward(self, x):
        out, (hn, cn) = self.lstm(self.embedding(x))
        return self.fc(hn[-1])

vocab = {"<pad>": 0, "<unk>": 1, "good": 2, "bad": 3, "great": 4}

def preprocess_sample(row: dict) -> dict:
    tokens = [vocab.get(w, 1) for w in str(row["text"]).lower().split()[:50]]
    tokens += [0] * (50 - len(tokens))
    return {
        "input_ids": torch.tensor(tokens, dtype=torch.long),
        "target": torch.tensor(row["sentiment_score"], dtype=torch.long),
    }

label_encoder = LabelEncoder(key="raw_label", classes=["negative", "positive"])
pipeline = Compose([
    RenameKey(src="raw_label", dst="sentiment_score"),
    label_encoder,
    Lambda(preprocess_sample),
    SelectKeys("input_ids", "target"),
])

train_loader, val_loader, test_loader = (
    Segment("data/reviews.parquet")
    .where("length(text) > 10")
    .transform(pipeline)
    .to_dataloader(batch_size=32, split=(0.8, 0.1, 0.1), seed=42)
)

model = SentimentLSTM(vocab_size=len(vocab) + 10)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss()
vault = Vault("./sentiment_vault")

for epoch in range(1, 6):
    model.train()
    for batch in train_loader:
        optimizer.zero_grad()
        loss = criterion(model(batch["input_ids"]), batch["target"])
        loss.backward()
        optimizer.step()

    # Evaluate validation loss
    model.eval()
    val_loss = sum(criterion(model(b["input_ids"]), b["target"]).item() for b in val_loader) / len(val_loader)

    vault.save(
        model=model,
        optimizer=optimizer,
        metrics={"val_loss": val_loss},
        epoch=epoch,
        save_if_better="val_loss",
        mode="min"
    )

best_weights = vault.load(metric="val_loss", mode="min")
model.load_state_dict(best_weights["model"])

Vault API Specification

Vault(dir_path: str, run_id: Optional[str] = None, overwrite: bool = False)

Opens or creates a checkpoint vault directory. Initializes an embedded SQLite index (vault_index.db) in WAL mode with multi-experiment run_id tracking.

Parameters

NameTypeDefaultDescription
dir_pathstrRequiredTarget directory path where vault_index.db and .safetensors files are stored.
run_idOptional[str]"default"Experiment or architecture identifier for multi-run comparison.
overwriteboolFalseIf True, wipes pre-existing records matching run_id. If False and run_id exists, raises a ValueError to prevent accidental metric collision.
Output & Side Effects: Returns a Vault instance. Modifies filesystem by creating dir_path and initializing SQLite database.
Vault.save(model=None, optimizer=None, *, metrics=None, step=None, epoch=None, tags=None, save_if_better=None, mode="min") -> Optional[str]

Continuously records metric history into SQLite, and conditionally serializes model state dicts to binary .safetensors files on disk.

Parameters

NameTypeDefaultDescription
modelOptional[nn.Module]NonePyTorch model whose state dict is saved. Pass None for metrics-only logging.
optimizerOptional[Optimizer]NoneOptional optimizer instance whose parameter states are saved.
metricsOptional[Dict[str, float]]NoneScalar metrics indexed in SQLite JSON column.
stepOptional[int]NoneGlobal training step counter.
epochOptional[int]NoneCurrent epoch number.
tagsOptional[List[str]]NoneOptional string tags for metadata filtering.
save_if_betterOptional[str]NoneMetric key to compare against prior saved weight checkpoints.
keep_top_kOptional[int]NoneIf specified, automatically prunes older .safetensors weight files on disk when checkpoint count exceeds keep_top_k, while preserving metric history in SQLite.
mode"min" | "max""min"Optimization direction ("min" for loss, "max" for accuracy).
Output & Side Effects: Returns str checkpoint ID if .safetensors file was written to disk, or None if weight file save was skipped. Modifies SQLite database state in-place.
Vault.log_metrics(metrics: Dict[str, float], step=None, epoch=None, tags=None) -> Optional[str]

Lightweight helper that records scalar metrics directly into the SQLite index without passing a model instance or writing binary weight files.

Parameters

NameTypeDefaultDescription
metricsDict[str, float]RequiredDictionary of scalar metric names and float values.
stepOptional[int]NoneGlobal training step counter.
epochOptional[int]NoneCurrent epoch number.
tagsOptional[List[str]]NoneString tags for query filtering.
Output & Side Effects: Returns str checkpoint ID of the inserted SQLite metric record. Modifies SQLite index in-place.
Vault.to_dataframe(query=None) -> pandas.DataFrame

Exports SQLite index metadata and flattened scalar metrics into a Pandas DataFrame for analysis or visualization.

Parameters

NameTypeDefaultDescription
queryOptional[str]NoneOptional SQL WHERE clause to filter exported records.
Output & Side Effects: Returns a pandas.DataFrame containing flattened metadata columns (read-only query; does not modify DB).
Vault.load(checkpoint_id=None, *, query=None, metric=None, mode="min") -> Dict[str, Any]

Reconstructs PyTorch state dict tensors from memory-mapped .safetensors files. Provide exactly one selector argument.

Parameters

NameTypeDefaultDescription
checkpoint_idOptional[str]NoneExact UUID string of checkpoint to load.
queryOptional[str]NoneSQL WHERE query to select checkpoint.
metricOptional[str]NoneMetric name to auto-select best checkpoint.
mode"min" | "max""min"Metric optimization direction.
Output & Side Effects: Returns a dictionary {"model": Dict[str, Tensor], "optimizer": Dict} (pure read operation).
Vault.list() -> List[Dict]

Queries and returns all checkpoint records in SQLite ordered by creation timestamp.

Output & Side Effects: Returns a List[Dict] of metadata records (read-only).
Vault.query(sql_where: str) -> List[Dict]

Executes an arbitrary SQL WHERE clause against SQLite index database.

Parameters

NameTypeDefaultDescription
sql_wherestrRequiredSQL predicate expression (e.g. "epoch >= 5 AND val_loss < 0.4").
Output & Side Effects: Returns a List[Dict] matching the query (read-only).
Vault.delete(checkpoint_id: str) -> None

Deletes the checkpoint record from SQLite index and removes its .safetensors weight file from disk.

Parameters

NameTypeDefaultDescription
checkpoint_idstrRequiredUUID string of checkpoint to remove.
Output & Side Effects: Returns None. Modifies database and deletes weight file from disk.
Vault.best(metric: str, mode="min") -> Optional[Dict]

Queries SQLite for the single best metadata record for a metric without loading weight tensors.

Parameters

NameTypeDefaultDescription
metricstrRequiredMetric name to evaluate.
mode"min" | "max""min"Optimization direction.
Output & Side Effects: Returns Optional[Dict] metadata record (read-only).
Vault.plot_metrics(metrics=None, x_axis="epoch", kind="line", title=None, dark_mode=True, output_path=None, separate_files=False, grid=True, color_palette=None) -> Any

Queries SQLite metrics history and generates styled publication-ready metric charts.

Parameters

NameTypeDefaultDescription
metricsOptional[List[str]]NoneList of metric keys to plot. Defaults to all scalar metrics.
x_axis"epoch" | "step" | "created_at""epoch"X-axis dimension.
kind"line" | "bar" | "scatter""line"Plot style type.
titleOptional[str]NoneCustom chart title.
dark_modeboolTrueUse dark theme background styling.
output_pathOptional[str]NoneFile path to save image(s).
separate_filesboolFalseIf True, plots each metric on its own figure and saves to separate files.
Output & Side Effects: Returns output file path str (or List[str] if separate_files=True), or Matplotlib Figure object(s).

Segment API Specification

Segment(source: Union[str, Any], split="train")

Initializes a dataset query builder backed by an in-memory DuckDB engine. Source can be a Parquet path (.parquet), CSV file (.csv, .tsv, .csv.gz), Hugging Face dataset name ("hf://stanfordnlp/imdb"), or a Hugging Face Dataset object.

Parameters

NameTypeDefaultDescription
sourceUnion[str, Dataset]RequiredFile path, CSV file, Hugging Face URI ("hf://..."), or HF Dataset instance.
splitstr"train"Dataset split name if passing Hugging Face DatasetDict.
Output & Side Effects: Returns a Segment instance.
Segment.from_hf(dataset: Any, split=None, cache_dir=None) -> Segment

Ergonomic constructor that initializes a Segment directly from a Hugging Face dataset identifier or DatasetDict instance with flexible split combinations and slice support.

Parameters

NameTypeDefaultDescription
datasetUnion[str, Dataset, DatasetDict]RequiredHugging Face dataset identifier (e.g. "stanfordnlp/imdb") or loaded Dataset/DatasetDict.
splitUnion[str, List[str]]"train" (or all splits if None)Dataset split identifier, slice ("train[:80%]"), string combination ("train+test"), or list of splits (["train", "test"]).
Output & Side Effects: Returns a Segment instance ready for SQL pushdown filtering.
Segment.where(predicate: str) -> Segment

Appends a SQL predicate for DuckDB pushdown. Predicates run directly on disk in C++/Rust before records reach Python memory.

Parameters

NameTypeDefaultDescription
predicatestrRequiredSQL expression (e.g. "score > 0.8 AND split = 'train'").
Output & Side Effects: Returns a new immutable Segment instance with the added predicate.
Segment.transform(fn: Callable) -> Segment

Registers a row-level Python callable fn(dict) -> dict to transform individual record dictionaries.

Parameters

NameTypeDefaultDescription
fnCallable[[Dict], Dict]RequiredFunction taking sample dict and returning processed dict.
Output & Side Effects: Returns a new immutable Segment instance.
Segment.map_batch(fn: Callable) -> Segment

Registers a batch-level callable to transform an entire PyArrow RecordBatch at once for vectorized processing.

Parameters

NameTypeDefaultDescription
fnCallable[[RecordBatch], RecordBatch]RequiredFunction processing PyArrow RecordBatch.
Output & Side Effects: Returns a new immutable Segment instance.
Segment.to_dataloader(batch_size=32, num_workers=0, split=None, seed=42, collate_fn=None, **kwargs) -> Union[DataLoader, Tuple[DataLoader, ...]]

Wraps the segment in PyTorch DataLoader(s). Pass split=(0.8, 0.1, 0.1) or split=(0.8, 0.2) to create train/val/test DataLoader tuples.

Parameters

NameTypeDefaultDescription
batch_sizeint32Number of samples per batch.
num_workersint0Subprocesses for data loading.
splitOptional[Tuple[float, ...]]NoneSplit ratios tuple e.g. (0.8, 0.1, 0.1). Evaluated BEFORE transforms!
seedint42Pseudo-random seed for deterministic split partitioning.
Output & Side Effects: Returns a single DataLoader (if split=None) or a Tuple[DataLoader, ...].
Segment.to_arrow_stream_capsule() -> PyCapsule

Exports an Arrow C Stream Interface PyCapsule object ("arrow_array_stream") for zero-copy memory interchange with PyArrow or Polars.

Output & Side Effects: Returns a PyCapsule memory pointer object.
Segment.sql() -> str

Returns the constructed DuckDB SQL query string generated by the accumulated .where() predicates.

Output & Side Effects: Returns a str SQL query string (read-only).

Transforms & Preprocessing Reference

Compose(transforms: List[Callable])

Chains multiple row transform callables sequentially.

Parameters

NameTypeDefaultDescription
transformsList[Callable]RequiredList of transform callables.
Output: Returns callable Compose instance.
Lambda(func: Callable, name="lambda")

Wraps an arbitrary Python callable into a pipeline transform step.

Parameters

NameTypeDefaultDescription
funcCallable[[Dict], Dict]RequiredPython function to execute on sample dict.
namestr"lambda"Step identifier name.
Output: Returns callable Lambda instance.
SelectKeys(*keys: str)

Filters a sample dictionary to retain only specified key names.

Parameters

NameTypeDefaultDescription
*keysstrRequiredPositional string key names to retain.
Output: Returns callable SelectKeys instance.
RenameKey(src: str, dst: str)

Renames a sample dictionary key from src to dst.

Parameters

NameTypeDefaultDescription
srcstrRequiredSource key name.
dststrRequiredDestination key name.
Output: Returns callable RenameKey instance.
LabelEncoder(key: str, classes=None)

Maps categorical string labels to integer class indices (0, 1, 2...). Support .fit(samples).

Parameters

NameTypeDefaultDescription
keystrRequiredDictionary field key to encode.
classesOptional[List[Any]]NoneExplicit class ordering list.
Output: Returns LabelEncoder instance (modifies mapping state on .fit()).
StandardScaler(keys: List[str], mean=None, std=None)

Standardizes numeric fields to zero mean and unit variance: (x - mean) / std. Supports .fit(samples).

Parameters

NameTypeDefaultDescription
keysList[str]RequiredNumeric field keys to scale.
Output: Returns StandardScaler instance.
MinMaxScaler(keys: List[str], min_val=None, max_val=None, feature_range=(0.0, 1.0))

Scales numeric fields to a specified range [a, b]. Supports .fit(samples).

Parameters

NameTypeDefaultDescription
keysList[str]RequiredNumeric field keys to scale.
feature_rangeTuple[float, float](0.0, 1.0)Target min and max range bounds.
Output: Returns MinMaxScaler instance.