Skip to content

Commit 86af478

Browse files
committed
Merge branch 'pr-2580' into mxin/qaopd-pr2442-pr2580
Signed-off-by: Meng Xin <mxin@nvidia.com> # Conflicts: # examples/configs/distillation_math.yaml # tests/unit/data_plane/test_architecture_invariants.py
2 parents c55988f + 3b9e56b commit 86af478

18 files changed

Lines changed: 1801 additions & 26 deletions

examples/configs/distillation_math.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,3 +281,18 @@ cluster:
281281
num_nodes: 1
282282
master_port_range_low: 25000
283283
master_port_range_high: 28000
284+
285+
# TransferQueue-mediated data plane for on-policy distillation.
286+
# Off by default; set enabled=true to keep teacher top-k logits in TQ
287+
# instead of returning them to the driver.
288+
data_plane:
289+
enabled: false
290+
impl: transfer_queue
291+
backend: "simple"
292+
storage_capacity: 1000000
293+
num_storage_units: 2
294+
claim_meta_poll_interval_s: 0.5
295+
global_segment_size: 549755813888
296+
local_buffer_size: 68719476736
297+
# observability:
298+
# enabled: false
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# TransferQueue-enabled variant of distillation_math.yaml.
2+
# Uses the same model/data defaults and selects the TQ distillation trainer
3+
# through examples/run_distillation.py when data_plane.enabled=true.
4+
defaults: distillation_math.yaml
5+
6+
checkpointing:
7+
checkpoint_dir: "checkpoints/distillation-tq-${policy.model_name}"
8+
9+
logger:
10+
log_dir: "logs/distillation-tq"
11+
wandb:
12+
name: "distillation-tq-${data.train.dataset_name}-${teacher.model_name}-${policy.model_name}-${loss_fn.kl_type}-${distillation.topk_logits_k}"
13+
tensorboard:
14+
log_dir: "tb_logs-distillation-tq-${data.train.dataset_name}"
15+
16+
data_plane:
17+
enabled: true
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
defaults: ../../distillation_math_tq.yaml
2+
distillation:
3+
num_prompts_per_step: 64
4+
max_num_steps: 20
5+
val_batch_size: 32
6+
val_period: 10
7+
max_val_samples: 256
8+
loss_fn:
9+
kl_type: reverse
10+
checkpointing:
11+
checkpoint_dir: checkpoints/distillation-qwen3-32b-to-1.7b-base-tq
12+
policy:
13+
train_global_batch_size: 32
14+
generation_batch_size: 32
15+
dtensor_cfg:
16+
tensor_parallel_size: 1
17+
context_parallel_size: 1
18+
dynamic_batching:
19+
enabled: false
20+
make_sequence_length_divisible_by: 1
21+
scheduler:
22+
- name: torch.optim.lr_scheduler.LinearLR
23+
kwargs:
24+
start_factor: 0.1
25+
end_factor: 1.0
26+
total_iters: 20
27+
- name: torch.optim.lr_scheduler.ConstantLR
28+
kwargs:
29+
factor: 1.0
30+
total_iters: 10000000000
31+
- milestones:
32+
- 20
33+
teacher:
34+
model_name: Qwen/Qwen3-32B
35+
train_global_batch_size: 32
36+
generation_batch_size: 32
37+
dtensor_cfg:
38+
context_parallel_size: 1
39+
dynamic_batching:
40+
enabled: false
41+
make_sequence_length_divisible_by: 1
42+
scheduler:
43+
- name: torch.optim.lr_scheduler.LinearLR
44+
kwargs:
45+
start_factor: 0.1
46+
end_factor: 1.0
47+
total_iters: 20
48+
- name: torch.optim.lr_scheduler.ConstantLR
49+
kwargs:
50+
factor: 1.0
51+
total_iters: 10000000000
52+
- milestones:
53+
- 20
54+
logger:
55+
log_dir: logs/distillation-qwen3-32b-to-1.7b-base-tq
56+
wandb:
57+
project: nemo-rl
58+
name: distillation-qwen3-32b-to-1.7b-base-tq

examples/run_distillation.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@
3030
from nemo_rl.utils.logger import get_next_experiment_dir
3131

3232

33+
def _select_trainer(master_config: MasterConfig):
34+
"""Pick the distillation trainer based on ``data_plane.enabled``."""
35+
dp_cfg = master_config.data_plane or {}
36+
if dp_cfg.get("enabled", False):
37+
from nemo_rl.algorithms.distillation_sync import distillation_train_sync
38+
39+
print("🚀 Running on-policy distillation training (TransferQueue)")
40+
return distillation_train_sync
41+
print("🚀 Running on-policy distillation training (legacy)")
42+
return distillation_train
43+
44+
3345
def parse_args() -> tuple[argparse.Namespace, list[str]]:
3446
"""Parse command line arguments."""
3547
parser = argparse.ArgumentParser(
@@ -86,6 +98,17 @@ def main() -> None:
8698
val_task_to_env,
8799
) = setup_response_data(tokenizer, config.data, config.env)
88100

101+
_dp_cfg = config.data_plane or {}
102+
if _dp_cfg.get("enabled", False):
103+
from nemo_rl.models.policy.tq_policy import TQPolicy
104+
105+
def _make_student_policy(**kwargs):
106+
return TQPolicy(**kwargs, dp_cfg=_dp_cfg)
107+
108+
_student_policy_factory = _make_student_policy
109+
else:
110+
_student_policy_factory = None
111+
89112
(
90113
student_policy,
91114
teacher_policy,
@@ -97,9 +120,16 @@ def main() -> None:
97120
checkpointer,
98121
distillation_state,
99122
master_config,
100-
) = setup(config, tokenizer, dataset, val_dataset)
123+
) = setup(
124+
config,
125+
tokenizer,
126+
dataset,
127+
val_dataset,
128+
student_policy_factory=_student_policy_factory,
129+
)
101130

