Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions docs-next/public/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ Repo: https://github.com/modal-projects/modal-dojo
- [Gemma4_26B_A4B_Recipe](https://dojo.modal.dev/reference/gemma4_26b_a4b_recipe)
- [GLM_4_7_Recipe](https://dojo.modal.dev/reference/glm_4_7_recipe)
- [GLM_5_3_Flash_LoRA_Recipe](https://dojo.modal.dev/reference/glm_5_3_flash_lora_recipe)
- [GLM_5_3_Flash_Recipe](https://dojo.modal.dev/reference/glm_5_3_flash_recipe)
- [Inkling_Small_LoRA_Recipe](https://dojo.modal.dev/reference/inkling_small_lora_recipe)
- [Inkling_Small_Recipe](https://dojo.modal.dev/reference/inkling_small_recipe)
- [Kimi_K3_LoRA_Recipe](https://dojo.modal.dev/reference/kimi_k3_lora_recipe)
Expand Down
5 changes: 5 additions & 0 deletions modal_dojo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
"GLM_4_7_Recipe",
),
"GLM_5_3_Flash": ("modal_dojo.common.models", "GLM_5_3_Flash"),
"GLM_5_3_Flash_Recipe": (
"modal_dojo.train_recipes.miles_recipe",
"GLM_5_3_Flash_Recipe",
),
"GLM_5_3_Flash_LoRA": ("modal_dojo.common.models", "GLM_5_3_Flash_LoRA"),
"GLM_5_3_Flash_LoRA_Recipe": (
"modal_dojo.train_recipes.miles_recipe",
Expand Down Expand Up @@ -213,6 +217,7 @@
"GLM_4_7",
"GLM_4_7_Recipe",
"GLM_5_3_Flash",
"GLM_5_3_Flash_Recipe",
"GLM_5_3_Flash_LoRA",
"GLM_5_3_Flash_LoRA_Recipe",
"HarborDataset",
Expand Down
3 changes: 2 additions & 1 deletion modal_dojo/common/models/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from .deepseek_v41_flash import DeepSeek_V4_1_Flash
from .gemma4_26b_a4b import Gemma4_26B_A4B
from .glm_4_7 import GLM_4_7
from .glm_5_3_flash import GLM_5_3_Flash_LoRA
from .glm_5_3_flash import GLM_5_3_Flash, GLM_5_3_Flash_LoRA
from .inkling_small import Inkling_Small, Inkling_Small_LoRA
from .kimi_k3 import Kimi_K3
from .moonlight_16b_a3b_instruct import Moonlight_16B_A3B_Instruct
Expand Down Expand Up @@ -84,6 +84,7 @@ def find(cls, name: str) -> "_ValidationConfig":

VALIDATION_CONFIGS: set[_ValidationConfig] = {
_ValidationConfig("GLM-4.7", GLM_4_7, Framework.SLIME),
_ValidationConfig("GLM-5.3-Flash", GLM_5_3_Flash, Framework.MILES),
_ValidationConfig("GLM-5.3-Flash-LoRA", GLM_5_3_Flash_LoRA, Framework.MILES),
_ValidationConfig("Qwen3-0.6B", Qwen3_0_6B, Framework.SLIME),
_ValidationConfig("Qwen3-1.7B", Qwen3_1_7B, Framework.SLIME),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
image: radixark/miles:glm53next
file: mbridge/models/ext/deepseek_v3/dequant_fp8_safetensor_io.py

Keep safetensors reads on each torchrun rank's GPU; bare cuda selects GPU 0.
"""

from pathlib import Path

MARKER = "PATCHED_MODAL_DOJO_GLM53_FP8_DEVICE"


def patch_source(source: str) -> str:
if MARKER in source:
return source
old = 'device="cuda"'
if source.count(old) != 2:
raise ValueError("GLM FP8 reader changed: expected two CUDA device anchors")
# safetensors interprets bare 'cuda' as cuda:0, independent of set_device.
source = source.replace(old, 'device=f"cuda:{torch.cuda.current_device()}"')
return f"# {MARKER}\n" + source


def main() -> None:
from importlib.util import find_spec

path = (
Path(find_spec("mbridge").origin).parent
/ "models/ext/deepseek_v3/dequant_fp8_safetensor_io.py"
)
path.write_text(patch_source(path.read_text()))
print(f"Applied {MARKER} to {path}")


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
"""
image: radixark/miles:glm53next
commit: https://github.com/radixark/miles/commit/cc76e23915b2132ecfff65b97331fd83b02637e6
file: train.py, miles/backends/megatron_utils/{actor,model}.py

Adapt Dojo step and substep timing to this driver; the shared patch targets
an older Miles API.
"""

from __future__ import annotations

import ast
from pathlib import Path

MARKER = "PATCHED_MODAL_DOJO_GLM53_TIMING"
PREAMBLE = f"""# {MARKER}
import sys as _dojo_glm_sys
if '/root' not in _dojo_glm_sys.path:
_dojo_glm_sys.path.insert(0, '/root')
from modal_dojo.common.timing_recorder import (
recording_lane as _dojo_glm_lane,
recording_lane_on_reporting_rank as _dojo_glm_actor,
time_phase as _dojo_glm_phase,
)
from modal_dojo.frameworks.miles.phase_reporting import report_step_event as _dojo_glm_report
"""


def _wrap(source: str, spans: list[tuple[int, int, str]]) -> str:
lines = source.splitlines(keepends=True)
for start, end, header in sorted(spans, reverse=True):
indent = lines[start - 1][
: len(lines[start - 1]) - len(lines[start - 1].lstrip())
]
body = [
" " + line if line.strip() else line for line in lines[start - 1 : end]
]
lines[start - 1 : end] = [
*(indent + line + "\n" for line in header.splitlines()),
*body,
]
return "".join(lines)


def _function(source: str, name: str):
matches = [
node
for node in ast.walk(ast.parse(source))
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
and node.name == name
]
if len(matches) != 1:
raise ValueError(f"expected one {name} function, found {len(matches)}")
return matches[0]


def _wrap_function(source: str, name: str, header: str) -> str:
node = _function(source, name)
body = node.body
if isinstance(body[0], ast.Expr) and isinstance(body[0].value, ast.Constant):
body = body[1:]
return _wrap(source, [(body[0].lineno, body[-1].end_lineno, header)])


def _wrap_calls(source: str, mapping: dict[str, str]) -> str:
spans = []
found = set()
for node in ast.walk(ast.parse(source)):
if not isinstance(node, (ast.Expr, ast.Assign, ast.Return)):
continue
value = node.value
if isinstance(value, ast.Await):
value = value.value
if isinstance(value, ast.Call) and (name := ast.unparse(value.func)) in mapping:
found.add(name)
header = mapping[name]
if name == "update_weights" and not value.keywords:
header = "with _dojo_glm_lane('driver', None), _dojo_glm_phase('initial_weight_sync'):"
spans.append((node.lineno, node.end_lineno, header))
if missing := mapping.keys() - found:
raise ValueError(f"missing GLM timing calls: {sorted(missing)}")
return _wrap(source, spans)


def patch_source(source: str, target: str) -> str:
if MARKER in source:
return source
if target == "train.py":
source = _wrap_calls(
source,
{
"create_rollout_components": "with _dojo_glm_lane('driver', None), _dojo_glm_phase('initialize_rollouts'):",
"create_training_models": "with _dojo_glm_lane('driver', None), _dojo_glm_phase('initialize_train'):",
"rollout_executor.get.remote": "with _dojo_glm_phase('generate_rollouts'):\n _dojo_glm_report('generate_rollouts', args, rollout_id)",
"actor_model.train": "with _dojo_glm_phase('train_models'):",
"inference_controller.offload": "with _dojo_glm_phase('offload_rollout'):",
"offload_train": "with _dojo_glm_phase('offload_train'):",
"save": "with _dojo_glm_phase('checkpoint_save'):",
"update_weights": "with _dojo_glm_phase('weight_sync'):",
"eval_dispatcher.dispatch": "with _dojo_glm_phase('evaluate_rollouts'):",
},
)
tree = ast.parse(source)
loops = [
node
for node in ast.walk(tree)
if isinstance(node, ast.For) and ast.unparse(node.target) == "rollout_id"
]
if len(loops) != 1:
raise ValueError("expected one driver rollout loop")
loop = loops[0]
source = _wrap(
source,
[
(
loop.body[0].lineno,
loop.body[-1].end_lineno,
"with _dojo_glm_lane('driver', rollout_id):",
)
],
)
elif target == "actor.py":
source = _wrap_function(
source, "compute_log_prob", "with _dojo_glm_phase('compute_log_probs'):"
)
source = _wrap_function(
source,
"train",
"with _dojo_glm_actor(rollout_id, 'critic' if self.role == 'critic' else 'actor'):",
)
elif target == "model.py":
source = _wrap_calls(
source,
{
"run_forward_backward_pass": "with _dojo_glm_phase('forward_backward'):",
"optimizer.step": "with _dojo_glm_phase('optimizer_step'):",
},
)
else:
raise ValueError(f"unknown GLM timing target: {target}")
lines = source.splitlines(keepends=True)
# Keep module docstrings and future imports at the top.
insertion = 0
for node in ast.parse(source).body:
if (isinstance(node, ast.ImportFrom) and node.module == "__future__") or (
isinstance(node, ast.Expr)
and isinstance(node.value, ast.Constant)
and isinstance(node.value.value, str)
):
insertion = node.end_lineno
else:
break
lines.insert(insertion, PREAMBLE)
source = "".join(lines)
compile(source, target, "exec")
return source


def main(root: Path = Path("/root/miles")) -> None:
for relative in (
"train.py",
"miles/backends/megatron_utils/actor.py",
"miles/backends/megatron_utils/model.py",
):
path = root / relative
path.write_text(patch_source(path.read_text(), path.name))
print(f"Applied {MARKER} to {path}")


if __name__ == "__main__":
main()
4 changes: 4 additions & 0 deletions modal_dojo/train_recipes/miles_recipe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
from modal_dojo.train_recipes.miles_recipe.gemma4_26b_a4b import (
Gemma4_26B_A4B_Recipe,
)
from modal_dojo.train_recipes.miles_recipe.glm_5_3_flash import (
GLM_5_3_Flash_Recipe,
)
from modal_dojo.train_recipes.miles_recipe.glm_5_3_flash_lora import (
GLM_5_3_Flash_LoRA_Recipe,
)
Expand All @@ -26,6 +29,7 @@
"MilesRecipe",
"DeepSeek_V4_1_Flash_Recipe",
"Gemma4_26B_A4B_Recipe",
"GLM_5_3_Flash_Recipe",
"GLM_5_3_Flash_LoRA_Recipe",
"Inkling_Small_Recipe",
"Inkling_Small_LoRA_Recipe",
Expand Down
Loading
Loading