File size: 12,229 Bytes
4e10f5a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e76146d
4e10f5a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# /// script
# dependencies = ["huggingface_hub>=0.35", "requests"]
# ///
"""Daily radar: transformers-related issues + monkey patches in downstream repos -> static Space.

Run:      HF_TOKEN=... GITHUB_TOKEN=... uv run tracker.py            (DRY=1 to write index.html locally instead of pushing)
Schedule: hf jobs scheduled uv run daily https://huggingface.co/spaces/$SPACE/resolve/main/tracker.py --secrets HF_TOKEN  (GITHUB_TOKEN optional: 3x faster search)
"""

import json
import os
import re
import subprocess
import tempfile
import time
from collections import defaultdict
from datetime import date, timedelta
from html import escape
from pathlib import Path

import requests
from huggingface_hub import HfApi, InferenceClient, hf_hub_download

# Heavy transformers users, ordered by GitHub stars (2026-11). Picked from stars >5k or pypi >300k/month.
REPOS = [
    "ggml-org/llama.cpp", "vllm-project/vllm", "unslothai/unsloth", "hiyouga/LlamaFactory",
    "deepspeedai/DeepSpeed", "hpcaitech/ColossalAI", "sgl-project/sglang", "huggingface/diffusers",
    "huggingface/lerobot", "huggingface/open-r1", "m-bain/whisperX", "verl-project/verl", "huggingface/peft",
    "huggingface/trl", "kvcache-ai/ktransformers", "huggingface/sentence-transformers", "NVIDIA-NeMo/Automodel",
    "modelscope/ms-swift", "dottxt-ai/outlines", "NVIDIA/TensorRT-LLM", "flairNLP/flair",
    "EleutherAI/lm-evaluation-harness", "axolotl-ai-cloud/axolotl", "FlagOpen/FlagEmbedding",
    "OpenRLHF/OpenRLHF", "huggingface/accelerate", "THUDM/slime", "bitsandbytes-foundation/bitsandbytes",
    "ml-explore/mlx-lm", "arcee-ai/mergekit", "linkedin/Liger-Kernel", "Blaizzy/mlx-vlm",
    "EvolvingLMMs-Lab/lmms-eval", "urchade/GLiNER", "vllm-project/llm-compressor", "allenai/open-instruct",
    "huggingface/optimum", "embeddings-benchmark/mteb", "alibaba/ROLL", "PrimeIntellect-ai/prime-rl",
    "intel/auto-round", "ModelCloud/GPTQModel", "huggingface/optimum-quanto", "unslothai/unsloth-zoo",
]  # fmt: skip
SPACE = os.environ.get("SPACE", "ModelAdders/transformers-downstream-tracker")
MODEL = os.environ.get("MODEL", "Qwen/Qwen3-235B-A22B-Instruct-2507")
DAYS = int(os.environ.get("DAYS", 2))  # ponytail: overlap between daily runs is fine
DRY = os.environ.get("DRY") == "1"

# ponytail: regex heuristic, catches `transformers.x.y = ...`, `modeling_llama.Foo.forward = ...`, setattr on
# transformers modules, and `LlamaAttention.forward = ...`. Misses aliased imports; AST pass if that matters.
PATCH_RE = re.compile(
    r"^\s*(transformers(\.\w+)+|modeling_\w+(\.\w+)+"
    r"|[A-Z]\w*(Attention|MLP|Model|ForCausalLM|DecoderLayer|RMSNorm|PreTrainedModel|Embedding)\w*\.\w+)\s*=[^=]"
    r"|setattr\(\s*(transformers|modeling_)"
)
MODEL_CLS = re.compile(
    r"^(?!PreTrainedModel)[A-Z]\w*?(?=(ForCausalLM|ForConditionalGeneration|SdpaAttention|FlashAttention2|Attention"
    r"|MLP|DecoderLayer|RMSNorm|RotaryEmbedding|SparseMoeBlock|Experts|Model)\b)"
)