102-
distillation_train(
131+
trainer = _select_trainer(master_config)
132+
trainer(
103133
student_policy,
104134
teacher_policy,
105135
student_generation,

nemo_rl/algorithms/distillation.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# limitations under the License.
1414
import os
1515
import warnings
16-
from typing import Any, NotRequired, Optional, TypedDict, TypeVar, cast
16+
from typing import Any, Callable, NotRequired, Optional, TypedDict, TypeVar, cast
1717

1818
import numpy as np
1919
import ray
@@ -44,6 +44,11 @@
4444
get_keys_from_message_log,
4545
)
4646
from nemo_rl.data.utils import load_dataloader_state
47+
from nemo_rl.data_plane.interfaces import DataPlaneConfig
48+
from nemo_rl.data_plane.transport_metrics import (
49+
add_byte_metric_derivatives,
50+
topk_payload_nbytes,
51+
)
4752
from nemo_rl.distributed.batched_data_dict import BatchedDataDict
4853
from nemo_rl.distributed.virtual_cluster import (
4954
ClusterConfig,
@@ -129,6 +134,7 @@ class MasterConfig(BaseModel, extra="allow"):
129134
logger: LoggerConfig # Logger configuration
130135
cluster: ClusterConfig # Cluster configuration
131136
checkpointing: CheckpointingConfig # Checkpointing configuration
137+
data_plane: Optional[DataPlaneConfig] = None
132138

133139

134140
# ===============================================================================
@@ -167,6 +173,7 @@ def setup(
167173
tokenizer: TokenizerType,
168174
train_dataset: AllTaskProcessedDataset,
169175
val_dataset: Optional[AllTaskProcessedDataset],
176+
student_policy_factory: Optional[Callable[..., ColocatablePolicyInterface]] = None,
170177
) -> tuple[
171178
ColocatablePolicyInterface, # student_policy
172179
ColocatablePolicyInterface, # teacher_policy
@@ -181,6 +188,14 @@ def setup(
181188
]:
182189
"""Main entry point for distillation algorithm.
183190
191+
Args:
192+
master_config: Fully resolved distillation configuration.
193+
tokenizer: Student tokenizer or processor.
194+
train_dataset: Training dataset.
195+
val_dataset: Optional validation dataset.
196+
student_policy_factory: Optional constructor for the student
197+
policy. ``None`` keeps the legacy plain ``Policy`` path.
198+
184199
Returns:
185200
tuple of student_policy, teacher_policy, student_generation,
186201
train_dataloader, val_dataloader,
@@ -456,7 +471,10 @@ def setup(
456471
)
457472
policy_config["megatron_cfg"]["train_iters"] = total_train_iters
458473

459-
student_policy = Policy(
474+
make_student_policy = (
475+
student_policy_factory if student_policy_factory is not None else Policy
476+
)
477+
student_policy = make_student_policy(
460478
name_prefix="student",
461479
cluster=train_cluster,
462480
config=policy_config,
@@ -745,6 +763,33 @@ def distillation_train(
745763
)
746764
train_data["teacher_topk_logits"] = teacher_topk["topk_logits"]
747765
train_data["teacher_topk_indices"] = teacher_topk["topk_indices"]
766+
teacher_topk_payload_bytes = topk_payload_nbytes(
767+
teacher_topk["topk_logits"],
768+
teacher_topk["topk_indices"],
769+
)
770+
teacher_topk_valid_payload_bytes = topk_payload_nbytes(
771+
teacher_topk["topk_logits"],
772+
teacher_topk["topk_indices"],
773+
[int(length) for length in input_lengths.tolist()],
774+
)
775+
transport_metrics: dict[str, float | int] = {
776+
"teacher_topk_payload_bytes": teacher_topk_payload_bytes,
777+
"teacher_topk_valid_payload_bytes": (
778+
teacher_topk_valid_payload_bytes
779+
),
780+
"teacher_topk_padding_overhead_bytes": max(
781+
teacher_topk_payload_bytes
782+
- teacher_topk_valid_payload_bytes,
783+
0,
784+
),
785+
"driver_rx_teacher_topk_bytes": teacher_topk_payload_bytes,
786+
"driver_tx_teacher_topk_bytes": teacher_topk_payload_bytes,
787+
"driver_teacher_topk_bytes": 2 * teacher_topk_payload_bytes,
788+
"driver_teacher_topk_bytes_avoided": 0,
789+
"tq_teacher_topk_write_bytes": 0,
790+
"tq_teacher_topk_write_ms_sum": 0.0,
791+
"tq_teacher_topk_write_ms_max": 0.0,
792+
}
748793

749794
print("▶ Preparing for training...", flush=True)
750795
with timer.time("training_prep"):
@@ -964,8 +1009,13 @@ def distillation_train(
9641009
timing_metrics["valid_tokens_per_sec_per_gpu"] = (
9651010
metrics["global_valid_toks"] / total_time / total_num_gpus
9661011
)
1012+
add_byte_metric_derivatives(
1013+
transport_metrics,
1014+
token_count=metrics["total_num_tokens"],
1015+
)
9671016
logger.log_metrics(metrics, total_steps + 1, prefix="train")
9681017
logger.log_metrics(timing_metrics, total_steps + 1, prefix="timing/train")
1018+
logger.log_metrics(transport_metrics, total_steps + 1, prefix="transport")
9691019

9701020
timer.reset()
9711021
current_step += 1

0 commit comments

Comments
 (0)