How to Optimize RAGAnything Performance with Concurrent File Processing and max_concurrent_files
Set the max_concurrent_files configuration in HKUDS/RAG-Anything to control how many documents parse simultaneously, balancing throughput against memory and CPU limits.
The HKUDS/RAG-Anything library ingests documents through a batch processing pipeline that can become a bottleneck when handling large collections. To optimize RAGAnything performance with concurrent file processing, you must tune the max_concurrent_files parameter, which governs the semaphore-based parallelism in the ingestion engine.
Where Concurrency Configuration Lives
RAG-Anything centralizes its concurrency settings in the configuration dataclass and propagates them through the batch processing mixin.
The Configuration Dataclass
In raganything/config.py, the RAGAnythingConfig dataclass defines the concurrency limit:
@dataclass
class RAGAnythingConfig:
max_concurrent_files: int = 1 # Reads MAX_CONCURRENT_FILES env var
The default value is 1, meaning files process sequentially unless you explicitly raise the limit. The constructor reads the MAX_CONCURRENT_FILES environment variable when present, allowing infrastructure-level tuning without code changes.
Startup Verification
During initialization in raganything/raganything.py, the library logs the effective concurrency setting:
self.logger.info(f" Max concurrent files: {self.config.max_concurrent_files}")
This verification ensures you can confirm the actual limit before batch processing begins.
How Concurrent Processing Works
The library enforces concurrency limits using Python’s asyncio primitives to prevent resource exhaustion while maximizing throughput.
Semaphore-Based Execution Control
In raganything/batch.py, the process_folder_complete method creates an asyncio.Semaphore using the configured limit:
# Inside process_folder_complete
semaphore = asyncio.Semaphore(max_workers) # max_workers defaults to config.max_concurrent_files
async def process_single_file(file_path: Path):
async with semaphore: # Limits simultaneous executions
await self.process_document_complete(...)
This semaphore ensures that at most max_workers coroutines run process_document_complete concurrently. The BatchMixin class propagates this limit through process_documents_batch and process_documents_batch_async.
The Document Processing Pipeline
Each concurrent slot handles the complete document ingestion pipeline:
- Load the file from disk.
- Parse using the configured backend (MinerU, Docling, PaddleOCR, etc.).
- Chunk and insert into the LightRAG storage backend.
Because these steps mix I/O-bound operations (disk reads, OCR API calls) with CPU-bound work (image processing, table extraction), moderate parallelism improves hardware utilization without overloading the system.
Tuning max_concurrent_files for Your Hardware
Match the concurrency limit to your hardware capabilities to avoid memory errors or CPU thrashing:
- Small machines (single-core CPU, <4 GB RAM): Use 1-2 to keep memory usage minimal.
- Modern desktops (4-8 cores, ≥16 GB RAM): Use 4-8 to match physical core counts for CPU-heavy parsing.
- GPU-accelerated servers: Use 12-16 to saturate both CPU preprocessing and GPU OCR pipelines.
- Cloud functions with strict memory caps: Keep the default 1 or set via environment variable to prevent OOM kills.
Exceeding OS file-descriptor limits (typically several thousand) raises OSError, so keep max_concurrent_files well below platform maximums.
Three Methods to Configure Concurrency
RAG-Anything offers three ways to adjust the limit, ordered from global to specific.
Environment Variable Configuration
Set the limit before process startup to affect all instances:
export MAX_CONCURRENT_FILES=8
python -m examples.raganything_example
This approach works best for containerized deployments where you want infrastructure teams to control resources without touching application code.
Runtime Configuration Updates
Modify the setting after initialization using the update_config method:
from raganything import RAGAnything
rag = RAGAnything(llm_model_func=my_llm, embedding_func=my_embedder)
# Increase concurrency for subsequent operations
rag.update_config(max_concurrent_files=8)
Changes take effect immediately for future batch calls but do not interrupt running operations.
Per-Call Parameter Overrides
Override the configuration for a specific batch operation using the max_workers argument:
await rag.process_documents_batch(
file_paths=["/data/reports"],
max_workers=12, # Overrides rag.config.max_concurrent_files for this call only
show_progress=True,
)
This pattern is useful when processing different document types that require varying resource levels, or when temporarily scaling up for large one-time ingestion jobs.
Complete Implementation Examples
End-to-End Batch Processing Setup
from raganything import RAGAnything
# Initialize with model functions
rag = RAGAnything(
llm_model_func=my_llm,
embedding_func=my_embedder,
vision_model_func=my_vision, # Optional for multimodal content
)
# Configure concurrency for this session
rag.update_config(max_concurrent_files=8)
# Process entire folder with parallel execution
await rag.process_folder_complete(
folder_path="my_documents",
output_dir="parsed_output",
recursive=True,
display_stats=True,
)
Inspect Current Settings
Verify the effective configuration before running expensive operations:
print("Effective concurrency:", rag.config.max_concurrent_files)
# Output: 8
Performance Characteristics and Resource Impact
Understanding the trade-offs helps you optimize RAGAnything performance without destabilizing your system:
- Throughput increases roughly linearly with concurrency until you saturate CPU, GPU, or disk I/O.
- Latency per file may rise slightly for individual workers due to context switching, but total batch completion time decreases significantly.
- Memory consumption grows proportionally with parallel parsers because each holds file buffers, OCR model states, and temporary tensors.
- Stability requires keeping the limit below OS thresholds for open file handles and thread counts.
The design deliberately isolates the concurrency knob in RAGAnythingConfig and propagates it through the BatchMixin, making it straightforward to experiment with different values reproducibly.
Summary
- Configuration location:
raganything/config.pydefinesmax_concurrent_files, defaulting to1unless overridden by theMAX_CONCURRENT_FILESenvironment variable. - Enforcement mechanism:
raganything/batch.pyuses anasyncio.Semaphore(max_workers)to limit concurrentprocess_document_completecalls. - Tuning guidance: Use 1-2 for small machines, 4-8 for desktops, and 12-16 for GPU servers.
- Configuration methods: Environment variables (global),
update_config()(runtime), ormax_workersargument (per-call). - Resource trade-off: Higher concurrency improves throughput linearly until hardware saturation but increases memory usage linearly with each parallel parser.
Frequently Asked Questions
What is the default value of max_concurrent_files in RAG-Anything?
The default value is 1, meaning files process sequentially. This conservative default prevents memory issues on minimal hardware and cloud functions. You can verify the current value in the startup logs or by inspecting rag.config.max_concurrent_files after initialization.
How does max_concurrent_files differ from max_workers?
The max_concurrent_files configuration property sets the global default for the RAGAnything instance. The max_workers parameter in batch methods like process_documents_batch temporarily overrides this global setting for that specific call. If you omit max_workers, the method falls back to self.config.max_concurrent_files.
Can I change the concurrency limit after initializing RAGAnything?
Yes. Call rag.update_config(max_concurrent_files=8) to modify the limit at runtime. This change affects all subsequent batch operations but does not alter currently running tasks. For immediate, one-time overrides, use the max_workers argument in the specific batch method instead.
Why does increasing max_concurrent_files cause out-of-memory errors?
Each concurrent file processing slot allocates memory for file buffers, parser models (especially OCR engines like PaddleOCR or Docling), and intermediate tensors. Setting max_concurrent_files too high for your RAM capacity causes the system to exhaust available memory. Reduce the value to 1-2 for machines with limited memory, or monitor memory usage during ingestion to find your hardware’s saturation point.
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 →