def issues():
    # GitHub search, not GH Archive: the public events feed is heavily sampled (~40 of ~650 daily vllm events).
    since = (date.today() - timedelta(days=DAYS)).isoformat()
    headers = {"Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}"} if "GITHUB_TOKEN" in os.environ else {}
    rows = []
    for i, repo in enumerate(REPOS, 1):
        q = f"repo:{repo} transformers in:title,body,comments updated:>={since}"
        r = requests.get(
            "https://api.github.com/search/issues", params={"q": q, "per_page": 100, "sort": "updated"},
            headers=headers, timeout=60,
        )  # fmt: skip
        if not r.ok:  # renamed/private repo shouldn't kill the whole report
            print(f"[{i}/{len(REPOS)}] {repo}: search failed {r.status_code}", flush=True)
            continue
        # ponytail: first 100 most recently updated only; vllm hits the cap, paginate if the tail matters
        items = r.json()["items"]
        rows += [
            {"repo_name": repo, "number": it["number"], "t": it["title"], "b": it["body"], "comments": it["comments"],
             "is_pr": "pull_request" in it}
            for it in items
        ]  # fmt: skip
        print(f"[{i}/{len(REPOS)}] {repo}: {len(items)} threads", flush=True)
        time.sleep(2.1 if headers else 6.5)  # search API: 30 req/min authenticated, 10 anonymous
    return sorted(rows, key=lambda r: -r["comments"])


def patches():
    found = set()
    for i, repo in enumerate(REPOS, 1):
        with tempfile.TemporaryDirectory() as dst:  # one at a time, keeps job disk small
            clone = ["git", "clone", "-q", "--depth", "1", "--filter=blob:limit=1m", f"https://github.com/{repo}", dst]
            if subprocess.run(clone).returncode:
                print(f"[{i}/{len(REPOS)}] {repo}: clone failed", flush=True)
                continue
            files = subprocess.run(["git", "ls-files", "*.py"], cwd=dst, capture_output=True, text=True).stdout
            # key on file + stripped line so line-number churn doesn't count as a new patch
            hits = {
                f"{repo}:{f}: {line.strip()}"
                for f in files.split()
                if "test" not in f and Path(dst, f).exists()
                for line in Path(dst, f).read_text(errors="ignore").splitlines()
                if PATCH_RE.search(line)
            }
            found |= hits
            print(f"[{i}/{len(REPOS)}] {repo}: {len(hits)} patches", flush=True)
    return found


def target(key):
    """`repo:file: modeling_llama.LlamaAttention.forward = f` -> `*Attention.forward` (what transformers symbol is patched)."""
    code = key.split(": ", 1)[1]
    m = re.search(r"setattr\(\s*([\w.]+)\s*,\s*['\"](\w+)", code)
    sym = f"{m[1]}.{m[2]}" if m else code.split("=")[0].strip()
    sym = re.sub(r"^(transformers\.)?(models\.\w+\.)?(\w*modeling_\w+\.?)?", "", sym) or "<module>"
    return MODEL_CLS.sub("*", sym)


def triage(rows):
    """LLM groups threads by root cause and says what transformers should do. Returns list of dicts or None."""
    listing = "\n".join(
        f"- {r['repo_name']}#{r['number']} ({'PR' if r['is_pr'] else 'issue'}, {r['comments']} comments): "
        f"{r['t']}\n  {(r['b'] or '')[:300]!r}"
        for r in rows[:200]
    )
    prompt = (
        "You triage downstream breakage for the huggingface/transformers maintainers. Below are recent issues/PRs from "
        "libraries that depend on transformers. Group them by root cause. Drop anything not caused by or about "
        "transformers (env dumps that merely list a version do not count). Reply with ONLY a JSON array, most urgent "
        'first, items: {"severity": "high|medium|low", "action": "fix in transformers|reply/guide users|watch", '
        '"component": "e.g. tokenizers, generate, from_pretrained, attention, model:<name>, trainer", '
        '"summary": "one line, in transformers terms", "refs": ["owner/repo#N", ...]}\n\n' + listing
    )
    try:
        out = InferenceClient().chat_completion([{"role": "user", "content": prompt}], model=MODEL, max_tokens=4000)
        return json.loads(re.search(r"\[.*\]", out.choices[0].message.content, re.S)[0])
    except Exception as e:  # LLM is a nicety; raw table still rendered
        print(f"triage failed: {e}", flush=True)
        return None


def link(ref):
    repo, n = ref.split("#")
    return f'<a href="https://github.com/{repo}/issues/{n}">{escape(ref)}</a>'


def patch_link(key):
    repo, rest = key.split(":", 1)
    f, code = rest.split(": ", 1)
    return f'<a href="https://github.com/{repo}/blob/HEAD/{f}">{escape(repo)}/{escape(f)}</a>', f"<code>{escape(code)}</code>"


def table(head, rows):
    th = "".join(f"<th>{h}</th>" for h in head)
    return f"<table><tr>{th}</tr>" + "".join("<tr>" + "".join(f"<td>{c}</td>" for c in r) + "</tr>" for r in rows) + "</table>"


