bit-atlas / src /lineage.py
Bit-Trading-Company's picture
CI deploy local
895687d verified
Raw
History Blame Contribute Delete
4.71 kB
"""Model lineage, walked over the `base_model` column.
Every model in the index may declare one base model. That makes the index a
forest, and this module is how the Space reads it: ancestors of a model,
descendants of a model, and the biggest family in the index (the design's
"lineage spotlight", which on real Hub data is the FinBERT tree).
**Every walk here is cycle-safe.** `base_model` is author-declared metadata,
not a verified relation, and the Hub does not stop anyone from declaring a
cycle -- A says it is finetuned from B while B says it is finetuned from A.
Two models that each claim the other would spin an unguarded walk forever and
hang the Space. So every traversal carries a `seen` set and stops the moment it
revisits a node, and every walk is additionally depth-capped.
The design draws at most a handful of generations, so MAX_DEPTH is a display
bound as much as a safety one.
"""
from __future__ import annotations
MAX_DEPTH = 24
def build_parents(rows) -> dict:
"""{model_id: declared base model} for rows whose parent is in the index.
A base model that is not itself indexed is kept -- the design shows
"fine-tuned from X" even when X is a general-purpose model outside the
Atlas -- but it terminates the walk, since there is no row to step to.
"""
return {
row["id"]: (row.get("base_model") or "")
for row in rows
if row.get("id")
}
def build_children(parents: dict) -> dict:
"""Invert the parent map: {model_id: [direct children]}."""
children: dict = {}
for child, parent in parents.items():
if not parent or parent == child:
continue
children.setdefault(parent, []).append(child)
for kids in children.values():
kids.sort()
return children
def ancestors(model_id: str, parents: dict, max_depth: int = MAX_DEPTH) -> list:
"""Walk up the tree from `model_id`, nearest parent first.
Stops on: no declared parent, a parent outside the index, the depth cap,
or a node already visited. That last condition is what makes a circular
declaration terminate instead of hanging.
"""
chain = []
seen = {model_id}
current = model_id
for _ in range(max_depth):
parent = parents.get(current) or ""
if not parent or parent in seen:
break
chain.append(parent)
seen.add(parent)
if parent not in parents:
# Declared but not indexed: it is a real ancestor and worth
# showing, but there is nothing further to walk to.
break
current = parent
return chain
def root_of(model_id: str, parents: dict, max_depth: int = MAX_DEPTH) -> str:
chain = ancestors(model_id, parents, max_depth)
return chain[-1] if chain else model_id
def descendants(model_id: str, children: dict, max_depth: int = MAX_DEPTH) -> list:
"""Every model below `model_id`, breadth-first, each returned once.
Breadth-first so the design's tree lists direct children before
grandchildren. `seen` guards both diamonds (two paths to one node) and
cycles.
"""
out = []
seen = {model_id}
frontier = [model_id]
for _ in range(max_depth):
following = []
for node in frontier:
for child in children.get(node, ()):
if child in seen:
continue
seen.add(child)
out.append(child)
following.append(child)
if not following:
break
frontier = following
return out
def direct_children(model_id: str, children: dict) -> list:
return list(children.get(model_id, ()))
def siblings(model_id: str, parents: dict, children: dict) -> list:
"""Other models sharing this model's declared parent."""
parent = parents.get(model_id) or ""
if not parent:
return []
return [c for c in children.get(parent, ()) if c != model_id]
def largest_family(rows, parents: dict = None, children: dict = None):
"""The indexed model with the most descendants: (root_id, [descendants]).
This is what fills the design's "lineage spotlight" panel. Ties break on
model id so the panel does not reshuffle between identical rebuilds.
"""
rows = list(rows)
if parents is None:
parents = build_parents(rows)
if children is None:
children = build_children(parents)
indexed = {row["id"] for row in rows if row.get("id")}
best_id, best_kids = "", []
for candidate in sorted(indexed):
kids = descendants(candidate, children)
if len(kids) > len(best_kids):
best_id, best_kids = candidate, kids
return best_id, best_kids