File size: 16,040 Bytes
cc857c8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import json, re, html as H
from collections import Counter
from datetime import datetime, timezone
NOW = datetime(2026, 10, 21, tzinfo=timezone.utc)
age = lambda s: (NOW - datetime.fromisoformat(s.replace('Z', '+00:00'))).days
D = json.load(open('land.json'))
MODELS = set(open('models_main.txt').read().split())
HF = {'MEMBER', 'OWNER', 'COLLABORATOR'}
BOTS = re.compile(r'(?i)bot|dependabot|github-actions|hf-security')
AREA_RULES = [  # (regex on path, area) — first match per file
 (r'^src/transformers/models/[^/]+/(image_processing|video_processing|feature_extraction|processing)_', 'processors (image/video/audio)'),
 (r'^src/transformers/models/', 'models'), (r'^src/transformers/generation/continuous_batching', 'serving / continuous batching'),
 (r'^src/transformers/generation/', 'generation'), (r'^src/transformers/(trainer|training_args|optimization)', 'trainer'),
 (r'tokenization', 'tokenization'), (r'^src/transformers/(quantizers/|integrations/(bitsandbytes|gptq|awq|hqq|quanto|torchao|fp_quant|compressed|fbgemm|mxfp4|eetq|higgs|spqr|vptq|bitnet|finegrained))', 'quantization'),
 (r'^src/transformers/integrations/(gguf|ggml)|gguf', 'gguf'), (r'^src/transformers/(integrations/(flash|sdpa|flex|hub_kernels|npu)|kernels/|modeling_flash)', 'attention / kernels'),
 (r'^src/transformers/pipelines/', 'pipelines'), (r'^src/transformers/(cli|commands)/', 'serving / continuous batching'),
 (r'^src/transformers/(image_processing|video_processing|feature_extraction|processing_utils|audio_utils|image_utils|video_utils)', 'processors (image/video/audio)'),
 (r'^src/transformers/(cache_utils|masking_utils)', 'cache / masks'), (r'^src/transformers/(modeling_utils|core_model_loading|conversion_mapping|configuration_utils|safetensors|modeling_rope)', 'core / loading'),
 (r'^src/transformers/(distributed|integrations/(tensor_parallel|fsdp|deepspeed|accelerate|moe))', 'distributed'),
 (r'^src/transformers/exporters|executorch|onnx', 'exporters'), (r'^src/transformers/layer_execution', 'layer execution'), (r'^src/transformers/loss/', 'trainer'), (r'^src/transformers/(testing_utils)', 'tests'), (r'^src/transformers/(utils/|__init__|hf_argparser|activations|pytorch_utils|modeling_outputs|dependency_versions)', 'core / utils'), (r'^src/transformers/image_transforms', 'processors (image/video/audio)'), (r'^src/transformers/integrations/', 'integrations (other)'), (r'^docs/', 'docs'), (r'^tests/', 'tests'),
 (r'^(\.github|\.circleci|docker|utils|benchmark|Makefile|setup|pyproject)', 'CI / tooling'), (r'^examples/', 'examples'), (r'.', 'other')]
ISSUE_KW = [(r'(?i)\bgguf\b', 'gguf'), (r'(?i)export|onnx|executorch|torch\.compile|compile', 'exporters'), (r'(?i)For(Sequence|Token)Classification|QuestionAnswering|classification head|detection head|\bheads?\b', 'models'), (r'(?i)\bloss\b|label smoothing|muon|save_strategy|optimi[sz]er', 'trainer'), (r'(?i)typing|FloatTensor|RopeParameters|import_utils', 'core / utils'), (r'(?i)quantiz|bitsandbytes|bnb|awq|gptq|fp8|int4|int8|torchao|mxfp4|compressed.tensors', 'quantization'),
 (r'(?i)tokeni[sz]|chat.template|special token|vocab', 'tokenization'), (r'(?i)trainer|training|fine.?tun|deepspeed|fsdp|gradient|optimizer|lr sched', 'trainer'),
 (r'(?i)generat|beam|sampling|logits|stopping|streamer|speculat|assist', 'generation'), (r'(?i)\bcache\b|kv.cache|past_key|mask', 'cache / masks'),
 (r'(?i)flash.?attn|flash.?attention|sdpa|flex.?attention|kernel|attention impl', 'attention / kernels'),
 (r'(?i)tensor.parallel|\btp\b|expert.parallel|distributed|multi.?gpu|device_map|accelerate', 'distributed'),
 (r'(?i)pipeline', 'pipelines'), (r'(?i)serve|continuous.batch|generate_batch|\bcli\b', 'serving / continuous batching'),
 (r'(?i)image.?processor|processor|feature.extract|video|audio|vision', 'processors (image/video/audio)'),
 (r'(?i)\bdocs?\b|documentation|translat|typo', 'docs'), (r'(?i)from_pretrained|save_pretrained|load|safetensors|checkpoint|hub', 'core / loading'),
 (r'(?i)\btests?\b|\bci\b|flaky', 'tests')]
def pr_area(p):
    files = [f['path'] for f in (p.get('files') or {}).get('nodes', [])]
    c = Counter()
    for f in files:
        for rx, a in AREA_RULES:
            if re.search(rx, f): c[a] += 1; break
    if not c: return 'other', files
    top = c.most_common(1)[0][0]
    if top == 'tests' and len(c) > 1: top = [a for a, _ in c.most_common() if a != 'tests'][0]
    return top, files
def models_in(files, title):
    s = {m.group(1) for f in files for m in [re.match(r'src/transformers/models/([^/]+)/', f)] if m}
    sq = re.sub(r'[^a-z0-9]', '', title.lower())
    s |= {m for m in MODELS if len(m.replace('_','')) > 3 and re.search(r'(?<![a-z0-9])' + re.escape(m.replace('_','')) + r'(?![a-z0-9])', re.sub(r'[-_ ]', '', title.lower()))}
    s |= {m for m in MODELS if len(m) > 4 and '_' in m and m.replace('_','') in sq}
    return s - {'auto', 'deprecated'}
rows = []
for p in D['prs']:
    area, files = pr_area(p); t = p['title']
    newdirs = {m.group(1) for f in files for m in [re.match(r'src/transformers/models/([^/]+)/modeling_', f)] if m and m.group(1) not in MODELS}
    kind = 'new model' if newdirs or re.search(r'(?i)^\[?add(ing)? (support for )?[A-Z]|new model', t) and area == 'models' else \
           'fix' if re.search(r'(?i)\bfix|bug|error|crash|wrong|broken|regress', t) else \
           'docs' if area == 'docs' else 'CI / tooling' if area in ('CI / tooling', 'tests') else 'feature / refactor'
    rd = p.get('reviewDecision')
    status = 'draft' if p['isDraft'] else 'approved' if rd == 'APPROVED' else 'changes requested' if rd == 'CHANGES_REQUESTED' else \
             'conflicting' if p.get('mergeable') == 'CONFLICTING' else 'awaiting review'
    au = (p['author'] or {}).get('login') or ''
    who = 'bot' if BOTS.search(au) else 'HF' if p['authorAssociation'] in HF else 'first-timer' if p['authorAssociation'] in ('FIRST_TIME_CONTRIBUTOR', 'FIRST_TIMER', 'NONE') else 'contributor'
    rows.append(dict(k='PR', n=p['number'], t=t, area=area, kind=kind, status=status, who=who, au=au, age=age(p['createdAt']), idle=age(p['updatedAt']),
                     size=p['additions'] + p['deletions'], models=sorted(models_in(files, t)), lab=[l['name'] for l in p['labels']['nodes']], react=p['reactions']['totalCount']))
for i in D['issues']:
    t = i['title']; lab = [l['name'] for l in i['labels']['nodes']]
    area = 'models' if 'New model' in lab else next((a for rx, a in ISSUE_KW if re.search(rx, t + ' ' + ' '.join(lab))), 'models' if models_in([], t) else 'other')
    kind = 'new model' if 'New model' in lab else 'feature' if 'Feature request' in lab else 'bug' if ('bug' in lab or re.search(r'(?i)error|bug|fail|crash|wrong|broken|not work|issue with|incorrect|mismatch|regression|nan\b|oom', t)) else \
           'docs' if area == 'docs' else 'tracker / RFC' if re.search(r'(?i)rfc|tracker|roadmap|\[.*(call|community).*\]|meta', t) else 'question / other'
    au = (i['author'] or {}).get('login') or ''
    who = 'bot' if BOTS.search(au) else 'HF' if i['authorAssociation'] in HF else 'community'
    status = 'active <14d' if age(i['updatedAt']) < 14 else 'idle 14–90d' if age(i['updatedAt']) < 90 else 'idle >90d'
    rows.append(dict(k='issue', n=i['number'], t=t, area=area, kind=kind, status=status, who=who, au=au, age=age(i['createdAt']), idle=age(i['updatedAt']),
                     size=i['comments']['totalCount'], models=sorted(models_in([], t)), lab=lab, react=i['reactions']['totalCount']))
