Spaces:
Running
Running
import os | |
import shutil | |
import subprocess | |
import signal | |
import gradio as gr | |
from huggingface_hub import create_repo, HfApi, snapshot_download, whoami, ModelCard | |
from gradio_huggingfacehub_search import HuggingfaceHubSearch | |
from textwrap import dedent | |
# Obtener el token de Hugging Face desde el entorno | |
HF_TOKEN = os.getenv("HF_TOKEN", "") | |
def ensure_valid_token(oauth_token): | |
"""Verifica si el token es válido.""" | |
if not oauth_token or not oauth_token.strip(): | |
raise ValueError("Debe proporcionar un token válido.") | |
return oauth_token.strip() | |
def generate_importance_matrix(model_path, train_data_path): | |
"""Genera la matriz de importancia usando llama-imatrix.""" | |
imatrix_command = f"./llama-imatrix -m ../{model_path} -f {train_data_path} -ngl 99 --output-frequency 10" | |
os.chdir("llama.cpp") | |
if not os.path.isfile(f"../{model_path}"): | |
raise FileNotFoundError(f"Archivo del modelo no encontrado: {model_path}") | |
process = subprocess.Popen(imatrix_command, shell=True) | |
try: | |
process.wait(timeout=60) | |
except subprocess.TimeoutExpired: | |
process.send_signal(signal.SIGINT) | |
try: | |
process.wait(timeout=5) | |
except subprocess.TimeoutExpired: | |
process.kill() | |
os.chdir("..") | |
def split_upload_model(model_path, repo_id, oauth_token, split_max_tensors=256, split_max_size=None): | |
"""Divide y sube el modelo en partes.""" | |
if not oauth_token or not oauth_token.strip(): | |
raise ValueError("Debe proporcionar un token válido.") | |
split_cmd = f"llama.cpp/llama-gguf-split --split --split-max-tensors {split_max_tensors}" | |
if split_max_size: | |
split_cmd += f" --split-max-size {split_max_size}" | |
split_cmd += f" {model_path} {model_path.split('.')[0]}" | |
result = subprocess.run(split_cmd, shell=True, capture_output=True, text=True) | |
if result.returncode != 0: | |
raise RuntimeError(f"Error al dividir el modelo: {result.stderr}") | |
sharded_model_files = [f for f in os.listdir('.') if f.startswith(model_path.split('.')[0])] | |
if sharded_model_files: | |
api = HfApi(token=oauth_token) | |
for file in sharded_model_files: | |
file_path = os.path.join('.', file) | |
try: | |
api.upload_file( | |
path_or_fileobj=file_path, | |
path_in_repo=file, | |
repo_id=repo_id, | |
) | |
except Exception as e: | |
raise RuntimeError(f"Error al subir el archivo {file_path}: {e}") | |
else: | |
raise FileNotFoundError("No se encontraron archivos divididos.") | |
def process_model(model_id, q_method, use_imatrix, imatrix_q_method, private_repo, train_data_file, split_model, split_max_tensors, split_max_size, oauth_token): | |
"""Procesa el modelo descargado y realiza la conversión y subida.""" | |
token = ensure_valid_token(oauth_token) | |
model_name = model_id.split('/')[-1] | |
fp16 = f"{model_name}.fp16.gguf" | |
try: | |
api = HfApi(token=token) | |
dl_pattern = [ | |
"*.safetensors", "*.bin", "*.pt", "*.onnx", "*.h5", "*.tflite", | |
"*.ckpt", "*.pb", "*.tar", "*.xml", "*.caffemodel", | |
"*.md", "*.json", "*.model" | |
] | |
pattern = ( | |
"*.safetensors" | |
if any( | |
file.path.endswith(".safetensors") | |
for file in api.list_repo_tree( | |
repo_id=model_id, | |
recursive=True, | |
) | |
) | |
else "*.bin" | |
) | |
dl_pattern += pattern | |
snapshot_download(repo_id=model_id, local_dir=model_name, local_dir_use_symlinks=False, allow_patterns=dl_pattern) | |
print("Modelo descargado exitosamente!") | |
conversion_script = "convert_hf_to_gguf.py" | |
fp16_conversion = f"python llama.cpp/{conversion_script} {model_name} --outtype f16 --outfile {fp16}" | |
result = subprocess.run(fp16_conversion, shell=True, capture_output=True) | |
if result.returncode != 0: | |
raise RuntimeError(f"Error al convertir a fp16: {result.stderr}") | |
imatrix_path = "llama.cpp/imatrix.dat" | |
if use_imatrix: | |
if train_data_file: | |
train_data_path = train_data_file.name | |
else: | |
train_data_path = "groups_merged.txt" | |
if not os.path.isfile(train_data_path): | |
raise FileNotFoundError(f"Archivo de datos de entrenamiento no encontrado: {train_data_path}") | |
generate_importance_matrix(fp16, train_data_path) | |
quantized_gguf_name = f"{model_name.lower()}-{imatrix_q_method.lower()}-imat.gguf" if use_imatrix else f"{model_name.lower()}-{q_method.lower()}.gguf" | |
quantized_gguf_path = quantized_gguf_name | |
quantise_ggml = f"./llama.cpp/llama-quantize {'--imatrix ' + imatrix_path if use_imatrix else ''} {fp16} {quantized_gguf_path} {imatrix_q_method if use_imatrix else q_method}" | |
result = subprocess.run(quantise_ggml, shell=True, capture_output=True) | |
if result.returncode != 0: | |
raise RuntimeError(f"Error al cuantificar: {result.stderr}") | |
username = whoami(token)["name"] | |
new_repo_url = api.create_repo(repo_id=f"{username}/{model_name}-{imatrix_q_method if use_imatrix else q_method}-GGUF", exist_ok=True, private=private_repo) | |
new_repo_id = new_repo_url.repo_id | |
try: | |
card = ModelCard.load(model_id, token=token) | |
except: | |
card = ModelCard("") | |
if card.data.tags is None: | |
card.data.tags = [] | |
card.data.tags.append("llama-cpp") | |
card.data.tags.append("gguf-my-repo") | |
card.data.base_model = model_id | |
card.text = dedent( | |
f""" | |
# {new_repo_id} | |
Este modelo fue convertido al formato GGUF desde [`{model_id}`](https://huggingface.co/{model_id}) usando llama.cpp a través del espacio GGUF-my-repo. | |
Consulta el [card del modelo original](https://huggingface.co/{model_id}) para más detalles. | |
## Uso con llama.cpp | |
Instala llama.cpp a través de brew (funciona en Mac y Linux) | |
```bash | |
brew install llama.cpp | |
``` | |
Invoca el servidor llama.cpp o la CLI. | |
### CLI: | |
```bash | |
llama-cli --hf-repo {new_repo_id} --hf-file {quantized_gguf_name} -p "El sentido de la vida y el universo es" | |
``` | |
### Servidor: | |
```bash | |
llama-server --hf-repo {new_repo_id} --hf-file {quantized_gguf_name} -c 2048 | |
``` | |
Nota: También puedes usar este punto de control directamente a través de los [pasos de uso](https://github.com/ggerganov/llama.cpp?tab=readme-ov-file#usage) listados en el repositorio llama.cpp. | |
""" | |
) | |
card.save(f"README.md") | |
if split_model: | |
split_upload_model(quantized_gguf_path, new_repo_id, token, split_max_tensors, split_max_size) | |
else: | |
try: | |
api.upload_file( | |
path_or_fileobj=quantized_gguf_path, | |
path_in_repo=quantized_gguf_name, | |
repo_id=new_repo_id, | |
) | |
except Exception as e: | |
raise RuntimeError(f"Error al subir el modelo cuantificado: {e}") | |
if os.path.isfile(imatrix_path): | |
try: | |
api.upload_file( | |
path_or_fileobj=imatrix_path, | |
path_in_repo="imatrix.dat", | |
repo_id=new_repo_id, | |
) | |
except Exception as e: | |
raise RuntimeError(f"Error al subir imatrix.dat: {e}") | |
api.upload_file( | |
path_or_fileobj=f"README.md", | |
path_in_repo=f"README.md", | |
repo_id=new_repo_id, | |
) | |
return ( | |
f'Encuentra tu repositorio <a href=\'{new_repo_url}\' target="_blank" style="text-decoration:underline">aquí</a>', | |
"llama.png", | |
) | |
except Exception as e: | |
return (f"Error: {e}", "error.png") | |
finally: | |
shutil.rmtree(model_name, ignore_errors=True) | |
with gr.Blocks() as app: | |
gr.Markdown("# Procesamiento de Modelos") | |
gr.Markdown( | |
""" | |
Este panel permite procesar modelos de machine learning, convertirlos al formato GGUF, y cargarlos en un repositorio de Hugging Face. | |
Puedes seleccionar diferentes métodos de cuantización y personalizar la conversión usando matrices de importancia. | |
""" | |
) | |
with gr.Row(): | |
model_id = gr.Textbox( | |
label="ID del Modelo", | |
placeholder="username/model-name", | |
info="Introduce el ID del modelo en Hugging Face que deseas procesar.", | |
) | |
q_method = gr.Dropdown( | |
["IQ3_M", "IQ3_XXS", "Q4_K_M", "Q4_K_S", "IQ4_NL", "IQ4_XS", "Q5_K_M", "Q5_K_S"], | |
label="Método de Cuantización", | |
info="Selecciona el método de cuantización que deseas aplicar al modelo." | |
) | |
use_imatrix = gr.Checkbox( | |
label="Usar Matriz de Importancia", | |
info="Marca esta opción si deseas usar una matriz de importancia para la cuantización." | |
) | |
imatrix_q_method = gr.Dropdown( | |
["IQ3_M", "IQ3_XXS", "Q4_K_M", "Q4_K_S", "IQ4_NL", "IQ4_XS", "Q5_K_M", "Q5_K_S"], | |
label="Método de Matriz de Importancia", | |
info="Selecciona el método de cuantización para la matriz de importancia.", | |
visible=False # Solo visible si se marca 'use_imatrix' | |
) | |
private_repo = gr.Checkbox( | |
label="Repositorio Privado", | |
info="Marca esta opción si deseas que el repositorio creado sea privado." | |
) | |
train_data_file = gr.File( | |
label="Archivo de Datos de Entrenamiento", | |
type="filepath", | |
info="Selecciona el archivo que contiene los datos de entrenamiento necesarios para generar la matriz de importancia.", | |
) | |
split_model = gr.Checkbox( | |
label="Dividir Modelo", | |
info="Marca esta opción para dividir el modelo en partes más pequeñas antes de subirlo." | |
) | |
split_max_tensors = gr.Number( | |
label="Max Tensors (para división)", | |
value=256, | |
info="Especifica el número máximo de tensores por parte si estás dividiendo el modelo.", | |
) | |
split_max_size = gr.Number( | |
label="Max Tamaño (para división)", | |
value=None, | |
info="Especifica el tamaño máximo de cada parte si estás dividiendo el modelo.", | |
) | |
oauth_token = gr.Textbox( | |
label="Token de Hugging Face", | |
type="password", | |
info="Introduce tu token de autenticación de Hugging Face. Asegúrate de que el token sea válido para acceder a los repositorios." | |
) | |
with gr.Row(): | |
result = gr.HTML(label="Resultado") | |
img = gr.Image(label="Imagen") | |
process_button = gr.Button("Procesar Modelo") | |
process_button.click( | |
process_model, | |
inputs=[model_id, q_method, use_imatrix, imatrix_q_method, private_repo, train_data_file, split_model, split_max_tensors, split_max_size, oauth_token], | |
outputs=[result, img] | |
) | |
app.launch() | |