Managing Multiple Pipeline Instances and Sharing Model Weights in Transformers.js
Load your model once using AutoModel.from_pretrained(), then inject that same PreTrainedModel instance into multiple pipeline constructors to share ONNX sessions and eliminate redundant memory allocation.
When building applications that require several AI tasks simultaneously—such as running both text generation and sentiment analysis—loading separate model weights for each pipeline wastes memory and compute. The huggingface/transformers.js library separates model loading from pipeline construction, making it straightforward to manage multiple pipeline instances efficiently and share model weights across different tasks. By understanding the internal architecture of the model registry, session construction, and pipeline factory, you can optimize resource usage in browser and Node.js environments.
Architecture Overview: How Transformers.js Handles Model Loading
The library organizes model management into distinct layers that make weight sharing possible. Understanding these layers helps you identify exactly where reuse occurs in the codebase.
Model Discovery and Cache Handling
The ModelRegistry class in src/utils/model_registry/ModelRegistry.js determines which files belong to a model and checks local cache status. When you call ModelRegistry.get_files() or the task-specific get_pipeline_files(), the registry walks the model repository and builds a file list such as ["config.json", "onnx/model_q4.onnx"].
To verify if a model is already cached locally, use ModelRegistry.is_cached(), which internally calls check_files_cache() from src/utils/model_registry/is_cached.js. This allows you to check storage status before initiating downloads.
Session Construction
The heavy computational work happens in constructSessions() located in src/utils/model-loader.js (line 49). This function receives the list of model file names, calls getSession() for each, and creates ONNX InferenceSession objects via createInferenceSession().
These sessions are cached inside the PreTrainedModel instance within the this.sessions property. Because this construction involves reading model files and initializing GPU/CPU contexts, doing it once and reusing the result provides significant performance benefits.
Pipeline Factory
The public pipeline() function in src/pipelines.js relies on loadItems() (line 14) to lazily load components. The loadItems() function creates a mapping of component names to concrete classes, then loads each in parallel:
async function loadItems(mapping, model, pretrainedOptions) {
const result = Object.create(null);
// Parallel loading of model, tokenizer, and processor
await Promise.all(promises);
return result; // { model: <PreTrainedModel>, tokenizer: <Tokenizer> }
}
When cls is a model class (e.g., BertForSequenceClassification), calling cls.from_pretrained(model, pretrainedOptions) creates a new PreTrainedModel instance with its own ONNX sessions. To share weights, you must bypass this automatic instantiation and provide your own model instance.
When Weight Sharing Occurs
The library handles model reuse differently depending on how you instantiate pipelines:
-
Calling
pipeline()twice with the same model ID: Each call executesloadItems(), andPreTrainedModel.from_pretrainedrecreates ONNX sessions every time. No sharing occurs—each pipeline maintains separate session objects. -
Manually constructing the model once: Calling
await AutoModelForSequenceClassification.from_pretrained('model-id', options)returns aPreTrainedModelinstance that holds the sessions. Passing this same instance to pipeline constructors enables true weight sharing across all pipelines. -
Sharing tokenizers and processors: Tokenizers are lightweight JavaScript objects. Instantiate once with
await AutoTokenizer.from_pretrained()and reuse across pipelines without memory concerns.
Practical Implementation: Sharing a Model Across Multiple Pipelines
The following pattern demonstrates how to load a model once and share it between a sentiment analysis pipeline and a zero-shot classification pipeline:
import {
AutoModelForSequenceClassification,
AutoTokenizer,
pipeline,
} from '@huggingface/transformers';
// Configuration options for device and quantization
const sharedOptions = {
device: 'webgpu', // or 'cpu', 'wasm'
dtype: 'fp16', // matches pre-quantized model file
cache_dir: './my_cache',
};
// 1. Load the heavy model and tokenizer once
const model = await AutoModelForSequenceClassification.from_pretrained(
'onnx-community/gpt2-ONNX',
sharedOptions,
);
const tokenizer = await AutoTokenizer.from_pretrained(
'onnx-community/gpt2-ONNX',
sharedOptions,
);
// 2. Instantiate multiple pipelines with shared references
const sentiment = new (await import('./src/pipelines/text-classification.js')).TextClassificationPipeline({
model,
tokenizer,
});
const zeroShot = new (await import('./src/pipelines/zero-shot-classification.js')).ZeroShotClassificationPipeline({
model,
tokenizer,
});
// 3. Execute inference—both pipelines use the same ONNX sessions
console.log(await sentiment('I love transformers!'));
console.log(await zeroShot('I love transformers!', {
candidate_labels: ['positive', 'negative', 'neutral'],
}));
// 4. Clean up all sessions in one operation
await model.dispose(); // Releases memory for both pipelines
In this implementation, AutoModelForSequenceClassification.from_pretrained creates exactly one set of ONNX sessions. Both TextClassificationPipeline and ZeroShotClassificationPipeline receive references to the same model and tokenizer objects, eliminating redundant I/O and memory allocation.
Checking Cache Status Before Loading
To check whether a model is fully cached before attempting to load it—useful for progress indicators or offline detection—use the ModelRegistry API:
import { ModelRegistry } from '@huggingface/transformers';
const { allCached, files } = await ModelRegistry.is_cached(
'onnx-community/gpt2-ONNX',
{ dtype: 'fp16' }
);
if (!allCached) {
console.log('Downloading missing files...');
}
This function calls get_files() to enumerate required assets, then check_files_cache() to verify local availability.
Key Source Files for Pipeline Management
Understanding these files helps you debug and optimize your implementation:
| File | Purpose for Weight Sharing |
|---|---|
src/utils/model_registry/ModelRegistry.js |
Central API for file discovery, cache checking, and cache management |
src/utils/model-loader.js |
Contains constructSessions() which builds ONNX sessions; reuse avoids rebuilding |
src/pipelines.js |
Houses loadItems(); shows how the factory loads components that you can bypass |
src/pipelines/_base.js |
Defines the Pipeline base class with the dispose() method for cleanup |
src/models/modeling_utils.js |
Implements PreTrainedModel class that holds the sessions map |
Summary
- Load once, reuse everywhere: Instantiate
PreTrainedModelviaAutoModel.from_pretrained()and pass the same instance to multiple pipeline constructors. - Avoid the convenience trap: The
pipeline()factory function creates new sessions per call; manual construction is required for sharing. - Cleanup is centralized: Calling
dispose()on the shared model instance releases resources for all connected pipelines. - Check cache proactively: Use
ModelRegistry.is_cached()to verify local availability before triggering downloads.
Frequently Asked Questions
How do I share a model between different pipeline types in Transformers.js?
Instantiate your model once using the appropriate AutoModel class (such as AutoModelForSequenceClassification.from_pretrained()), then pass that same model instance to each pipeline constructor. For example, share a single BERT model between both TextClassificationPipeline and TokenClassificationPipeline by injecting the identical model object into both constructors rather than letting each pipeline load its own copy.
Does calling the pipeline() function multiple times share weights automatically?
No. Each call to pipeline() invokes loadItems(), which triggers PreTrainedModel.from_pretrained() and creates new ONNX sessions. To share weights, you must manually construct the model and tokenizer first, then instantiate pipeline classes directly with those pre-loaded instances.
What is the memory impact of sharing model weights across pipelines?
Sharing a PreTrainedModel instance ensures that only one copy of the ONNX sessions resides in GPU or CPU memory, regardless of how many pipelines reference it. This reduces memory consumption linearly compared to loading separate instances for each pipeline. The model's dispose() method then releases all shared resources in a single operation.
Can I share only the tokenizer or processor between pipelines?
Yes. Tokenizers and processors are lightweight JavaScript objects that consume minimal memory. You can safely instantiate them once with AutoTokenizer.from_pretrained() or AutoProcessor.from_pretrained() and reuse the same instance across any number of pipelines without significant performance considerations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →