Skip to content

Commit

Permalink
v0.1.2-pre4 scheduler rework, queue GUI, more testing!
Browse files Browse the repository at this point in the history
  • Loading branch information
christianazinn committed Apr 16, 2024
1 parent 8022c78 commit bfe769e
Show file tree
Hide file tree
Showing 20 changed files with 156 additions and 195 deletions.
4 changes: 3 additions & 1 deletion Homepage.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# main.py
# Version 0.1.2-pre2: Still need to test Hugging_Face_Downloader.py with new queuer, updated semantics, wrote Queue GUI and that needs testing
# Version 0.1.2-pre4: Conversion of most pages to new scheduler complete, testing complete, wrote Queue GUI and that needs testing
import streamlit as st
st.set_page_config(layout="wide")
from st_pages import Page, Section, show_pages, add_indentation
from util.scheduler import *

# UI CODE ---------------------------------------------------------------------------------

Expand Down
6 changes: 2 additions & 4 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,10 @@ TODO thread safety? threading.Thread(target=process_queue, daemon=True).start()

TODO follow page FILESTATUSes and also rework the scripts in the main directory
TODO TEST TEST TEST!!!

TODO add a page that allows you to make one great big queued op for download/convert/quantize/(imatrix)/upload/etc
TODO keep an eye on streamlit-process-manager

# TODO NEXT TESTS
test Hugging_Face_Downloader.py
test queueing downloaded files
test the actual file downloads
test whether it works fine mixing conversion and download jobs
test cancelling file downloads midway
test whether it's actually parallelized???
Expand Down
1 change: 1 addition & 0 deletions pages/Convert_Safetensors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# FILESTATUS: pretty much done, needs testing. Last updated v0.1.2-pre3
# IMPORTS ---------------------------------------------------------------------------------
import streamlit as st
st.set_page_config(layout="wide")
from st_pages import add_indentation
from util.constants import config
from util.scheduler import *
Expand Down
1 change: 1 addition & 0 deletions pages/Create_IMatrix.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# FILESTATUS: the entire thing still needs to be written + implemented. Last updated v0.1.1
# IMPORTS ---------------------------------------------------------------------------------
import streamlit as st
st.set_page_config(layout="wide")
from st_pages import add_indentation

# FUNCTIONS ---------------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions pages/Docs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# FILESTATUS: pretty much done but needs to have docs written in util.docs_inline. Last updated v0.1.0
# IMPORTS ---------------------------------------------------------------------------------
import streamlit as st
st.set_page_config(layout="wide")
from util.docs_inline import docs
from st_pages import add_indentation

Expand Down
1 change: 1 addition & 0 deletions pages/HF_Token_Encrypter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# FILESTATUS: unknown, needs testing. Last updated v0.1.0
# IMPORTS ---------------------------------------------------------------------------------
import streamlit as st
st.set_page_config(layout="wide")
from st_pages import add_indentation
from util.key import encrypt_token, generate_new_key

Expand Down
5 changes: 3 additions & 2 deletions pages/Hugging_Face_Downloader.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# FILESTATUS: completed, needs testing. Last updated v0.1.2-pre3
# IMPORTS ---------------------------------------------------------------------------------
import requests, streamlit as st
st.set_page_config(layout="wide")
from pathlib import Path
from st_pages import add_indentation
from util.scheduler import *
from util.paths import *

# FUNCTIONS ---------------------------------------------------------------------------------
# TODO be able to change output directory
# TODO test how fast this is because I don't think this is parallelized???
# TODO write new scheduler and parallelize
# TODO maybe you need to make another scheduler for just download jobs while the other is used for conversion/quantization/finetuning jobs

# write the download task to the queue
Expand All @@ -19,7 +20,7 @@ def queue_command(file_url, download_path, filename):
"-d", str(download_path), "-o", filename,
"--continue=true"
]
get_scheduler.add_job(command)
get_scheduler().add_job(command)

