Spaces:
Running
Running
Download datainventor/compose.py from codelion/data-inventor: direct link, hf CLI and curl.
- Browser
- Download file 4.48 kB
-
https://huggingface.co/spaces/codelion/data-inventor/resolve/main/datainventor/compose.py
- Command line
-
hf download hf://spaces/codelion/data-inventor/datainventor/compose.py
-
curl -L -o compose.py https://huggingface.co/spaces/codelion/data-inventor/resolve/main/datainventor/compose.py
4.48 kB
| """Compose: turn one prepared corpus into one or more training mixes that fit a token budget. | |
| Defaults come from our own 1B-token study (huggingface.co/blog/codelion/optimal-dataset-mixing): a static mix of | |
| 50% textbook-style text, 30% filtered web and 20% educational web beat every curriculum we tried at 70M parameters | |
| and 1B tokens. That is one model size and one budget, so treat it as a starting point and fork it. | |
| When a source is too small for its share, it is repeated up to `max_epochs` (about four epochs of repetition cost | |
| little in Muennighoff et al., 2023). Past that, the gap is filled by rephrasing that source's best documents, | |
| up to `max_synthetic_share` of the mix, since a mix that leans on synthetic text generalized worst in our study. | |
| """ | |
| from __future__ import annotations | |
| GROUP_OF_KIND = {"textbook": "textbook", "synthetic": "textbook", "web": "web", "multilingual": "web", | |
| "educational": "educational", "math": "math", "code": "code", "other": "web"} | |
| BASE_SHARES = {"textbook": 0.50, "web": 0.30, "educational": 0.20} | |
| def default_weights(sources: list[dict]) -> dict[str, float]: | |
| """Share per source name. Math and code, when present, take 10% each off the top; the rest follows 50/30/20 | |
| over the groups that exist, split evenly inside each group.""" | |
| groups: dict[str, list[str]] = {} | |
| for s in sources: | |
| groups.setdefault(GROUP_OF_KIND.get(s.get("kind", "web"), "web"), []).append(s["name"]) | |
| shares: dict[str, float] = {} | |
| fixed = {g: 0.10 for g in ("math", "code") if g in groups} | |
| rest = 1.0 - sum(fixed.values()) | |
| base = {g: v for g, v in BASE_SHARES.items() if g in groups} | |
| if not base: | |
| # only math and/or code sources: split evenly | |
| return {s["name"]: round(1 / len(sources), 4) for s in sources} | |
| tot = sum(base.values()) | |
| for g, v in base.items(): | |
| shares[g] = rest * v / tot | |
| shares.update(fixed) | |
| out = {} | |
| for g, names in groups.items(): | |
| for n in names: | |
| out[n] = round(shares[g] / len(names), 4) | |
| return out | |
| def plan_mix(budget_tokens: int, sources: list[dict], weights: dict[str, float], max_epochs: float = 4.0, | |
| max_synthetic_share: float = 0.25, rephrase_yield: float = 4.0, avg_out_tokens: int = 700) -> dict: | |
| """sources: [{name, kind, available_tokens (after cleaning and dedup), high_tokens (top tier)}]. | |
| Returns per-source rows and totals, including how much synthetic text to generate and roughly how many calls.""" | |
| tot_w = sum(weights.get(s["name"], 0) for s in sources) or 1.0 | |
| rows, synth_total, short_total = [], 0, 0 | |
| for s in sources: | |
| share = weights.get(s["name"], 0) / tot_w | |
| want = int(budget_tokens * share) | |
| avail = max(0, int(s.get("available_tokens", 0))) | |
| fresh = min(want, avail) | |
| repeat_cap = int(avail * max_epochs) | |
| from_repeat = min(want, repeat_cap) | |
| gap = want - from_repeat | |
| synth = min(gap, int(s.get("high_tokens", avail) * rephrase_yield)) if gap > 0 else 0 | |
| short = gap - synth | |
| epochs = round(from_repeat / avail, 2) if avail else 0.0 | |
| rows.append({"name": s["name"], "kind": s.get("kind", "web"), "share": round(share, 4), "want_tokens": want, | |
| "available_tokens": avail, "fresh_tokens": fresh, "epochs": epochs, "synthetic_tokens": synth, | |
| "short_tokens": short}) | |
| synth_total += synth | |
| short_total += short | |
| cap = int(budget_tokens * max_synthetic_share) | |
| warnings = [] | |
| if synth_total > cap: | |
| warnings.append(f"The plan needs {fmt(synth_total)} synthetic tokens, over the {int(max_synthetic_share * 100)}% cap " | |
| f"({fmt(cap)}). Add sources, lower the budget, or raise the cap knowingly.") | |
| if short_total: | |
| warnings.append(f"{fmt(short_total)} tokens cannot be filled even with repetition and rephrasing.") | |
| for r in rows: | |
| if r["epochs"] > 1.0: | |
| warnings.append(f"{r['name']} repeats {r['epochs']}x to reach its share.") | |
| return {"budget_tokens": budget_tokens, "rows": rows, "synthetic_tokens": synth_total, | |
| "synthetic_calls": synth_total // max(1, avg_out_tokens), "short_tokens": short_total, "warnings": warnings} | |
| def fmt(n: float) -> str: | |
| n = float(n) | |
| for unit, div in (("T", 1e12), ("B", 1e9), ("M", 1e6), ("K", 1e3)): | |
| if abs(n) >= div: | |
| return f"{n / div:.3g}{unit}" | |
| return f"{int(n)}" | |