Skip to content

Documents

Use DocumentStorage when each training sample should remain one whole record, such as a conversation. A document can contain tokens, labels, IDs, or any other fields your format can encode. Each sample is one decoded record. Records are not concatenated or padded. Pass the storage to StatefulDataLoader.from_storage and omit sequence_length.

Store trusted Python records

This self-contained example writes two documents and pads them in the PyTorch collator. Real preprocessing and training should be separate scripts, as in the quick-start.

from pathlib import Path
from tempfile import TemporaryDirectory

import torch
from torch.nn.utils.rnn import pad_sequence

from guideloom import DocumentPickleFormat, DocumentStorage, StatefulDataLoader


def collate(samples):
    lengths = torch.tensor([len(sample["tokens"]) for sample in samples])
    tokens = pad_sequence(
        [torch.tensor(sample["tokens"], dtype=torch.int64) for sample in samples],
        batch_first=True,
        padding_value=0,
    )
    return {"tokens": tokens, "lengths": lengths}


with TemporaryDirectory() as directory:
    storage = DocumentStorage(Path(directory), DocumentPickleFormat())
    with storage.sync_writer() as writer:
        writer.append(0, {"document_id": "a", "tokens": [10, 11]})
        writer.append(1, {"document_id": "b", "tokens": [12, 13, 14]})

    with storage.sync_reader() as reader:
        assert reader.read(1)["document_id"] == "b"

    with StatefulDataLoader.from_storage(
        storage, collate, device=torch.device("cpu"), local_batch_size=2
    ) as loader:
        batches = list(loader)
        assert len(batches) == 1
        assert batches[0]["tokens"].tolist() == [[10, 11, 0], [12, 13, 14]]
        assert batches[0]["lengths"].tolist() == [2, 3]

Pickle can execute code when decoded; only load pickle datasets you trust. It is convenient for prototypes, but constructing many Python objects can be expensive. The custom document format uses struct and np.frombuffer to return a document ID, token array, and shard number without pickle. JSON or MessagePack are other options for general records; see performance tips.

Custom document format

A format defines both the input you write and the decoded object you read. These shapes can differ: the following format stores a document ID and tokens, then also returns the source shard when decoding. It's best to keep the format class in an importable module shared by preprocessing and training.

Implement DocumentStorageFormat.pack(data) to return bytes and unpack(encoded_data, shard) to decode them. This example stores a four-byte document ID followed by little-endian 32-bit tokens, without pickle.

import struct
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import TypedDict

import numpy as np

from guideloom import DocumentStorage
from guideloom.types import DocumentStorageFormat


# Typing is optional. These TypedDicts describe the shapes for a type checker.
class TokenDocument(TypedDict):
    document_id: int
    tokens: list[int]


class DecodedTokenDocument(TypedDict):
    document_id: int
    tokens: np.ndarray
    shard: int


# The type parameters and method annotations below are optional.
class TokenDocumentFormat(DocumentStorageFormat[TokenDocument, DecodedTokenDocument]):
    prefix = struct.Struct("<I")

    def pack(self, data: TokenDocument) -> bytes:
        return self.prefix.pack(data["document_id"]) + np.asarray(data["tokens"], dtype="<i4").tobytes()

    def unpack(self, encoded_data, shard: int) -> DecodedTokenDocument:
        (document_id,) = self.prefix.unpack_from(encoded_data)
        tokens = np.frombuffer(encoded_data, dtype="<i4", offset=self.prefix.size)
        return {"document_id": document_id, "tokens": tokens, "shard": shard}


with TemporaryDirectory() as directory:
    storage = DocumentStorage(Path(directory), TokenDocumentFormat())
    with storage.sync_writer() as writer:
        writer.append(0, {"document_id": 7, "tokens": [10, 11, 12]})
    with storage.sync_reader() as reader:
        assert reader.read(0)["tokens"].tolist() == [10, 11, 12]

Keep the codec and schema with the dataset. Your application owns compatibility of custom encodings; package version guarantees cannot validate changes to your dtype, byte order, payload schema, or decoding code.