Pipelines: components and nlp.pipe

spaCy · Practical · 12 min

Processing large volumes efficiently is mostly about batching and running only the components you use.

nlp.pipe_names lists the components that run, in order, on every nlp(text) call: the tokenizer first (not listed), then for example tok2vec, tagger, parser, attribute_ruler, lemmatizer, ner. nlp.get_pipe("ner") returns a component; nlp.add_pipe, remove_pipe, replace_pipe and rename_pipe edit the pipeline.

Running unnecessary components wastes time. Three ways to skip them:

  • spacy.load(name, exclude=["parser", "ner"]): never loaded
  • spacy.load(name, disable=[...]): loaded but switched off; re-enable with nlp.enable_pipe
  • with nlp.select_pipes(enable=["tagger"]): temporarily, for one block

Watch dependencies: the lemmatizer needs POS from the tagger and attribute ruler, sentence boundaries come from the parser, and components that listen to a shared tok2vec need it enabled.

For many texts, never loop over nlp(text). `nlp.pipe(texts, batch_size=...)` streams Docs while batching internally, which is much faster, especially for transformer and GPU pipelines. Pass as_tuples=True with (text, context) pairs to carry ids or metadata through. n_process=2 or more uses multiprocessing on CPU, though each process loads its own copy of the model, so it pays off only for large jobs.

If you only need tokens, nlp.make_doc(text) runs just the tokenizer. nlp.analyze_pipes(pretty=True) reports what each component sets and requires.

Example

import time
import spacy

nlp = spacy.load("en_core_web_sm", exclude=["parser"])
print(nlp.pipe_names)      # ['tok2vec', 'tagger', 'attribute_ruler', 'lemmatizer', 'ner']

texts = [f"Order {i} was shipped to Berlin on Monday." for i in range(2000)]

t = time.perf_counter()
docs = [nlp(t_) for t_ in texts[:500]]
loop = time.perf_counter() - t

t = time.perf_counter()
docs = list(nlp.pipe(texts[:500], batch_size=256))
batched = time.perf_counter() - t
print(batched < loop)       # True: batching is faster

with nlp.select_pipes(enable=["tok2vec", "ner"]):   # only what NER needs
    ents = [d.ents for d in nlp.pipe(texts[:3])]
print(ents[0])              # (Order 0, Berlin, Monday)

records = [("Paris is lovely.", {"id": 1}), ("I met Ana Lopez.", {"id": 2})]
for doc, ctx in nlp.pipe(records, as_tuples=True):
    print(ctx["id"], [e.text for e in doc.ents])    # 1 ['Paris'] / 2 ['Ana Lopez']

Remember

  • pipe_names shows the components run on each call
  • exclude/disable/select_pipes skip components you don't need
  • nlp.pipe batches many texts and is much faster than a loop
  • as_tuples carries metadata; n_process parallelises big CPU jobs

Watch out for

  • Calling nlp(text) in a loop over millions of texts instead of nlp.pipe.

Check yourself

  1. What is the difference between exclude and disable in spacy.load?
  2. Why does nlp.pipe outperform calling nlp() per text?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: spacy.io/usage/processing-pipelines