Why map() is the workhorse
ds.map(fn) applies a function row-by-row (or batch-by-batch) and returns a new dataset. It looks like a Pandas .apply() but with two crucial differences: it's cached (results memoized to disk keyed by function source) and it's multi-process (num_proc=N).
Batched and unbatched
Default is per-row. For tokenization, NLP preprocessing, and most heavy ops, batched=True is dramatically faster — the function receives a dict of column lists, returns the same shape. The library splits batches automatically.
The remove_columns gotcha
If your map function adds new columns (typical: tokenize the text column to input_ids + attention_mask), the original column stays unless you set remove_columns=['text']. Trainers don't accept extra string columns, so this is the most common cause of "the trainer is broken" tickets.