Add config.json support with auto-populate and reset features
- Added ConfigParser module for parsing Hugging Face config files - Added model name display above form - Added file upload for config.json (accepts only .json files) - Added reset button to clear all fields - Added error indicators (!) with language-aware messages for missing fields - Auto-populates fields: num_hidden_layers, num_key_value_heads, head_dim, num_attention_heads, max_position_embeddings, full_attention_interval - Sets defaults for optional fields: parallel=1, model_size=0 - Auto-calculates after successful config upload - Default quantization set to f16
This commit is contained in:
parent
c471e1d0a9
commit
b8352ebd17
3 changed files with 290 additions and 1 deletions
|
|
@ -300,6 +300,49 @@ html[lang="en"] .tooltip-icon:hover::after {
|
|||
text-align: left;
|
||||
}
|
||||
|
||||
.model-name {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
color: #0000FF;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
display: none;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
cursor: help;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.error-icon:hover::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: #FF0000;
|
||||
color: #FFFFFF;
|
||||
padding: 8px 12px;
|
||||
font-size: 11px;
|
||||
white-space: pre-wrap;
|
||||
max-width: 300px;
|
||||
z-index: 1000;
|
||||
line-height: 1.4;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#reset-btn {
|
||||
background-color: #333333;
|
||||
width: auto;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
#reset-btn:hover {
|
||||
background-color: #555555;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.container {
|
||||
padding: 15px;
|
||||
|
|
|
|||
11
index.html
11
index.html
|
|
@ -13,6 +13,9 @@
|
|||
<button id="lang-toggle" class="lang-btn">EN</button>
|
||||
</div>
|
||||
|
||||
<!-- Model name display -->
|
||||
<div id="model-name-display" class="model-name"></div>
|
||||
|
||||
<form id="calculator-form">
|
||||
<div class="form-group">
|
||||
<label for="context-length" data-key="context_length_label">Длина Контекста (токены)</label>
|
||||
|
|
@ -79,6 +82,14 @@
|
|||
<span class="tooltip-icon" data-tooltip-ru="Интервал слоев для полного внимания. Слои считают полный KV кеш каждые N слоев. Уменьшает эффективное количество слоев" data-tooltip-en="Interval for full attention layers. Layers compute full KV cache every N layers. Reduces effective layer count"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="display: flex; align-items: center; gap: 10px;">
|
||||
<label for="config-file" data-key="config_file_label" style="margin-bottom: 0; white-space: nowrap;">Config.json</label>
|
||||
<input type="file" id="config-file" accept=".json" style="flex: 1; margin-bottom: 0;">
|
||||
<button type="button" id="reset-btn" data-key="reset_btn" style="margin-bottom: 0;">Сбросить</button>
|
||||
</div>
|
||||
<span class="tooltip-icon" data-tooltip-ru="Загрузите config.json из модели. Автоматически заполнит поля и запустит расчет" data-tooltip-en="Upload model config.json. Auto-fills fields and runs calculation"></span>
|
||||
<span class="error-icon" style="display:none; color: #FF0000; cursor: help; margin-left: 5px;">!</span>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<input type="checkbox" id="asymmetric">
|
||||
<label for="asymmetric" data-key="asymmetric_label">Асимметричный Контекст</label>
|
||||
|
|
|
|||
237
js/app.js
237
js/app.js
|
|
@ -29,6 +29,10 @@
|
|||
asymmetric_label: 'Асимметричный Контекст',
|
||||
v_type_label: 'Квантование V кеша',
|
||||
calculate_btn: 'Рассчитать',
|
||||
reset_btn: 'Сбросить',
|
||||
config_file_label: 'Config.json',
|
||||
config_file_ru: 'Загрузите config.json из модели. Автоматически заполнит поля и запустит расчет',
|
||||
config_file_en: 'Upload model config.json. Auto-fills fields and runs calculation',
|
||||
example_text: 'Пример: context=8192, layers=32, kv_heads=32, head_size=128, model_size=7 GB, parallel=1',
|
||||
results_title: 'Результаты',
|
||||
k_cache: 'K кеш:',
|
||||
|
|
@ -64,6 +68,10 @@
|
|||
asymmetric_label: 'Asymmetric Context',
|
||||
v_type_label: 'V cache quantization',
|
||||
calculate_btn: 'Calculate',
|
||||
reset_btn: 'Reset',
|
||||
config_file_label: 'Config.json',
|
||||
config_file_ru: 'Загрузите config.json из модели. Автоматически заполнит поля и запустит расчет',
|
||||
config_file_en: 'Upload model config.json. Auto-fills fields and runs calculation',
|
||||
example_text: 'Example: context=8192, layers=32, kv_heads=32, head_size=128, model_size=7 GB, parallel=1',
|
||||
results_title: 'Results',
|
||||
k_cache: 'K cache:',
|
||||
|
|
@ -80,6 +88,55 @@
|
|||
}
|
||||
};
|
||||
|
||||
// ConfigParser module
|
||||
const ConfigParser = {
|
||||
defaults: { parallel: 1, model_size: 0 },
|
||||
|
||||
parse(config) {
|
||||
const result = {
|
||||
modelName: config.model_type || 'Unknown',
|
||||
fields: {},
|
||||
errors: {},
|
||||
warnings: {}
|
||||
};
|
||||
|
||||
const textConfig = config.text_config || config;
|
||||
|
||||
// Required fields
|
||||
if (config.num_hidden_layers) {
|
||||
result.fields['num-layers'] = config.num_hidden_layers;
|
||||
}
|
||||
|
||||
if (config.num_key_value_heads) {
|
||||
result.fields['kv-heads'] = config.num_key_value_heads;
|
||||
}
|
||||
|
||||
if (textConfig.head_dim) {
|
||||
result.fields['head-size'] = textConfig.head_dim;
|
||||
} else if (textConfig.hidden_size && textConfig.num_attention_heads) {
|
||||
result.fields['head-size'] = Math.round(textConfig.hidden_size / textConfig.num_attention_heads);
|
||||
}
|
||||
|
||||
if (config.num_attention_heads) {
|
||||
result.fields['num-heads'] = config.num_attention_heads;
|
||||
}
|
||||
|
||||
if (textConfig.max_position_embeddings) {
|
||||
result.fields['context-length'] = textConfig.max_position_embeddings;
|
||||
}
|
||||
|
||||
if (textConfig.full_attention_interval) {
|
||||
result.fields['full-attention'] = textConfig.full_attention_interval;
|
||||
}
|
||||
|
||||
// Optional fields with defaults
|
||||
result.fields['parallel'] = config.parallel || this.defaults.parallel;
|
||||
result.fields['model-size'] = config.model_size_gb || this.defaults.model_size;
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// Tooltip translations
|
||||
const tooltips = {
|
||||
ru: {
|
||||
|
|
@ -121,6 +178,9 @@
|
|||
const resultsContainer = document.getElementById('results');
|
||||
const exampleText = document.getElementById('example-text');
|
||||
const langToggleBtn = document.getElementById('lang-toggle');
|
||||
const configFileInput = document.getElementById('config-file');
|
||||
const resetBtn = document.getElementById('reset-btn');
|
||||
const modelNameDisplay = document.getElementById('model-name-display');
|
||||
|
||||
// Initialize
|
||||
function init() {
|
||||
|
|
@ -140,6 +200,16 @@
|
|||
|
||||
// Language toggle
|
||||
langToggleBtn.addEventListener('click', toggleLanguage);
|
||||
|
||||
// Config file upload
|
||||
if (configFileInput) {
|
||||
configFileInput.addEventListener('change', handleConfigUpload);
|
||||
}
|
||||
|
||||
// Reset button
|
||||
if (resetBtn) {
|
||||
resetBtn.addEventListener('click', handleReset);
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle language
|
||||
|
|
@ -181,6 +251,23 @@
|
|||
currentLang === 'ru' ? 'Опционально, для общего объема памяти' : 'Optional, for total memory';
|
||||
document.getElementById('full-attention').placeholder =
|
||||
currentLang === 'ru' ? 'Опционально' : 'Optional';
|
||||
|
||||
// Update config field translations
|
||||
const configLabel = document.querySelector('#config-file + label');
|
||||
if (configLabel) {
|
||||
configLabel.textContent = translations[currentLang].config_file_label;
|
||||
}
|
||||
|
||||
const configTooltip = document.querySelector('#config-file + .tooltip-icon');
|
||||
if (configTooltip) {
|
||||
configTooltip.setAttribute('data-tooltip-ru', translations[currentLang].config_file_ru);
|
||||
configTooltip.setAttribute('data-tooltip-en', translations[currentLang].config_file_en);
|
||||
}
|
||||
|
||||
// Re-evaluate errors
|
||||
if (window.configErrors && Object.keys(window.configErrors).length > 0) {
|
||||
showConfigErrors();
|
||||
}
|
||||
}
|
||||
|
||||
// Update tooltips
|
||||
|
|
@ -326,6 +413,148 @@
|
|||
};
|
||||
}
|
||||
|
||||
// Config file upload handler
|
||||
function handleConfigUpload(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
// Only accept .json files
|
||||
if (!file.name.toLowerCase().endsWith('.json')) {
|
||||
showConfigError('config-file', currentLang === 'ru'
|
||||
? 'Неверный формат файла. Только .json файлы.'
|
||||
: 'Invalid file format. Only .json files allowed.');
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
try {
|
||||
const config = JSON.parse(e.target.result);
|
||||
populateFromConfig(config);
|
||||
} catch (err) {
|
||||
showConfigError('config-file', currentLang === 'ru'
|
||||
? 'Неверный JSON формат.'
|
||||
: 'Invalid JSON format.');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
// Populate form from config
|
||||
function populateFromConfig(config) {
|
||||
// Set default quantization
|
||||
document.getElementById('k-type').value = 'f16';
|
||||
document.getElementById('v-type').value = 'f16';
|
||||
|
||||
// Parse config using ConfigParser
|
||||
const parsed = ConfigParser.parse(config);
|
||||
|
||||
// Clear previous errors
|
||||
window.configErrors = {};
|
||||
|
||||
// Display model name
|
||||
modelNameDisplay.textContent = parsed.modelName;
|
||||
|
||||
// Set field values
|
||||
Object.keys(parsed.fields).forEach(fieldId => {
|
||||
const input = document.getElementById(fieldId);
|
||||
if (input) {
|
||||
input.value = parsed.fields[fieldId];
|
||||
}
|
||||
});
|
||||
|
||||
// Show warnings for default values
|
||||
if (config.parallel === undefined) {
|
||||
showConfigError('parallel', currentLang === 'ru'
|
||||
? 'Не найден параметр parallel. Использовано значение по умолчанию: 1.'
|
||||
: 'parallel not found. Default value used: 1.');
|
||||
}
|
||||
if (config.model_size_gb === undefined) {
|
||||
showConfigError('model-size', currentLang === 'ru'
|
||||
? 'Не найден параметр model_size_gb. Использовано значение по умолчанию: 0.'
|
||||
: 'model_size_gb not found. Default value used: 0.');
|
||||
}
|
||||
|
||||
// Show errors for missing required fields
|
||||
if (!config.num_hidden_layers) {
|
||||
showConfigError('num-layers', currentLang === 'ru'
|
||||
? 'Не найден параметр num_hidden_layers. Важно для расчёта количества слоёв в KV кеше.'
|
||||
: 'num_hidden_layers not found. Important for calculating KV cache layers.');
|
||||
}
|
||||
if (!config.num_key_value_heads) {
|
||||
showConfigError('kv-heads', currentLang === 'ru'
|
||||
? 'Не найден параметр num_key_value_heads. Важно для расчёта голов внимания.'
|
||||
: 'num_key_value_heads not found. Important for attention head calculation.');
|
||||
}
|
||||
const textConfig = config.text_config || config;
|
||||
if (!textConfig.head_dim && !(textConfig.hidden_size && textConfig.num_attention_heads)) {
|
||||
showConfigError('head-size', currentLang === 'ru'
|
||||
? 'Не найден параметр head_dim. Важно для размера каждой головы.'
|
||||
: 'head_dim not found. Important for head size calculation.');
|
||||
}
|
||||
|
||||
// Clear and show errors
|
||||
clearAllTooltips();
|
||||
showConfigErrors();
|
||||
|
||||
// Auto-calculate
|
||||
handleCalculate();
|
||||
}
|
||||
|
||||
// Reset button handler
|
||||
function handleReset() {
|
||||
// Clear all form fields
|
||||
document.getElementById('context-length').value = '';
|
||||
document.getElementById('kv-heads').value = '';
|
||||
document.getElementById('head-size').value = '';
|
||||
document.getElementById('num-heads').value = '';
|
||||
document.getElementById('num-layers').value = '';
|
||||
document.getElementById('model-size').value = '';
|
||||
document.getElementById('parallel').value = '1';
|
||||
document.getElementById('full-attention').value = '';
|
||||
|
||||
// Reset quantization
|
||||
document.getElementById('k-type').value = 'KV';
|
||||
document.getElementById('v-type').value = 'f16';
|
||||
document.getElementById('asymmetric').checked = false;
|
||||
document.getElementById('asymmetric-controls').classList.remove('visible');
|
||||
|
||||
// Clear model name
|
||||
modelNameDisplay.textContent = '';
|
||||
|
||||
// Clear errors
|
||||
window.configErrors = {};
|
||||
clearAllTooltips();
|
||||
}
|
||||
|
||||
// Show config error
|
||||
function showConfigError(field, message) {
|
||||
window.configErrors[field] = message;
|
||||
}
|
||||
|
||||
// Clear all tooltips
|
||||
function clearAllTooltips() {
|
||||
document.querySelectorAll('.tooltip-icon').forEach(icon => {
|
||||
icon.removeAttribute('data-tooltip-ru');
|
||||
icon.removeAttribute('data-tooltip-en');
|
||||
});
|
||||
document.querySelectorAll('.error-icon').forEach(icon => {
|
||||
icon.style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// Show config errors
|
||||
function showConfigErrors() {
|
||||
Object.keys(window.configErrors).forEach(fieldId => {
|
||||
const errorIcon = document.querySelector(`#${fieldId} ~ .error-icon`);
|
||||
if (errorIcon) {
|
||||
errorIcon.style.display = 'inline-block';
|
||||
errorIcon.setAttribute('data-tooltip-ru', window.configErrors[fieldId]);
|
||||
errorIcon.setAttribute('data-tooltip-en', window.configErrors[fieldId]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Expose functions globally for testing
|
||||
window.CalculatorApp = {
|
||||
collectParams,
|
||||
|
|
@ -334,7 +563,13 @@
|
|||
updateExampleText,
|
||||
toggleLanguage,
|
||||
updateText,
|
||||
updateTooltips
|
||||
updateTooltips,
|
||||
handleConfigUpload,
|
||||
populateFromConfig,
|
||||
handleReset,
|
||||
showConfigError,
|
||||
clearAllTooltips,
|
||||
showConfigErrors
|
||||
};
|
||||
|
||||
// Initialize when DOM is ready
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue