File size: 3,105 Bytes
dfb775d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
"""Auto model-card generation from a completed run.

Renders a HuggingFace-flavored `README.md` from the run's `XTrainConfig`
plus the eval JSON. Uses Jinja2 if installed; falls back to stdlib
`string.Template` so the base install (`uv sync` no extras) still works.
"""

from __future__ import annotations

import importlib.util
import json
from pathlib import Path
from string import Template
from typing import Any

_FALLBACK_TEMPLATE = Template(
    """---
license: apache-2.0
base_model: ${base_model}
tags:
- mindxtrain
- amd-mi300x
- fine-tuned
---

# ${run_name}

Fine-tuned with [mindxtrain](https://github.com/mindx/mindxtrain) on AMD MI300X.

- **Base model:** ${base_model}
- **Trainer:** ${backend}
- **Run id:** ${run_name}

## Evaluation

```json
${eval_json}
```

## Provenance

- BLAKE3 hashes of config / dataset / checkpoint / eval are recorded in the
  accompanying `manifest.json`.
- ROCm: 7.2.1 / gfx942
"""
)


_JINJA_TEMPLATE = """---
license: apache-2.0
base_model: {{ base_model }}
tags:
- mindxtrain
- amd-mi300x
- fine-tuned
---

# {{ run_name }}

Fine-tuned with [mindxtrain](https://github.com/mindx/mindxtrain) on AMD MI300X.

- **Base model:** {{ base_model }}
- **Trainer:** {{ backend }}
- **Run id:** {{ run_name }}

{% if hyperparams %}## Hyperparameters

{% for k, v in hyperparams.items() %}- **{{ k }}**: {{ v }}
{% endfor %}{% endif %}

## Evaluation

```json
{{ eval_json }}
```

## Provenance

- BLAKE3 hashes recorded in `manifest.json`.
- ROCm 7.2.1 / gfx942 (MI300X).
"""


def render_card(cfg: Any, eval_json: Path | None, out_path: Path) -> Path:
    """Write a `README.md` model card; return the path."""
    base_model = getattr(getattr(cfg, "model", None), "name", "unknown")
    run_name = getattr(getattr(cfg, "meta", None), "run_name", "run")
    backend = getattr(getattr(cfg, "train", None), "backend", "axolotl")

    eval_payload = "{}"
    if eval_json is not None and Path(eval_json).exists():
        try:
            eval_payload = json.dumps(json.loads(Path(eval_json).read_text()), indent=2)
        except (OSError, json.JSONDecodeError):
            pass

    out_path = Path(out_path)
    out_path.parent.mkdir(parents=True, exist_ok=True)

    if importlib.util.find_spec("jinja2") is not None:
        from jinja2 import Template as JinjaTemplate

        hyperparams = {
            "learning_rate": getattr(getattr(cfg.train, "optim", None), "learning_rate", None),
            "epochs": getattr(cfg.train, "num_epochs", None),
            "micro_batch_size": getattr(cfg.train, "micro_batch_size", None),
        }
        rendered = JinjaTemplate(_JINJA_TEMPLATE).render(
            base_model=base_model,
            run_name=run_name,
            backend=backend,
            hyperparams=hyperparams,
            eval_json=eval_payload,
        )
    else:
        rendered = _FALLBACK_TEMPLATE.substitute(
            base_model=base_model,
            run_name=run_name,
            backend=backend,
            eval_json=eval_payload,
        )

    out_path.write_text(rendered)
    return out_path


__all__ = ["render_card"]