Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
df7e912
init commit
kamran-nvidia Feb 19, 2026
fd806d2
fix: Add missing import of io in task_encoder and test_task_encoder
kamran-nvidia Feb 19, 2026
1cf57ee
Merge branch 'main' into kamran/qwen3_vl_energon
kamran-nvidia Feb 19, 2026
6fcf48f
fix: Update file permissions for peft_seq_unpacked.sh
kamran-nvidia Feb 19, 2026
374252a
fix: Improve logging and comments in EnergonMultiModalDataModule for …
kamran-nvidia Feb 19, 2026
d2ec77a
fix: Remove unnecessary blank lines in task_encoder and test_task_enc…
kamran-nvidia Feb 19, 2026
0aace0e
feat: Add comprehensive tests for Context Parallelism handling in Ene…
kamran-nvidia Feb 19, 2026
21d2628
fix: Remove unused import of 'io' in task_encoder.py
kamran-nvidia Feb 19, 2026
6b6e273
Update src/megatron/bridge/recipes/qwen_vl/data/energon/task_encoder.py
kamran-nvidia Feb 19, 2026
1477d91
Merge branch 'main' into kamran/qwen3_vl_energon
kamran-nvidia Feb 19, 2026
2fb1b96
fix: Remove unused '__subflavor__' attribute from QwenVLTaskEncoder t…
kamran-nvidia Feb 21, 2026
258dc4b
fix: Add missing '__subflavors__' attribute to ChatMLSample in QwenVL…
kamran-nvidia Feb 21, 2026
f5187af
Merge branch 'main' into kamran/qwen3_vl_energon
kamran-nvidia Feb 23, 2026
706fe9b
fix: Add pack_sequences_in_batch attribute to EnergonProvider
kamran-nvidia Feb 23, 2026
10feab5
feat: Add dataset_type argument for VLM recipes in run_recipe.py
kamran-nvidia Feb 23, 2026
70df6af
fix: Update videos attribute type in ChatMLSample to support nested l…
kamran-nvidia Feb 24, 2026
5c4b45a
feat: Add energon_test.sh for LoRA finetuning with sequence packing c…
kamran-nvidia Feb 24, 2026
612ae77
Merge branch 'main' into kamran/qwen3_vl_energon
kamran-nvidia Feb 24, 2026
5a0685f
fix: Update copyright year and modify command for running LoRA finetu…
kamran-nvidia Feb 24, 2026
3445703
docs: Add finetuning instructions for Energon dataset in README.md
kamran-nvidia Feb 24, 2026
19ca029
feat: Extend video handler to support additional video extensions and…
kamran-nvidia Feb 24, 2026
d42610f
Merge branch 'main' into kamran/qwen3_vl_energon
kamran-nvidia Feb 25, 2026
4f35c68
fix: correct typos in README.md and task_encoder.py
kamran-nvidia Feb 25, 2026
fee00b2
Merge branch 'main' into kamran/qwen3_vl_energon
kamran-nvidia Feb 26, 2026
550cba7
Merge branch 'kamran/qwen3_vl_energon' of github.com:NVIDIA-NeMo/Mega…
kamran-nvidia Feb 26, 2026
4926295
feat: integrate ProcessGroupCollection for distributed training in En…
kamran-nvidia Feb 26, 2026
853dc57
Merge branch 'main' into kamran/qwen3_vl_energon
kamran-nvidia Feb 26, 2026
5047bd6
feat: update image and video processing to use PIL format in QwenVLTa…
kamran-nvidia Feb 26, 2026
9390e19
fix: update input_ids extraction to handle BatchEncoding type in Qwen…
kamran-nvidia Feb 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified examples/models/vlm/qwen3_vl/peft_seq_unpacked.sh
100644 → 100755
Empty file.
39 changes: 30 additions & 9 deletions src/megatron/bridge/data/energon/base_energon_datamodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,17 +167,30 @@ def train_dataloader(self) -> Any:
return self.train_dataloader_object
if not parallel_state.is_initialized():
logger.info(
f"Muiltimodal data loader parallel state is not initialized,"
f"using default worker config with no_workers {self.num_workers}"
f"Multimodal data loader parallel state is not initialized, "
f"using default worker config with num_workers {self.num_workers}"
)
worker_config = WorkerConfig.default_worker_config(self.num_workers)
else:
# NOTE: We intentionally use the pure DP rank (with_context_parallel=False)
# rather than the combined DP-CP rank. With Megatron's rank ordering
# (default "tp-cp-ep-dp-pp"), all CP ranks within the same DP replica
# already share the same pure DP rank. This ensures that CP ranks
# processing different sequence portions of the same batch receive
# identical data from the dataloader.
# Using with_context_parallel=True would be INCORRECT here — it would
# assign each CP rank a unique rank, causing them to read different
# data shards.
rank = parallel_state.get_data_parallel_rank()
world_size = parallel_state.get_data_parallel_world_size()
data_parallel_group = parallel_state.get_data_parallel_group()
cp_size = parallel_state.get_context_parallel_world_size()
cp_rank = parallel_state.get_context_parallel_rank()
Comment thread
kamran-nvidia marked this conversation as resolved.
Outdated
logger.info(
f" Multimodal train dataloader initializing with"
f"rank {rank} world_size {world_size} data_parallel_group {data_parallel_group} ****** "
f"Multimodal train dataloader initializing with "
f"dp_rank {rank} dp_world_size {world_size} "
f"cp_rank {cp_rank} cp_size {cp_size} "
f"data_parallel_group {data_parallel_group}"
)
worker_config = WorkerConfig(
rank=rank,
Expand Down Expand Up @@ -207,20 +220,28 @@ def val_dataloader(self):

if not parallel_state.is_initialized():
logger.info(
f"Muiltimodal val data loader parallel state is not initialized,"
f"using default worker config with no_workers {self.num_workers}"
f"Multimodal val data loader parallel state is not initialized, "
f"using default worker config with num_workers {self.num_val_workers}"
)
worker_config = WorkerConfig.default_worker_config(self.num_val_workers)
else:
# NOTE: Pure DP rank (with_context_parallel=False) is correct here.
# See train_dataloader() comment for detailed explanation of CP handling.
rank = parallel_state.get_data_parallel_rank()
world_size = parallel_state.get_data_parallel_world_size()
data_parallel_group = parallel_state.get_data_parallel_group()

logger.info(f"rank {rank} world_size {world_size} data_parallel_group {data_parallel_group}")
cp_size = parallel_state.get_context_parallel_world_size()
cp_rank = parallel_state.get_context_parallel_rank()
logger.info(
f"Multimodal val dataloader initializing with "
f"dp_rank {rank} dp_world_size {world_size} "
f"cp_rank {cp_rank} cp_size {cp_size} "
f"data_parallel_group {data_parallel_group}"
)
worker_config = WorkerConfig(
rank=rank,
world_size=world_size,
num_workers=self.num_workers,
num_workers=self.num_val_workers,
data_parallel_group=data_parallel_group,
worker_debug_path=None,
worker_log_level=0,
Expand Down
100 changes: 50 additions & 50 deletions src/megatron/bridge/recipes/qwen_vl/data/energon/task_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,22 @@
# limitations under the License.

import dataclasses
import io
Comment thread
kamran-nvidia marked this conversation as resolved.
Outdated
import json
import logging
import pickle
import re
from collections import defaultdict
from dataclasses import dataclass
from typing import Dict, List
from typing import Dict, List, Optional

import numpy as np
import torch
from megatron.energon import Batch, DefaultTaskEncoder
from megatron.energon.epathlib.epath import EPath
from megatron.energon.flavors.base_dataset import Sample
from megatron.energon.task_encoder.cooking import Cooker, basic_sample_keys
from PIL import Image
from megatron.energon.flavors.webdataset import DefaultDecoderWebdatasetFactory
from webdataset.autodecode import Decoder, imagehandler

from megatron.bridge.training.utils.visual_inputs import Qwen2_5_VLVisualInputs

Expand Down Expand Up @@ -154,13 +156,53 @@ def _get(token_str: str, default_id: int) -> int:

@dataclass
class ChatMLSample(Sample):
"""Intermediate Sample Format"""
"""multi-turn complex samples with images and videos"""

# __key__: str
# __subflavors__: Dict
imgs: List[Image.Image]
videos: List[torch.Tensor | list[Image.Image]]
conversation: str # JSON string of GPT-format conversations
imgs: Optional[List[torch.Tensor]] = None
videos: Optional[List[torch.Tensor]] = None


class videohandler:
"""Create an video handler."""
Comment thread
kamran-nvidia marked this conversation as resolved.
Outdated

def __init__(self, imagespec):
self.extensions = ["jpgs", "mp4s"]
self.extensions_mapping = {"jpgs": "jpg", "mp4s": "jpg"}
self.image_handler = imagehandler(imagespec)

def __call__(self, key, data):
"""Perform nested image decoding."""
extension = re.sub(r".*[.]", "", key)
if extension.lower() not in self.extensions:
return None
data = pickle.loads(data)
Comment thread
kamran-nvidia marked this conversation as resolved.
key = self.extensions_mapping[extension]
Comment thread
kamran-nvidia marked this conversation as resolved.
if extension.lower() == "jpgs":
data = [self.image_handler(key, d) for d in data]
else:
data = [[self.image_handler(key, d) for d in video] for video in data]
return data


class ChatMLWebdataset(DefaultDecoderWebdatasetFactory[ChatMLSample]):
"""Webdataset factory for multi-turn ChatML samples with multimodal support.

Extends DefaultDecoderWebdatasetFactory to decode webdataset shards into
ChatMLSample instances, using custom handlers for image, audio, and video fields.
"""

__sample_type__ = ChatMLSample

def __init__(self, path: EPath, *, auto_decode: bool = True, **kwargs):
super().__init__(path, auto_decode=auto_decode, **kwargs)
if auto_decode:
self._decoder = Decoder(
[
imagehandler(self.image_decode),
videohandler(self.image_decode),
]
)
Comment thread
kamran-nvidia marked this conversation as resolved.


@dataclass
Expand Down Expand Up @@ -231,51 +273,9 @@ def convert_to_qwenvl_content(user_input: str, image_pattern: str = "<image>", v
return contents


def cook_chatml_sample(sample: dict) -> ChatMLSample:
Comment thread
cuichenx marked this conversation as resolved.
"""
Convert crude sampel to ChatMLSample.

Args:
sample: Crude sample in pickle serialized format

Returns:
sample in ChatMLSample format
"""
imgs = sample.get("jpgs", None)
if imgs:
imgs = pickle.loads(imgs)
if isinstance(imgs, list) and len(imgs) > 0:
imgs = [Image.fromarray(d) for d in imgs]
else:
imgs = None
videos = sample.get("videos", None)
if videos:
videos = pickle.loads(videos)
if isinstance(videos, list) and len(videos) > 0:
videos = [[d for d in video] for video in videos]
else:
videos = None
if "<image>" in sample["json"] and imgs is None:
logging.warning("<image> in conversation text but no image data")
if "<video>" in sample["json"] and videos is None:
logging.warning("<video> in conversation text but no video data")

chat_sample = ChatMLSample(
**basic_sample_keys(sample),
imgs=imgs,
videos=videos,
conversation=sample["json"],
)
return chat_sample


class QwenVLTaskEncoder(DefaultTaskEncoder[ChatMLSample, QwenVLTaskSample, QwenVLTaskBatch, dict]):
"""A simple task encoder for captioning."""

cookers = [
Cooker(cook_chatml_sample),
]

def __init__(
self,
tokenizer,
Expand Down
Loading
Loading