# queues a download task for each file in the file_links_dict
def trigger_command(file_links_dict, model_name):
Expand Down
72 changes: 25 additions & 47 deletions pages/Quantize_GGUF.py
Original file line number Diff line number Diff line change
@@ -1,89 +1,67 @@
# FILESTATUS: needs to be migrated to the New Way of Doing Things. Last updated v0.1.2-pre2
# FILESTATUS: migrated implementation but need to finish testing pre1 before this can be used and tested. Last updated v0.1.2-pre4
# IMPORTS ---------------------------------------------------------------------------------
import os, subprocess, sys, streamlit as st
from apscheduler.schedulers.background import BackgroundScheduler
import os, streamlit as st
st.set_page_config(layout="wide")
from pathlib import Path
from st_pages import add_indentation
from util.constants import config
from util.scheduler import *
from util.paths import *

# FUNCTIONS ---------------------------------------------------------------------------------
# TODO dont forget to implement imatrices somehow + gpu offloading, figure out --allow-requantize and --imatrix
# TODO dont forget to implement imatrices somehow + gpu offloading
# TODO be able to change output directory + add support for other options like -c

# Initialize the scheduler
scheduler = BackgroundScheduler()
scheduler.start()

def list_gguf_files(models_dir):
# List valid, selectable GGUF files in the models directory
def list_gguf_files():
gguf_files = []
if os.path.exists(models_dir):
for model_folder in os.listdir(models_dir):
hpq_folder = os.path.join(models_dir, model_folder, 'High-Precision-Quantization')
if os.path.exists(models_dir()):
for model_folder in os.listdir(models_dir()):
hpq_folder = os.path.join(models_dir(), model_folder, 'High-Precision-Quantization')
if os.path.exists(hpq_folder) and os.path.isdir(hpq_folder):
for file in os.listdir(hpq_folder):
if file.lower().endswith('.gguf'):
gguf_files.append(os.path.join(model_folder, 'High-Precision-Quantization', file))
return gguf_files

def schedule_quantize_task(command):
try:
subprocess.run(command, check=True)
return f"Task completed: {' '.join(command)}"
except subprocess.CalledProcessError as e:
return f"Error in task execution: {e}"

def trigger_command(modelpath, options):

if not any(options.values()):
return "Error: Please select at least one quantization option."

if not list_gguf_files():
return "Error: No GGUF files found."

debug_output = ""
llama_cpp_dir = Path("llama.cpp")
if llama_cpp_dir:
base_dir = llama_cpp_dir / 'models'
gguf_files = list_gguf_files(base_dir)
else:
base_dir = 'llama.cpp/models'
models_dir = os.path.join(base_dir)
gguf_files = list_gguf_files(models_dir)

if not gguf_files:
st.warning("No GGUF files found using the new logic. Falling back to the old logic.")
base_dir = 'llama.cpp/models'
models_dir = os.path.join(base_dir)
gguf_files = list_gguf_files(models_dir)

modelpath_path = Path(modelpath)
# TODO I'm not convinced this logic works - write debugs
model_name_only, model_file = modelpath_path.parts[-3], modelpath_path.name
medium_precision_dir = base_dir / model_name_only / 'Medium-Precision-Quantization'
medium_precision_dir = models_dir() / model_name_only / 'Medium-Precision-Quantization'
medium_precision_dir.mkdir(parents=True, exist_ok=True)

for option, selected in options.items():
if selected:
source_path = base_dir / model_name_only / 'High-Precision-Quantization' / model_file
source_path = models_dir() / model_name_only / 'High-Precision-Quantization' / model_file
modified_model_file = model_file.lower().replace('f16.gguf', '').replace('q8_0.gguf', '').replace('f32.gguf', '')
output_path = medium_precision_dir / f"{modified_model_file}-{option.upper()}.GGUF"

# if sys.platform == "linux":
# command = [str(base_dir.parent / 'quantize'), str(source_path), str(output_path), option]
# else:
command = [str(llama_cpp_dir / 'quantize'), str(source_path), str(output_path), option]

print(command)

scheduler.add_job(schedule_quantize_task, args=[command])
debug_command_str = ' '.join(command)
debug_command_str = queue_command(source_path, output_path, option)
debug_output += f"Scheduled: {debug_command_str}\n"

return debug_output if debug_output else "No options selected."

def queue_command(source_path, output_path, option):
command = [str(llama_cpp_dir() / 'quantize'), "--allow-requantize", str(source_path), str(output_path), option]
get_scheduler().add_job(command)
return " ".join(command)