json.dump(rows, open('land_rows.json', 'w'))
P = [r for r in rows if r['k'] == 'PR']; I = [r for r in rows if r['k'] == 'issue']
bucket = lambda d: '<30d' if d < 30 else '30–90d' if d < 90 else '90–180d' if d < 180 else '180d–1y' if d < 365 else '>1y'
AGE = ['<30d', '30–90d', '90–180d', '180d–1y', '>1y']
def bars(title, cnt, order=None, total=None, key=None):
    items = [(k, cnt.get(k, 0)) for k in order] if order else cnt.most_common()
    mx = max([v for _, v in items] or [1]); total = total or sum(cnt.values())
    li = ''.join(f"<tr data-f='{key}' data-v='{H.escape(k)}' title='{H.escape(k)}: {v} ({100*v/total:.0f}%)'><td class='l'>{H.escape(k)}</td>"
                 f"<td class='b'><span style='width:{100*v/mx:.1f}%'></span></td><td class='v'>{v}</td><td class='p'>{100*v/total:.0f}%</td></tr>" for k, v in items if v)
    return f"<section class='card'><h3>{title}</h3><table class='bars'>{li}</table></section>"
mc = Counter(m for r in rows for m in r['models']); mcp = Counter(m for r in P for m in r['models']); mci = Counter(m for r in I for m in r['models'])
topm = ''.join(f"<tr data-f='model' data-v='{m}'><td class='l'>{m}</td><td class='b'><span style='width:{100*v/mc.most_common(1)[0][1]:.0f}%'></span></td><td class='v'>{v}</td><td class='p'>{mcp[m]} PR · {mci[m]} iss</td></tr>" for m, v in mc.most_common(20))
tiles = (f"<div class='tiles'><div class='tile'><b>{len(I)}</b>open issues</div><div class='tile'><b>{len(P)}</b>open PRs</div>"
         f"<div class='tile'><b>{sum(r['status']=='awaiting review' for r in P)}</b>PRs awaiting review</div>"
         f"<div class='tile'><b>{sum(r['status']=='approved' for r in P)}</b>PRs approved, not merged</div>"
         f"<div class='tile'><b>{sum(r['kind']=='bug' for r in I)}</b>bug reports</div>"
         f"<div class='tile'><b>{sum(r['idle']<14 for r in rows)}</b>active in last 14d</div></div>")
SEC = (tiles +
 "<h2>Pull requests</h2><div class='grid'>" + bars('By area (from files touched)', Counter(r['area'] for r in P), key='area') + bars('By status', Counter(r['status'] for r in P), ['awaiting review','approved','changes requested','conflicting','draft'], key='status') +
 bars('By kind', Counter(r['kind'] for r in P), key='kind') + bars('Author', Counter(r['who'] for r in P), ['HF','contributor','first-timer','bot'], key='who') + bars('Age (opened)', Counter(bucket(r['age']) for r in P), AGE) + "</div>" +
 "<h2>Issues</h2><div class='grid'>" + bars('By area (labels + title)', Counter(r['area'] for r in I), key='area') + bars('By kind', Counter(r['kind'] for r in I), key='kind') +
 bars('Activity', Counter(r['status'] for r in I), ['active <14d','idle 14–90d','idle >90d'], key='status') + bars('Author', Counter(r['who'] for r in I), ['HF','community','bot'], key='who') + bars('Age (opened)', Counter(bucket(r['age']) for r in I), AGE) + "</div>" +
 "<h2>Most-touched models (PRs + issues)</h2><div class='grid'><section class='card wide'><table class='bars'>" + topm + "</table></section></div>")