def render(rows, groups, now, new, gone):
    sev = {"high": "🔴", "medium": "🟠", "low": "⚪"}
    h = [f"<h1>transformers downstream radar · {date.today()}</h1>",
         f"<p>{len(REPOS)} repos · last {DAYS} days of GitHub activity · {len(now)} monkey patches tracked</p>"]  # fmt: skip

    h.append("<h2>Act on these</h2>")
    if groups is not None:
        groups = sorted(groups, key=lambda g: ["high", "medium", "low"].index(g.get("severity", "low")))
        h.append(table(["", "action", "component", "what", "threads"], [
            [sev.get(g.get("severity"), ""), escape(g.get("action", "")), escape(g.get("component", "")),
             escape(g.get("summary", "")), " ".join(link(r) for r in g.get("refs", []) if "#" in r)]
            for g in groups if g.get("action") != "watch"
        ]))  # fmt: skip
    h.append(f"<details><summary>All {len(rows)} matching threads</summary>")
    h.append(table(["thread", "comments", "title"], [
        [link(f"{r['repo_name']}#{r['number']}"), r["comments"], escape(r["t"] or "")] for r in rows
    ]))  # fmt: skip
    h.append("</details>")

    h.append(f"<h2>New monkey patches since last run ({len(new)})</h2>")
    h.append("<p>Something changed on their side or ours: check whether a transformers change forced it.</p>")
    h.append(table(["where", "patch"], [patch_link(k) for k in new]) if new else "<p>None.</p>")
    if gone:
        h.append(f"<details><summary>Removed since last run ({len(gone)})</summary>")
        h.append(table(["where", "patch"], [patch_link(k) for k in gone]) + "</details>")

    by_target = defaultdict(set)
    for k in now:
        by_target[target(k)].add(k.split(":", 1)[0])
    top = sorted(by_target.items(), key=lambda kv: (-len(kv[1]), kv[0]))[:40]
    h.append("<h2>Most-patched transformers internals</h2>")
    h.append("<p>Patched by several repos = missing extension point (kernel hub / attention interface / hook).</p>")
    h.append(table(["symbol", "#repos", "repos"], [
        [f"<code>{escape(t)}</code>", len(rs), ", ".join(r.split("/")[1] for r in sorted(rs))] for t, rs in top
    ]))  # fmt: skip

    threads = defaultdict(int)
    for r in rows:
        threads[r["repo_name"]] += 1
    count = lambda keys, repo: sum(k.startswith(repo + ":") for k in keys)
    h.append("<h2>Per repo</h2>")
    h.append(table(["repo", "threads", "patches", "new patches"], [
        [f'<a href="https://github.com/{r}">{r}</a>', threads[r], count(now, r), count(new, r)] for r in REPOS
    ]))  # fmt: skip
    h.append(f"<details><summary>All {len(now)} patches</summary>")
    h.append(table(["where", "patch"], [patch_link(k) for k in sorted(now)]) + "</details>")

    css = (
        "body{max-width:1200px;margin:auto;padding:1em;font-family:system-ui}table{border-collapse:collapse;width:100%}"
        "td,th{border-bottom:1px solid #ddd;padding:4px 8px;text-align:left;vertical-align:top}code{font-size:85%}"
    )
    return f"<!doctype html><meta charset=utf-8><title>transformers radar</title><style>{css}</style>" + "\n".join(h)


def main():
    rows = issues()
    print(f"{len(rows)} transformers-related threads in last {DAYS}d", flush=True)
    groups = triage(rows) if rows else []
    print(f"triaged into {len(groups) if groups is not None else '?'} groups", flush=True)
    now = patches()
    try:
        before = set(json.load(open(hf_hub_download(SPACE, "patches.json", repo_type="space"))))
    except Exception:  # first run: nothing to diff against
        before = now
    new, gone = sorted(now - before), sorted(before - now)
    html = render(rows, groups, now, new, gone)

    if DRY:
        Path("index.html").write_text(html)
        print("wrote index.html", flush=True)
        return
    api = HfApi()
    api.create_repo(SPACE, repo_type="space", space_sdk="static", exist_ok=True)
    with tempfile.TemporaryDirectory() as tmp:
        Path(tmp, "index.html").write_text(html)
        Path(tmp, "patches.json").write_text(json.dumps(sorted(now), indent=1))
        Path(tmp, "reports").mkdir()
        Path(tmp, f"reports/{date.today()}.json").write_text(json.dumps({"threads": rows, "groups": groups}, indent=1))
        api.upload_folder(folder_path=tmp, repo_id=SPACE, repo_type="space", commit_message=f"radar {date.today()}")
    print(f"pushed https://huggingface.co/spaces/{SPACE}", flush=True)


if __name__ == "__main__":
    main()