# UI CODE ---------------------------------------------------------------------------------

add_indentation()

st.title("Medium Precision Quantization")

models_dir = os.path.join("llama.cpp", "models")
gguf_files = list_gguf_files(models_dir)
gguf_files = list_gguf_files()

selected_gguf_file = st.selectbox("Select a GGUF File", gguf_files)
options = {option: st.checkbox(label=option) for option in config['quantization_I'] + config['quantization_K']}
Expand Down
94 changes: 0 additions & 94 deletions pages/Quantize_GGUF_pre3.py

This file was deleted.

77 changes: 68 additions & 9 deletions pages/Queue_GUI.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,82 @@
# FILESTATUS: entire page needs to be written + implemented. Last updated v0.1.2-pre2
# FILESTATUS: mostly done, but still needs testing and feature implementations. Last updated v0.1.2-pre4
# IMPORTS ---------------------------------------------------------------------------------
import streamlit as st
st.set_page_config(layout="wide")
from st_pages import add_indentation
from util.scheduler import *
from streamlit_sortables import sort_items

# FUNCTIONS ---------------------------------------------------------------------------------

# UI CODE ---------------------------------------------------------------------------------

add_indentation()

if st.button('Run'):
get_scheduler().toggle()
st.title("Compute Job Scheduler")

def update_active():
st.session_state["active"] = get_scheduler().is_active()

st.write("The current state of the job scheduler is shown below.")

update_active()
st.write(st.session_state["active"])

if st.button("Toggle Compute Job Scheduler", on_click=get_scheduler().toggle):
# callback and update have to be in a very specific order for them to work
update_active()
st.rerun()

if st.button("Terminate Current Job", on_click=get_scheduler().send_termination_signal):
# callback and update have to be in a very specific order for them to work
update_active()
st.rerun()

# functionality you need:
# TODO show queue and completed jobs
# TODO show active status and allow toggle
# TODO be able to reorder and move tasks in queue - pip install streamlit-sortables
# TODO ^ write rearrange_all_jobs(self, positions) in util.scheduler.py
# TODO be able to delete tasks in queue
# TODO support download tasks when you write the new download scheduler
# TODO be able to queue later jobs for files that don't yet exist (i.e. convert then quantize) and handle errors in ordering

st.write('----')
st.write('Reorder the job list as you like, then click "Save Changes" to save your changes to the queue. If the drag-and-drop field bugs out, try going to another page and coming back.')

def update_jobs():
st.session_state["jobs"] = [item.strip() for item in get_scheduler().get_queue()]

update_jobs()
sorted_items = sort_items(st.session_state["jobs"], direction="vertical")
st.write(sorted_items)

if st.button("Refresh Job List", on_click=update_jobs):
# callback and update have to be in a very specific order for them to work
update_jobs()

st.write("You can only write changes when the scheduler is paused.")

if st.button("Save Changes", disabled=st.session_state["active"], on_click=lambda: get_scheduler().write_jobs(sorted_items)):
# callback and update have to be in a very specific order for them to work
update_jobs()

if st.button("Clear Job List", disabled=st.session_state["active"], on_click=get_scheduler().clear_queue):
# callback and update have to be in a very specific order for them to work
update_jobs()

delete_idx = st.number_input("Delete Job at Index", min_value=0, max_value=(len(st.session_state["jobs"])-1), value=0)
if st.button("Delete Job", disabled=st.session_state["active"], on_click=lambda: get_scheduler().remove_job(delete_idx)):
# callback and update have to be in a very specific order for them to work
update_jobs()

st.write('----')
st.title("Completed Jobs")
st.write('Completed jobs in plaintext:')

def update_completed():
st.session_state["completed"] = '\n'.join(get_scheduler().get_completed())

update_completed()
st.code(st.session_state["completed"])

st.button("Refresh Completed Jobs", on_click=update_completed)

st.write(get_scheduler().active)
if st.button("Clear Completed Jobs", on_click=get_scheduler().clear_completed):
# callback and update have to be in a very specific order for them to work
update_completed()
Loading

0 comments on commit bfe769e

Please sign in to comment.