AREAS = sorted({r['area'] for r in rows})
page = """<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Open Backlog Map</title>
<style>
:root{--bg:#fafaf9;--fg:#1c1917;--mut:#78716c;--card:#fff;--bd:#e7e5e4;--bar:#2563eb;--acc:#2563eb}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:#1c1917;--fg:#f5f5f4;--mut:#a8a29e;--card:#292524;--bd:#44403c;--bar:#60a5fa;--acc:#60a5fa}}
:root[data-theme="dark"]{--bg:#1c1917;--fg:#f5f5f4;--mut:#a8a29e;--card:#292524;--bd:#44403c;--bar:#60a5fa;--acc:#60a5fa}
body{background:var(--bg);color:var(--fg);font:14px/1.45 system-ui,sans-serif;margin:0 auto;padding:16px;max-width:1200px}
h1{font-size:20px;margin:4px 0}h2{font-size:16px;margin:24px 0 8px}h3{font-size:13px;margin:0 0 6px;color:var(--mut);font-weight:600}
.tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px;margin:12px 0}.tile{background:var(--card);border:1px solid var(--bd);border-radius:8px;padding:10px;color:var(--mut);font-size:12px}.tile b{display:block;font-size:26px;color:var(--fg)}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(340px,1fr));gap:10px}.card{background:var(--card);border:1px solid var(--bd);border-radius:8px;padding:10px 12px}.wide{grid-column:1/-1}
.bars{width:100%;border-collapse:collapse}.bars tr{cursor:pointer}.bars tr:hover td{background:color-mix(in srgb,var(--acc) 8%,transparent)}.bars td{padding:2px 4px;font-size:12px;white-space:nowrap}
.l{width:34%;overflow:hidden;text-overflow:ellipsis;max-width:180px}.b{width:46%}.b span{display:block;height:10px;background:var(--bar);border-radius:0 4px 4px 0;min-width:2px}.v{text-align:right;font-variant-numeric:tabular-nums}.p{text-align:right;color:var(--mut)}
#flt{margin:16px 0 6px;display:flex;gap:8px;flex-wrap:wrap;align-items:center}select,input{background:var(--card);color:var(--fg);border:1px solid var(--bd);border-radius:4px;padding:4px}
#tbl{width:100%;border-collapse:collapse;font-size:12px}#tbl th,#tbl td{border-bottom:1px solid var(--bd);padding:3px 6px;text-align:left;vertical-align:top}#tbl th{cursor:pointer;position:sticky;top:0;background:var(--bg)}
a{color:var(--acc)}.wrap{overflow-x:auto}
</style></head><body>
<h1>transformers open backlog — what's left</h1><div style="color:var(--mut)">as of 2026-10-21 · after the Oct 20–21 cleanup · click any bar to filter the table below</div>
__SEC__
<h2>Every open item</h2><div id="flt">
<select id="fk"><option value="">PRs + issues</option><option>PR</option><option>issue</option></select>
<select id="fa"><option value="">all areas</option>__AREAS__</select>
<input id="fq" placeholder="search title / model / author"><span id="fc" style="color:var(--mut)"></span><button id="fx">clear</button></div>
<div class="wrap"><table id="tbl"><thead><tr><th data-s="k">type</th><th data-s="n">#</th><th data-s="t">title</th><th data-s="area">area</th><th data-s="kind">kind</th><th data-s="status">status</th><th data-s="who">author</th><th data-s="age">age d</th><th data-s="idle">idle d</th><th data-s="models">models</th></tr></thead><tbody></tbody></table></div>
<script>
const ROWS=__ROWS__;let F={},sortK='idle',dir=1;
const U='https://github.com/huggingface/transformers/';
function draw(){const k=fk.value,a=fa.value,q=fq.value.toLowerCase();
 let r=ROWS.filter(x=>(!k||x.k===k)&&(!a||x.area===a)&&Object.entries(F).every(([f,v])=>f==='model'?x.models.includes(v):x[f]===v)&&(!q||(x.t+' '+x.models.join(' ')+' '+x.au).toLowerCase().includes(q)));
 r.sort((x,y)=>{const A=x[sortK],B=y[sortK];return (A>B?1:A<B?-1:0)*dir});
 fc.textContent=r.length+' items'+(Object.keys(F).length?' · filter: '+Object.entries(F).map(e=>e.join('=')).join(', '):'');
 const tb=document.querySelector('#tbl tbody');tb.innerHTML='';
 for(const x of r.slice(0,1500)){const tr=document.createElement('tr');
  const cells=[x.k,'',x.t,x.area,x.kind,x.status,x.who+(x.au?' @'+x.au:''),x.age,x.idle,x.models.join(', ')];
  cells.forEach((c,i)=>{const td=document.createElement('td');if(i===1){const l=document.createElement('a');l.href=U+(x.k==='PR'?'pull/':'issues/')+x.n;l.target='_blank';l.textContent='#'+x.n;td.appendChild(l)}else td.textContent=c;tr.appendChild(td)});tb.appendChild(tr)}}
document.querySelectorAll('.bars tr').forEach(tr=>tr.onclick=()=>{const f=tr.dataset.f,v=tr.dataset.v;if(!f||f==='undefined')return;
 const sec=tr.closest('.grid')?.previousElementSibling?.textContent||'';if(sec.startsWith('Pull'))fk.value='PR';else if(sec.startsWith('Issues'))fk.value='issue';
 if(f==='area'){fa.value=v;delete F.area}else F[f]=v;draw();document.getElementById('flt').scrollIntoView({behavior:'smooth'})});
document.querySelectorAll('#tbl th').forEach(th=>th.onclick=()=>{const s=th.dataset.s;dir=sortK===s?-dir:1;sortK=s;draw()});
fk.onchange=fa.onchange=fq.oninput=draw;fx.onclick=()=>{F={};fk.value='';fa.value='';fq.value='';draw()};draw();
</script></body></html>"""
page = page.replace('__SEC__', SEC).replace('__AREAS__', ''.join(f'<option>{H.escape(a)}</option>' for a in AREAS)).replace('__ROWS__', json.dumps(rows).replace('</', '<\\/'))
open('backlog.html', 'w').write(page)
print(len(I), 'issues', len(P), 'PRs')
print('PR area', Counter(r['area'] for r in P).most_common()); print('PR status', Counter(r['status'] for r in P)); print('PR kind', Counter(r['kind'] for r in P))
print('issue area', Counter(r['area'] for r in I).most_common()); print('issue kind', Counter(r['kind'] for r in I))
print('top models', mc.most_common(12))