diff --git a/src/msquant/app/pages/configure.py b/src/msquant/app/pages/configure.py index 9ff2c04..113e39b 100644 --- a/src/msquant/app/pages/configure.py +++ b/src/msquant/app/pages/configure.py @@ -69,11 +69,23 @@ def start_quantization(): ui.label('Calibration Settings').classes('text-2xl font-bold') ui.input('Calibration Dataset', placeholder='e.g., wikitext').classes('w-full').bind_value(form_data, 'calib_dataset') ui.input('Dataset Config', placeholder='e.g., wikitext-2-raw-v1 (optional)').classes('w-full').bind_value(form_data, 'calib_config') - ui.input('Dataset Split', placeholder='e.g., train (optional)').classes('w-full').bind_value(form_data, 'calib_split') - + ui.select( + ['train', 'test', 'validation', 'train[:512]', 'test[:512]'], + value='train', + label='Dataset Split' + ).classes('w-full').bind_value(form_data, 'calib_split').props('use-input new-value-mode=add-unique clearable') + with ui.row().classes('w-full gap-4'): - ui.number('Max Calibration Samples', value=256, min=1, max=10000).classes('flex-1').bind_value(form_data, 'max_calib_samples') - ui.number('Max Sequence Length', value=2048, min=128, max=8192).classes('flex-1').bind_value(form_data, 'max_seq_length') + ui.select( + [128, 256, 512, 1024, 2048], + value=256, + label='Max Calibration Samples' + ).classes('flex-1').bind_value(form_data, 'max_calib_samples').props('use-input new-value-mode=add-unique') + ui.select( + [512, 1024, 2048, 4096, 8192], + value=2048, + label='Max Sequence Length' + ).classes('flex-1').bind_value(form_data, 'max_seq_length').props('use-input new-value-mode=add-unique') ui.separator() @@ -81,7 +93,11 @@ def start_quantization(): ui.label('AWQ Settings (only for AWQ method)').classes('text-2xl font-bold') with ui.row().classes('w-full gap-4'): ui.select([2, 3, 4, 5, 8], value=4, label='Weight Bits').classes('flex-1').bind_value(form_data, 'w_bit') - ui.number('Group Size', value=128, min=1).classes('flex-1').bind_value(form_data, 'group_size') + ui.select( + [32, 64, 128, 256], + value=128, + label='Group Size' + ).classes('flex-1').bind_value(form_data, 'group_size').props('use-input new-value-mode=add-unique') ui.checkbox('Zero Point', value=True).bind_value(form_data, 'zero_point') ui.separator() diff --git a/src/msquant/app/pages/monitor.py b/src/msquant/app/pages/monitor.py index b207223..bba918e 100644 --- a/src/msquant/app/pages/monitor.py +++ b/src/msquant/app/pages/monitor.py @@ -12,7 +12,10 @@ def create_monitor_page(job_service: JobService, gpu_monitor: GPUMonitor): # State for selected GPU selected_gpu_index = {"value": 0} available_gpus = {"list": []} - + + # Track last notified status to prevent notification spam + last_notified_status: dict[str, str | None] = {"status": None, "message": None} + with ui.column().classes('w-full p-8 gap-4'): ui.label('Job Monitor').classes('text-4xl font-bold') @@ -79,25 +82,38 @@ def update_status(): """Update job status and logs.""" status = job_service.get_status() status_label.text = f'Status: {status.value.upper()}' - + # Enable/disable cancel button based on status cancel_btn.props(f'disable={status.value != "running"}') - + # Get logs logs = job_service.get_logs(last_n=100) log_output.value = '\n'.join(logs) if logs else 'No logs yet...' - - # Show result or error - if status.value == 'completed': + + # Show result or error - but only once per status change + current_status = status.value + + if current_status == 'completed': result = job_service.get_result() - if result: + if result and last_notified_status["status"] != 'completed': ui.notify(f'Job completed! Output: {result}', type='positive') - elif status.value == 'failed': + last_notified_status["status"] = 'completed' + last_notified_status["message"] = result + elif current_status == 'failed': error = job_service.get_error() - if error: + if error and (last_notified_status["status"] != 'failed' or last_notified_status["message"] != error): ui.notify(f'Job failed: {error}', type='negative') - elif status.value == 'cancelled': - ui.notify('Job was cancelled', type='warning') + last_notified_status["status"] = 'failed' + last_notified_status["message"] = error + elif current_status == 'cancelled': + if last_notified_status["status"] != 'cancelled': + ui.notify('Job was cancelled', type='warning') + last_notified_status["status"] = 'cancelled' + last_notified_status["message"] = 'cancelled' + elif current_status == 'idle': + # Reset notification state when job is idle (ready for next job) + last_notified_status["status"] = None + last_notified_status["message"] = None def cancel_job(): """Cancel the current job.""" diff --git a/src/msquant/core/quantizer/config.py b/src/msquant/core/quantizer/config.py index c1ca636..0a18c1a 100644 --- a/src/msquant/core/quantizer/config.py +++ b/src/msquant/core/quantizer/config.py @@ -35,12 +35,35 @@ def __init__( self.calib_dataset = calib_dataset self.calib_config = calib_config self.calib_split = calib_split - self.max_calib_samples = max_calib_samples - self.max_seq_length = max_seq_length - - # AWQ - self.w_bit = w_bit - self.group_size = group_size + + # Convert numeric parameters with validation + try: + self.max_calib_samples = int(max_calib_samples) + if self.max_calib_samples <= 0: + raise ValueError("max_calib_samples must be positive") + except (ValueError, TypeError) as e: + raise ValueError(f"Invalid max_calib_samples value '{max_calib_samples}': must be a positive integer") from e + + try: + self.max_seq_length = int(max_seq_length) + if self.max_seq_length <= 0: + raise ValueError("max_seq_length must be positive") + except (ValueError, TypeError) as e: + raise ValueError(f"Invalid max_seq_length value '{max_seq_length}': must be a positive integer") from e + + # AWQ parameters + try: + self.w_bit = int(w_bit) + except (ValueError, TypeError) as e: + raise ValueError(f"Invalid w_bit value '{w_bit}': must be an integer") from e + + try: + self.group_size = int(group_size) + if self.group_size <= 0: + raise ValueError("group_size must be positive") + except (ValueError, TypeError) as e: + raise ValueError(f"Invalid group_size value '{group_size}': must be a positive integer") from e + self.zero_point = zero_point # NVFP4