Implementation:NVIDIA NeMo Curator QAMultilingualSyntheticStage
| Knowledge Sources | |
|---|---|
| Domains | Synthetic Data, Multilingual NLP, LLM Integration |
| Last Updated | 2026-02-14 00:00 GMT |
Overview
QAMultilingualSyntheticStage generates synthetic multilingual question-answer data from scratch using an LLM, without requiring any seed documents as input.
Description
QAMultilingualSyntheticStage extends ProcessingStage[_EmptyTask, DocumentBatch] as a Python dataclass. Unlike most processing stages that transform existing data, this stage takes an _EmptyTask as input and produces a DocumentBatch containing freshly generated synthetic QA data.
For each of num_samples iterations, the stage randomly selects a language from the provided languages list (using secrets.choice for cryptographic randomness), formats the prompt template with the selected language, and queries the LLM. Generated responses have any markdown bold markers (*) stripped.
The stage supports both synchronous (LLMClient) and asynchronous (AsyncLLMClient) clients. The async path uses asyncio.gather for concurrent generation of all samples and includes a thread-based fallback for nested event loop scenarios.
Usage
Use QAMultilingualSyntheticStage when bootstrapping multilingual QA datasets from scratch. It is particularly useful for creating training data across multiple languages by leveraging LLM capabilities, without requiring pre-existing seed documents in those languages.
Code Reference
Source Location
- Repository: NeMo-Curator
- File:
nemo_curator/stages/synthetic/qa_multilingual_synthetic.py - Lines: 1-129
Signature
@dataclass
class QAMultilingualSyntheticStage(ProcessingStage[_EmptyTask, DocumentBatch]):
prompt: str
languages: list[str]
client: AsyncLLMClient | LLMClient
model_name: str
num_samples: int
generation_config: GenerationConfig | None = None
name: str = "QAMultilingualSyntheticStage"
def __post_init__(self) -> None: ...
def inputs(self) -> tuple[list[str], list[str]]: ...
def outputs(self) -> tuple[list[str], list[str]]: ...
def setup(self, _: WorkerMetadata | None = None) -> None: ...
def process(self, _: _EmptyTask) -> DocumentBatch: ...
def _process_llm_response(self, response: list[str]) -> str: ...
def _process_sync(self) -> list[str]: ...
def _process_async(self) -> list[str]: ...
async def _generate_responses_async(self) -> list[str]: ...
Import
from nemo_curator.stages.synthetic.qa_multilingual_synthetic import QAMultilingualSyntheticStage
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| prompt | str |
Yes | Prompt template containing a {language} placeholder that gets substituted with the selected language
|
| languages | list[str] |
Yes | List of language names to randomly sample from for each generated sample |
| client | AsyncLLMClient or LLMClient |
Yes | LLM client instance for querying the model |
| model_name | str |
Yes | Name of the LLM model to query |
| num_samples | int |
Yes | Number of synthetic QA samples to generate |
| generation_config | GenerationConfig |
No | Optional generation parameters (temperature, max tokens, etc.) |
| _ | _EmptyTask |
Yes | An empty task object (no actual data needed as input) |
Outputs
| Name | Type | Description |
|---|---|---|
| result | DocumentBatch |
A document batch containing a pandas DataFrame with a single "text" column holding the generated QA responses, with dataset_name="simple_synthetic_data"
|
Key Implementation Details
Language Selection
For each sample, a language is chosen randomly using secrets.choice (cryptographically secure random selection):
language = secrets.choice(self.languages)
prompt = self.prompt.format(language=language)
Response Post-Processing
Generated text has markdown bold markers stripped to produce clean output:
def _process_llm_response(self, response: list[str]) -> str:
generated_text = response[0] if response else ""
if "*" in generated_text:
generated_text = generated_text.replace("*", "")
return generated_text
Async Processing with Event Loop Handling
The async path handles both normal and nested event loop cases:
def _process_async(self) -> list[str]:
try:
asyncio.get_running_loop()
except RuntimeError:
# No loop running - normal case
return asyncio.run(self._generate_responses_async())
# Already in async context - run in separate thread
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(asyncio.run, self._generate_responses_async())
return future.result()
Concurrent Sample Generation
When using an async client, all samples are generated concurrently via asyncio.gather:
async def _generate_responses_async(self) -> list[str]:
async def generate_single_response(_i: int) -> str:
language = secrets.choice(self.languages)
prompt = self.prompt.format(language=language)
response = await self.client.query_model(
model=self.model_name,
messages=[{"role": "user", "content": prompt}],
generation_config=self.generation_config,
)
return self._process_llm_response(response)
tasks = [generate_single_response(i) for i in range(self.num_samples)]
return await asyncio.gather(*tasks)
Usage Examples
Basic Usage with Sync Client
from nemo_curator.stages.synthetic.qa_multilingual_synthetic import QAMultilingualSyntheticStage
stage = QAMultilingualSyntheticStage(
prompt="Generate a question and answer pair in {language}.",
languages=["English", "Spanish", "French", "German", "Japanese"],
client=my_sync_client,
model_name="nemotron-4-340b",
num_samples=100,
)
With Async Client and Generation Config
from nemo_curator.stages.synthetic.qa_multilingual_synthetic import QAMultilingualSyntheticStage
from nemo_curator.models.client.llm_client import GenerationConfig
gen_config = GenerationConfig(temperature=0.8, max_tokens=512)
stage = QAMultilingualSyntheticStage(
prompt="Generate a detailed question and answer pair in {language} about science.",
languages=["English", "Chinese", "Hindi", "Arabic"],
client=my_async_client,
model_name="nemotron-4-340b",
num_samples=500,
generation_config=gen_config,
)
Related Pages
- Environment:NVIDIA_NeMo_Curator_Python_Linux_Base
- Implementation:NVIDIA_NeMo_Curator_BaseSyntheticStage - Base class for Nemotron-CC synthetic stages (similar pattern but requires seed data)
- Implementation:NVIDIA_NeMo_Curator_NemotronCC_Stages - Concrete Nemotron-CC stages for text transformation
- Implementation:NVIDIA_NeMo_Curator_DataDesignerStage - Alternative synthetic data generation via NeMo Data Designer