|
import gradio as gr |
|
import os |
|
import numpy as np |
|
import ujson as json |
|
import time |
|
from threading import Thread |
|
|
|
from loading import load_data, save_git, load_git |
|
from tools import compute_ordered_matrix |
|
from plotting import plot_sim_matrix_fig, plot_umap_fig, plot_tree, update_sim_matrix_fig, update_umap_fig, update_tree_fig |
|
from llm_run import download_llm_to_cache, load_model, llm_run |
|
|
|
def reload_figures(): |
|
global MODEL_SEARCHED_X, MODEL_SEARCHED_Y, ALPHA_EDGES, ALPHA_NAMES, ALPHA_MARKERS, FIGS, ORDERED_MODEL_NAMES |
|
fig1 = update_sim_matrix_fig(FIGS['fig1'], ORDERED_MODEL_NAMES, model_search_x=MODEL_SEARCHED_X, model_search_y=MODEL_SEARCHED_Y) |
|
fig2 = update_umap_fig(FIGS['fig2'], DIST_MATRIX, MODEL_NAMES, FAMILIES, COLORS, model_search_x=MODEL_SEARCHED_X, alpha_edges=ALPHA_EDGES['fig2'], alpha_names=ALPHA_NAMES['fig2'], alpha_markers=ALPHA_MARKERS['fig2']) |
|
fig4 = update_tree_fig(FIGS['fig4'], MODEL_NAMES, model_search=MODEL_SEARCHED_X, alpha_edges=ALPHA_EDGES['fig4'], alpha_names=ALPHA_NAMES['fig4'], alpha_markers=ALPHA_MARKERS['fig4']) |
|
return [fig1,fig2,fig4] |
|
|
|
def search_bar_changeX(value): |
|
global MODEL_SEARCHED_X |
|
MODEL_SEARCHED_X = value |
|
return reload_figures() |
|
|
|
def search_bar_changeY(value): |
|
global MODEL_SEARCHED_Y |
|
MODEL_SEARCHED_Y = value |
|
return reload_figures() |
|
|
|
def slider_changeAlphaMarkers(value,key): |
|
global ALPHA_MARKERS |
|
ALPHA_MARKERS[key] = value |
|
return reload_figures() |
|
|
|
def slider_changeAlphaNames(value,key): |
|
global ALPHA_NAMES |
|
ALPHA_NAMES[key] = value |
|
return reload_figures() |
|
|
|
def slider_changeAlphaEdges(value,key): |
|
global ALPHA_EDGES |
|
ALPHA_EDGES[key] = value |
|
return reload_figures() |
|
|
|
def search_bar_gr(model_names,slider=True,double_search=False,key=None): |
|
global MODEL_SEARCHED_X,MODEL_SEARCHED_Y,ALPHA_EDGES,ALPHA_NAMES, ALPHA_MARKERS |
|
|
|
ret = [] |
|
with gr.Column(scale=1) as col1: |
|
with gr.Group(): |
|
if MODEL_SEARCHED_X is None: |
|
index = 0 |
|
else: |
|
index = model_names.index(MODEL_SEARCHED_X) |
|
ms_x = gr.Dropdown(label='Search'+(' X' if double_search else ''),choices=model_names,value=model_names[index],key='model_search_x_'+key,interactive=True) |
|
|
|
ret.append(ms_x) |
|
if double_search: |
|
if MODEL_SEARCHED_Y is None: |
|
index = 0 |
|
else: |
|
index = model_names.index(MODEL_SEARCHED_Y) |
|
ms_y = gr.Dropdown(label='Search Y',choices=model_names,value=model_names[index],key='model_search_y_'+key,interactive=True) |
|
ret.append(ms_y) |
|
if slider: |
|
with gr.Group(): |
|
values = np.arange(0, 1.05,0.05) |
|
|
|
values = np.round(values,2) |
|
alpha_edges = gr.Slider(label='Alpha Edges', |
|
minimum=0, |
|
maximum=1, |
|
step=0.05, |
|
value=ALPHA_EDGES[key], |
|
key='alpha_edges_'+key, |
|
interactive=True) |
|
|
|
values = np.arange(0, 1.05,0.05) |
|
|
|
values = np.round(values,2) |
|
alpha_names = gr.Slider(label='Alpha Names', |
|
minimum=0, |
|
maximum=1, |
|
step=0.05, |
|
value=ALPHA_NAMES[key], |
|
key='alpha_names_'+key, |
|
interactive=True) |
|
|
|
values = np.arange(0, 1.05,0.05) |
|
|
|
values = np.round(values,2) |
|
alpha_markers = gr.Slider(label='Alpha Markers', |
|
minimum=0, |
|
maximum=1, |
|
step=0.05, |
|
value=ALPHA_MARKERS[key], |
|
key='alpha_markers_'+key, |
|
interactive=True) |
|
ret.append(alpha_edges) |
|
ret.append(alpha_names) |
|
ret.append(alpha_markers) |
|
col2 = gr.Column(scale=5) |
|
ret.insert(0,col2) |
|
return ret |
|
|
|
import spaces |
|
@spaces.GPU(duration=300) |
|
def _run(path,genes,N,progress_bar): |
|
|
|
progress_bar(0.20, desc="Loading Model...",total=100) |
|
try: |
|
model,tokenizer = load_model(path) |
|
except ValueError as e: |
|
print(f"Error loading model '{path}': {e}") |
|
gr.Warning("Model couldn't load. This space currently only works with AutoModelForCausalLM models and for security reasons cannot execute remote code. Please check the model architecture and whether it too recent and requires the execution of custom code.") |
|
return None |
|
except OSError as e: |
|
print(f"Error loading model '{path}': {e}") |
|
gr.Warning("Model doesn't seem to exist on the HuggingFace Hub or might be gated. Please check the model name and its accessibility.") |
|
return None |
|
except RuntimeError as e: |
|
if 'out of memory' in str(e): |
|
print(f"Error loading model '{path}': {e}") |
|
gr.Warning("Loading the model triggered an out of memory error. It may be too big for the GPU (80Go RAM max). Please verify the size of the model.") |
|
return None |
|
else: |
|
print(f"Error loading model '{path}': {e}") |
|
gr.Warning("Model couldn't be loaded. Check the logs for what happened or report an issue including the model's name.") |
|
return None |
|
except Exception as e: |
|
print(f"Error loading model '{path}': {e}") |
|
gr.Warning("Model couldn't be loaded. Check the logs for what happened or report an issue including the model's name.") |
|
return None |
|
progress_bar(0.25, desc="Generating data...",total=100) |
|
time0 = time.perf_counter() |
|
for i,output in enumerate(llm_run(model,tokenizer,genes,N)): |
|
time_elapsed = time.perf_counter()-time0 |
|
estimated_time_remaining = int(len(genes)*time_elapsed/(i+1)) |
|
minutes = str(estimated_time_remaining//60) |
|
minutes = "0"*(2-min(2,len(minutes))) + minutes |
|
seconds = str(estimated_time_remaining%60) |
|
seconds = "0"*(2-min(2,len(seconds))) + seconds |
|
progress_bar(0.25 + i*(70/len(genes))/100, desc=f"Generating data... {i+1}/{len(genes)} - estimated remaining time {minutes}:{seconds}",total=100) |
|
return output |
|
|
|
def run(path,progress_bar): |
|
global DEFAULT_FAMILY_NAME, PHYLOLM_N |
|
family = DEFAULT_FAMILY_NAME |
|
N = PHYLOLM_N |
|
|
|
progress_bar(0, desc="Downloading model...",total=100) |
|
try: |
|
|
|
if download_llm_to_cache(path) is None: |
|
gr.Warning("Model not found on Hugging Face Hub or might be gated. Please check the model name and try again.") |
|
return None |
|
except OSError as e: |
|
print(f"Error downloading model: {e}") |
|
gr.Warning("Model not found on Hugging Face Hub or might be gated. Please check the model name and try again.") |
|
return None |
|
|
|
|
|
progress_bar(0.10, desc="Loading contexts...",total=100) |
|
|
|
with open('inputs/math.json', 'r') as f: |
|
genes = json.load(f) |
|
|
|
|
|
progress_bar(0.15, desc="Waiting for GPU...",total=100) |
|
|
|
try: |
|
output = _run(path,genes,N,progress_bar) |
|
if output is None: |
|
return None |
|
except Exception as e: |
|
print(f"Error running model: {e}") |
|
gr.Warning("Something unexpected happened during the run or the loading of the model. Please check the logs or report an issue.") |
|
return None |
|
|
|
progress_bar(0.95, desc="Saving data ...",total=100) |
|
|
|
alleles = [[compl[j]['generated_text'][len(gene):][:4] for j in range(len(compl))] for gene,compl in zip(genes,output)] |
|
fsave = False |
|
for i in range(10): |
|
try: |
|
save_git(alleles,genes,path,family) |
|
fsave = True |
|
break |
|
except Exception as e: |
|
print(f"Error saving data: {e}") |
|
|
|
try: |
|
load_git(force_clone=True) |
|
except Exception as e: |
|
print(f"Error recloning repo: {e}") |
|
if not fsave: |
|
gr.Warning("Something went wrong with GitHub and data couldn't be sent to the server. Please check the logs or report an issue. ") |
|
"""def download_data(): |
|
d = {'family':family,'alleles':alleles} |
|
model_name = path |
|
data_path = f'math/{model_name}.json' |
|
path = os.path.join('Data',data_path) |
|
#create the file folder path |
|
if not os.path.exists(os.path.dirname(path)): |
|
os.makedirs(os.path.dirname(path), exist_ok=True) |
|
#Open the file |
|
with open(path,'w') as f: |
|
json.dump(d,f) |
|
# Provide the download link |
|
return gr.File.update(value=path, label="Download data", file_name=f"{model_name}.json") |
|
gr.Button("Download data",variant="primary").click(fn=download_data, inputs=[], outputs=None)""" |
|
return None |
|
|
|
|
|
progress_bar(1, desc="Done!",total=100) |
|
|
|
def prepare_run(model_name,progress_bar=gr.Progress()): |
|
global MODEL_SEARCHED_X,MODEL_NAMES |
|
if model_name in MODEL_NAMES: |
|
gr.Warning('Model already exists in the database.') |
|
MODEL_SEARCHED_X = model_name |
|
reload_figures() |
|
return |
|
run(model_name,progress_bar) |
|
|
|
def reload_env(): |
|
global SIM_MAT_SEARCH_X, SIM_MAT_SEARCH_Y, VIZ_SEARCH, TREE_SEARCH |
|
global MODEL_NAMES, FAMILIES, COLORS, SIM_MATRIX, DIST_MATRIX |
|
global FIGS, FIGS_OBJECTS |
|
global ORDERED_MODEL_NAMES |
|
|
|
|
|
data, model_names, families, sim_matrix, colors = load_data() |
|
|
|
sim_matrix_safe = np.where(sim_matrix == 0, np.finfo(np.float64).eps, sim_matrix) |
|
dist_matrix = -np.log(sim_matrix_safe) |
|
|
|
|
|
MODEL_NAMES = model_names |
|
FAMILIES = families |
|
COLORS = colors |
|
SIM_MATRIX = sim_matrix |
|
DIST_MATRIX = dist_matrix |
|
|
|
|
|
ordered_sim_matrix, ordered_model_names = compute_ordered_matrix(sim_matrix,dist_matrix, model_names) |
|
ORDERED_MODEL_NAMES = ordered_model_names |
|
FIGS['fig1'] = plot_sim_matrix_fig(ordered_sim_matrix, ordered_model_names, families, colors) |
|
FIGS['fig2'] = plot_umap_fig(dist_matrix, sim_matrix, model_names, families, colors, |
|
alpha_edges=ALPHA_EDGES['fig2'],alpha_names=ALPHA_NAMES['fig2'],alpha_markers=ALPHA_MARKERS['fig2']) |
|
FIGS['fig4'] = plot_tree(sim_matrix, model_names, families, colors,alpha_edges=ALPHA_EDGES['fig4'],alpha_names=ALPHA_NAMES['fig4'],alpha_markers=ALPHA_MARKERS['fig4']) |
|
|
|
|
|
sim_mat_search_x = gr.Dropdown(label='Search X',choices=model_names,value=model_names[0],key='model_search_x_fig1',interactive=True) |
|
sim_mat_search_y = gr.Dropdown(label='Search Y',choices=model_names,value=model_names[0],key='model_search_y_fig1',interactive=True) |
|
viz_search = gr.Dropdown(label='Search',choices=model_names,value=model_names[0],key='model_search_fig2',interactive=True) |
|
tree_search = gr.Dropdown(label='Search',choices=model_names,value=model_names[0],key='model_search_fig4',interactive=True) |
|
|
|
return FIGS['fig1'], FIGS['fig2'], FIGS['fig4'], sim_mat_search_x, sim_mat_search_y, viz_search, tree_search |
|
|
|
|
|
USERNAME = os.environ['GITHUB_USERNAME'] |
|
TOKEN = os.environ['GITHUB_TOKEN'] |
|
MAIL = os.environ['GITHUB_MAIL'] |
|
|
|
MODEL_SEARCHED_X = None |
|
MODEL_SEARCHED_Y = None |
|
ALPHA_EDGES = {'fig2':0.05, 'fig3':0.05,'fig4':1.0} |
|
ALPHA_NAMES = {'fig2':0.0, 'fig3':0.0,'fig4':0.0} |
|
ALPHA_MARKERS = {'fig2':0.8, 'fig3':0.8,'fig4':1.0} |
|
|
|
FIGS = {'fig1':None,'fig2':None,'fig3':None,'fig4':None} |
|
FIGS_OBJECTS = [None,None,None] |
|
MODEL_NAMES = None |
|
FAMILIES = None |
|
COLORS = None |
|
ORDERED_MODEL_NAMES = None |
|
SIM_MATRIX = None |
|
DIST_MATRIX = None |
|
|
|
DEFAULT_FAMILY_NAME = '?' |
|
PHYLOLM_N = 32 |
|
|
|
SIM_MAT_SEARCH_X = None |
|
SIM_MAT_SEARCH_Y = None |
|
VIZ_SEARCH = None |
|
TREE_SEARCH = None |
|
|
|
|
|
with gr.Blocks(title="PhyloLM", theme=gr.themes.Default()) as demo: |
|
gr.Markdown("# PhyloLM: Phylogenetic Mapping of Language Models") |
|
|
|
gr.Markdown("_This space is under active development. New features and improvements will be added regularly and some things may not work properly. Feel free to open an issue if you encounter any problems._") |
|
|
|
gr.Markdown( |
|
"Welcome to PhyloLM ([paper](https://arxiv.org/abs/2404.04671) - [code](https://github.com/Nicolas-Yax/PhyloLM)) — a tool for comparing language models based on their **behavioral similarity**, inspired by methods from comparative genomics. " |
|
"Instead of architecture or weights, we use output behavior on diagnostic prompts as a behavioral fingerprint to compute a distance metric, akin to how biologists compare species using genetic data. This makes it possible to draw a unique map of all LLMs (various architectures, gated and non gated, ...)." |
|
"The goal of this space is to create a collaborative space where everyone can visualize these maps and extend them with models of their choice. " |
|
) |
|
|
|
gr.Markdown("## Explore Maps of Models") |
|
|
|
gr.Markdown( |
|
"This interactive space allows users to explore model similarities through four types of visualizations:\n" |
|
"- A similarity matrix (values range from 0 = dissimilar to 1 = highly similar). \n" |
|
"- 2D and 3D scatter plots representing how close or far from each other LLMs are (plotted using UMAP). \n" |
|
"- A tree to visualize distances between models (distance from leaf A to leaf B in the tree is similar to the distance between the two models)\n\n" |
|
"Models are colored according to their family (e.g., LLaMA, OPT, Mistral) for the ones that were in the original paper. Models added by users are colored in grey for now. " |
|
) |
|
|
|
|
|
data, model_names, families, sim_matrix, colors = load_data() |
|
|
|
sim_matrix_safe = np.where(sim_matrix == 0, np.finfo(np.float64).eps, sim_matrix) |
|
dist_matrix = -np.log(sim_matrix_safe) |
|
|
|
|
|
MODEL_NAMES = model_names |
|
FAMILIES = families |
|
COLORS = colors |
|
SIM_MATRIX = sim_matrix |
|
DIST_MATRIX = dist_matrix |
|
|
|
|
|
tab_state = gr.State(value="Similarity Matrix") |
|
tabs = gr.Tabs(["Similarity Matrix", "2D Visualization","Tree Visualization"]) |
|
with tabs: |
|
with gr.TabItem("Similarity Matrix"): |
|
|
|
with gr.Row(): |
|
col2,sim_mat_search_x,sim_mat_search_y = search_bar_gr(model_names,slider=False,double_search=True,key='fig1') |
|
with col2: |
|
ordered_sim_matrix, ordered_model_names = compute_ordered_matrix(sim_matrix,dist_matrix, model_names) |
|
fig = plot_sim_matrix_fig(ordered_sim_matrix, ordered_model_names, families, colors) |
|
sim_matrix_output = gr.Plot(fig,label="Similarity Matrix") |
|
FIGS['fig1'] = fig |
|
ORDERED_MODEL_NAMES = ordered_model_names |
|
FIGS_OBJECTS[0] = sim_matrix_output |
|
with gr.TabItem("2D Visualization"): |
|
|
|
with gr.Row(): |
|
col2,viz_search,viz_alpha_edge,viz_alpha_name,viz_alpha_marker = search_bar_gr(model_names,slider=True,double_search=False,key='fig2') |
|
with col2: |
|
fig = plot_umap_fig(dist_matrix, sim_matrix, model_names, families, colors, |
|
alpha_edges=ALPHA_EDGES['fig2'],alpha_names=ALPHA_NAMES['fig2'],alpha_markers=ALPHA_MARKERS['fig2']) |
|
plot_output = gr.Plot(fig,label="2D Visualization") |
|
FIGS['fig2'] = fig |
|
FIGS_OBJECTS[1] = plot_output |
|
with gr.TabItem("Tree Visualization"): |
|
|
|
with gr.Row(): |
|
col2,tree_search,tree_alpha_edge,tree_alpha_name,tree_alpha_marker = search_bar_gr(model_names,slider=True,double_search=False,key='fig4') |
|
with col2: |
|
fig = plot_tree(sim_matrix, model_names, families, colors,alpha_edges=ALPHA_EDGES['fig4'],alpha_names=ALPHA_NAMES['fig4'],alpha_markers=ALPHA_MARKERS['fig4']) |
|
tree_output = gr.Plot(fig,label="Tree Visualization") |
|
FIGS['fig4'] = fig |
|
FIGS_OBJECTS[2] = tree_output |
|
|
|
|
|
|
|
gr.Markdown("## Submit a Model") |
|
|
|
gr.Markdown( |
|
"You may contribute new models to this collaborative space using compute resources. " |
|
"Once processed, the model will be compared to existing ones, and its results added to a shared public database. " |
|
"Model families (e.g., LLaMA, OPT, Mistral) are extracted from Hugging Face model cards and used only for visualization (e.g., coloring plots); they are **not** involved in the computation of similarity." |
|
) |
|
|
|
gr.Markdown( |
|
"**To add a new model:**\n" |
|
"1. Enter the name of a model hosted on Hugging Face (e.g., `'Qwen/Qwen2.5-7B-Instruct'`).\n" |
|
"2. Click on the **Run PhyloLM** button.\n" |
|
"- If the model has already been processed, you'll be notified and no new run will start.\n" |
|
"- If it hasn't been processed, it will be downloaded and be evaluated.\n\n" |
|
"⚠️ Be careful when submitting large LLMs (typically >15B parameters) as they may exceed the GPU RAM or the time limit, leading to failed runs.\n" |
|
) |
|
|
|
with gr.Group(): |
|
model_input = gr.Textbox(label="Model", interactive=True) |
|
submit_btn = gr.Button("Run PhyloLM", variant="primary",interactive=True) |
|
|
|
|
|
|
|
gr.Markdown("## Disclaimer") |
|
gr.Markdown( |
|
"This is a research prototype and may contain bugs or limitations. " |
|
"All computed data are public and hosted on [GitHub](https://github.com/PhyloLM/Data). " |
|
"If you'd like to contribute additional models — especially for gated or large models that cannot be processed via the web interface — " |
|
"you are welcome to submit a pull request to the repository cited above. " |
|
"All results are computed on the 'Math' set of genes used in the original paper." |
|
) |
|
|
|
gr.Markdown("## Citation") |
|
gr.Markdown("If you find this project useful for your research, please consider citing the following paper:") |
|
|
|
|
|
gr.Code('''@inproceedings{ |
|
yax2025phylolm, |
|
title={Phylo{LM}: Inferring the Phylogeny of Large Language Models and Predicting their Performances in Benchmarks}, |
|
author={Nicolas Yax and Pierre-Yves Oudeyer and Stefano Palminteri}, |
|
booktitle={The Thirteenth International Conference on Learning Representations}, |
|
year={2025}, |
|
url={https://openreview.net/forum?id=rTQNGQxm4K} |
|
}''',language=None) |
|
gr.Markdown('Socials : [BlueSky](https://bsky.app/profile/nicolasyax.bsky.social) [Twitter](https://x.com/nicolas__yax?s=11)') |
|
|
|
|
|
sim_mat_search_x.change(fn=search_bar_changeX, inputs=sim_mat_search_x, outputs=FIGS_OBJECTS) |
|
sim_mat_search_y.change(fn=search_bar_changeY, inputs=sim_mat_search_y, outputs=FIGS_OBJECTS) |
|
|
|
viz_search.change(fn=search_bar_changeX, inputs=viz_search, outputs=FIGS_OBJECTS) |
|
|
|
tree_search.change(fn=search_bar_changeX, inputs=tree_search, outputs=FIGS_OBJECTS) |
|
|
|
|
|
viz_alpha_edge.change(fn=lambda x : slider_changeAlphaEdges(x,'fig2'), inputs=viz_alpha_edge, outputs=FIGS_OBJECTS) |
|
viz_alpha_name.change(fn=lambda x : slider_changeAlphaNames(x,'fig2'), inputs=viz_alpha_name, outputs=FIGS_OBJECTS) |
|
viz_alpha_marker.change(fn=lambda x : slider_changeAlphaMarkers(x,'fig2'), inputs=viz_alpha_marker, outputs=FIGS_OBJECTS) |
|
|
|
tree_alpha_edge.change(fn=lambda x : slider_changeAlphaEdges(x,'fig4'), inputs=tree_alpha_edge, outputs=FIGS_OBJECTS) |
|
tree_alpha_name.change(fn=lambda x : slider_changeAlphaNames(x,'fig4'), inputs=tree_alpha_name, outputs=FIGS_OBJECTS) |
|
tree_alpha_marker.change(fn=lambda x : slider_changeAlphaMarkers(x,'fig4'), inputs=tree_alpha_marker, outputs=FIGS_OBJECTS) |
|
|
|
|
|
run_event = submit_btn.click(fn=prepare_run, inputs=[model_input], outputs=[model_input]).then(fn=reload_env, inputs=[], outputs=FIGS_OBJECTS+ [sim_mat_search_x, sim_mat_search_y, viz_search, tree_search]) |
|
|
|
|
|
|
|
SIM_MAT_SEARCH_X = sim_mat_search_x |
|
SIM_MAT_SEARCH_Y = sim_mat_search_y |
|
VIZ_SEARCH = viz_search |
|
TREE_SEARCH = tree_search |
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
demo.launch() |