Spaces:
Running
Running
| <html lang="ja"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Z-Image LoRA Command Builder</title> | |
| <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;600&family=Syne:wght@400;600;800&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --bg: #0a0c10; --surface: #111318; --surface2: #181c24; --border: #252a35; | |
| --accent: #00e5a0; --text: #e2e8f0; --text-dim: #64748b; --text-bright: #f8fafc; | |
| --radius: 8px; --mono: 'JetBrains Mono', monospace; --sans: 'Syne', sans-serif; | |
| } | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { background: var(--bg); color: var(--text); font-family: var(--sans); min-height: 100vh; line-height: 1.6; } | |
| body::before { | |
| content: ''; position: fixed; inset: 0; | |
| background-image: linear-gradient(rgba(0,229,160,0.025) 1px, transparent 1px), linear-gradient(90deg, rgba(0,229,160,0.025) 1px, transparent 1px); | |
| background-size: 40px 40px; pointer-events: none; z-index: 0; | |
| } | |
| .container { max-width: 1000px; margin: 0 auto; padding: 32px 24px; position: relative; z-index: 1; } | |
| header { margin-bottom: 24px; border-left: 3px solid var(--accent); padding-left: 20px; } | |
| header h1 { font-size: 1.8rem; font-weight: 800; color: var(--text-bright); letter-spacing: -0.5px; } | |
| header h1 span { color: var(--accent); } | |
| header p { color: var(--text-dim); font-size: 0.8rem; font-family: var(--mono); margin-top: 4px; } | |
| .section { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px 22px; margin-bottom: 12px; } | |
| .section-title { font-size: 0.6rem; font-family: var(--mono); color: var(--accent); text-transform: uppercase; letter-spacing: 2px; margin-bottom: 14px; display: flex; align-items: center; gap: 8px; } | |
| .section-title::after { content: ''; flex: 1; height: 1px; background: var(--border); } | |
| .grid { display: grid; gap: 12px; } | |
| .g2 { grid-template-columns: 1fr 1fr; } | |
| .g3 { grid-template-columns: 1fr 1fr 1fr; } | |
| .g4 { grid-template-columns: 1fr 1fr 1fr 1fr; } | |
| .span2 { grid-column: span 2; } | |
| .full { grid-column: 1 / -1; } | |
| @media (max-width: 680px) { .g2,.g3,.g4 { grid-template-columns: 1fr; } .span2,.full { grid-column: span 1; } } | |
| .field { display: flex; flex-direction: column; gap: 5px; } | |
| label { font-size: 0.7rem; font-family: var(--mono); color: var(--text-dim); display: flex; justify-content: space-between; align-items: baseline; } | |
| label .hint { font-size: 0.6rem; color: rgba(100,116,139,0.5); } | |
| input[type="text"], input[type="number"], select { | |
| background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; | |
| color: var(--text-bright); padding: 8px 11px; font-family: var(--mono); font-size: 0.78rem; | |
| outline: none; transition: border-color 0.15s, box-shadow 0.15s; width: 100%; | |
| } | |
| input:focus, select:focus { border-color: rgba(0,229,160,0.4); box-shadow: 0 0 0 2px rgba(0,229,160,0.07); } | |
| select option { background: #181c24; } | |
| .check-group { display: flex; flex-wrap: wrap; gap: 12px; } | |
| .check-item { display: flex; align-items: center; gap: 6px; cursor: pointer; font-family: var(--mono); font-size: 0.74rem; color: var(--text); white-space: nowrap; } | |
| .check-item input[type="checkbox"] { width: 14px; height: 14px; accent-color: var(--accent); cursor: pointer; } | |
| .sep { height: 1px; background: var(--border); margin: 12px 0; } | |
| .os-toggle { display: flex; gap: 2px; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; padding: 3px; width: fit-content; } | |
| .os-btn { padding: 5px 18px; border: none; border-radius: 4px; background: none; color: var(--text-dim); font-family: var(--mono); font-size: 0.74rem; cursor: pointer; transition: all 0.15s; } | |
| .os-btn.active { background: var(--surface); color: var(--accent); border: 1px solid rgba(0,229,160,0.2); } | |
| .gen-btn { background: var(--accent); color: #000; border: none; padding: 13px; border-radius: var(--radius); font-family: var(--mono); font-size: 0.85rem; font-weight: 600; cursor: pointer; width: 100%; margin: 16px 0 14px; transition: all 0.15s; letter-spacing: 0.5px; } | |
| .gen-btn:hover { background: #00ffb3; transform: translateY(-1px); box-shadow: 0 4px 20px rgba(0,229,160,0.25); } | |
| .gen-btn:active { transform: translateY(0); } | |
| .output-wrap { background: #060810; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; } | |
| .output-header { display: flex; justify-content: space-between; align-items: center; padding: 9px 16px; background: var(--surface); border-bottom: 1px solid var(--border); } | |
| .output-label { font-size: 0.6rem; font-family: var(--mono); color: var(--text-dim); text-transform: uppercase; letter-spacing: 1.5px; } | |
| .copy-btn { background: rgba(0,229,160,0.1); border: 1px solid rgba(0,229,160,0.2); color: var(--accent); padding: 4px 14px; border-radius: 4px; font-family: var(--mono); font-size: 0.7rem; cursor: pointer; transition: all 0.15s; } | |
| .copy-btn:hover { background: rgba(0,229,160,0.2); } | |
| .copy-btn.copied { color: #000; background: var(--accent); border-color: var(--accent); } | |
| pre { padding: 20px; font-family: var(--mono); font-size: 0.75rem; line-height: 1.9; color: var(--text); white-space: pre-wrap; word-break: break-all; } | |
| .k { color: #7dd3fc; } .c { color: var(--accent); font-weight: 600; } .ct { color: #f97316; } | |
| /* ===== PRESET BAR ===== */ | |
| .preset-bar { display: flex; gap: 10px; margin-bottom: 16px; } | |
| .preset-btn { | |
| flex: 1; padding: 11px 16px; border-radius: var(--radius); font-family: var(--mono); | |
| font-size: 0.78rem; font-weight: 600; cursor: pointer; border: none; transition: all 0.15s; | |
| display: flex; align-items: center; justify-content: center; gap: 8px; | |
| } | |
| .pbtn-export { background: rgba(0,229,160,0.12); color: var(--accent); border: 1px solid rgba(0,229,160,0.25); } | |
| .pbtn-export:hover { background: rgba(0,229,160,0.22); } | |
| .pbtn-export.done { background: var(--accent); color: #000; border-color: var(--accent); } | |
| /* ===== DROP ZONE ===== */ | |
| .drop-zone { | |
| border: 2px dashed var(--border); border-radius: var(--radius); | |
| background: var(--surface); padding: 0; | |
| display: flex; align-items: center; gap: 16px; | |
| cursor: pointer; transition: all 0.2s; position: relative; overflow: hidden; | |
| min-height: 56px; padding: 0 20px; | |
| } | |
| .drop-zone:hover, .drop-zone.drag-over { border-color: var(--accent); background: rgba(0,229,160,0.05); } | |
| .drop-zone.drag-over { box-shadow: 0 0 0 3px rgba(0,229,160,0.12); } | |
| .drop-zone .dz-icon { font-size: 1.4rem; flex-shrink: 0; } | |
| .drop-zone .dz-text { font-family: var(--mono); font-size: 0.75rem; color: var(--text-dim); } | |
| .drop-zone .dz-text span { color: var(--accent); } | |
| .drop-zone input[type="file"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%; } | |
| .drop-zone.loaded { border-color: var(--accent); border-style: solid; } | |
| .drop-zone.loaded .dz-text { color: var(--accent); } | |
| /* ===== TOAST ===== */ | |
| .toast { | |
| position: fixed; bottom: 28px; right: 28px; z-index: 999; | |
| background: var(--surface); border: 1px solid var(--accent); border-radius: var(--radius); | |
| padding: 12px 20px; font-family: var(--mono); font-size: 0.78rem; color: var(--accent); | |
| box-shadow: 0 4px 24px rgba(0,0,0,0.5); | |
| transform: translateY(16px); opacity: 0; transition: all 0.22s; pointer-events: none; | |
| } | |
| .toast.show { transform: translateY(0); opacity: 1; } | |
| .toast.err { border-color: #f97316; color: #f97316; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <h1>Z-Image <span>LoRA</span> Command Builder</h1> | |
| <p>musubi-tuner (kohya-ss) — zimage_train_network.py</p> | |
| </header> | |
| <!-- PRESET EXPORT / IMPORT --> | |
| <div class="preset-bar"> | |
| <button class="preset-btn pbtn-export" id="exportBtn" onclick="exportSettings()"> | |
| ⬇ 設定をJSONで保存 | |
| </button> | |
| </div> | |
| <div class="drop-zone" id="dropZone"> | |
| <span class="dz-icon">📂</span> | |
| <span class="dz-text">設定ファイル(JSON)を<span>ドラッグ&ドロップ</span>、またはクリックして読み込む</span> | |
| <input type="file" accept=".json" onchange="loadFile(this.files[0])"> | |
| </div> | |
| <div style="height:16px"></div> | |
| <!-- OS --> | |
| <div class="section"> | |
| <div class="section-title">OS / 改行スタイル</div> | |
| <div class="os-toggle"> | |
| <button class="os-btn active" id="btn-win" onclick="setOS('win')">Windows ( ^ )</button> | |
| <button class="os-btn" id="btn-lnx" onclick="setOS('lnx')">Linux / Mac ( \ )</button> | |
| </div> | |
| </div> | |
| <!-- PATHS --> | |
| <div class="section"> | |
| <div class="section-title">モデル・パス</div> | |
| <div class="grid g2"> | |
| <div class="field span2"> | |
| <label>--dit <span class="hint">分割safetensorsは1枚目を指定</span></label> | |
| <input type="text" id="dit" value="path/to/dit_model"> | |
| </div> | |
| <div class="field"> | |
| <label>--vae</label> | |
| <input type="text" id="vae" value="path/to/vae_model"> | |
| </div> | |
| <div class="field"> | |
| <label>--text_encoder</label> | |
| <input type="text" id="te" value="path/to/text_encoder"> | |
| </div> | |
| <div class="field span2"> | |
| <label>--dataset_config <span class="hint">TOML</span></label> | |
| <input type="text" id="ds" value="path/to/dataset_config.toml"> | |
| </div> | |
| <div class="field"> | |
| <label>--output_dir</label> | |
| <input type="text" id="outdir" value="path/to/output_dir"> | |
| </div> | |
| <div class="field"> | |
| <label>--output_name</label> | |
| <input type="text" id="outname" value="my_lora"> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- LORA --> | |
| <div class="section"> | |
| <div class="section-title">LoRAパラメータ</div> | |
| <div class="grid g4"> | |
| <div class="field"> | |
| <label>--network_module</label> | |
| <select id="netmod"> | |
| <option value="networks.lora_zimage" selected>lora_zimage(推奨)</option> | |
| <option value="networks.lora">lora(汎用)</option> | |
| </select> | |
| </div> | |
| <div class="field"> | |
| <label>--network_dim <span class="hint">Rank</span></label> | |
| <select id="ndim" onchange="syncAlpha()"> | |
| <option value="4">4</option><option value="8">8</option><option value="16">16</option> | |
| <option value="32" selected>32</option><option value="64">64</option><option value="128">128</option> | |
| </select> | |
| </div> | |
| <div class="field"> | |
| <label>--network_alpha</label> | |
| <select id="nalpha"> | |
| <option value="4">4</option><option value="8">8</option><option value="16">16</option> | |
| <option value="32" selected>32</option><option value="64">64</option><option value="128">128</option> | |
| </select> | |
| </div> | |
| <div class="field"> | |
| <label style="opacity:0">-</label> | |
| <label class="check-item" style="padding-top:8px;"><input type="checkbox" id="use_loha"> LoHa/LoKr</label> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- STEPS --> | |
| <div class="section"> | |
| <div class="section-title">ステップ・シード</div> | |
| <div class="grid g4"> | |
| <div class="field"> | |
| <label>--max_train_steps</label> | |
| <input type="number" id="steps" value="3000" min="100" step="100"> | |
| </div> | |
| <div class="field"> | |
| <label>--save_every_n_steps</label> | |
| <input type="number" id="save_steps" value="200" min="50" step="50"> | |
| </div> | |
| <div class="field"> | |
| <label>--seed</label> | |
| <input type="number" id="seed" value="42"> | |
| </div> | |
| <div class="field"> | |
| <label>--max_data_loader_n_workers</label> | |
| <input type="number" id="workers" value="2" min="0" max="8"> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- OPTIMIZER --> | |
| <div class="section"> | |
| <div class="section-title">オプティマイザ / 学習率</div> | |
| <div class="grid g4"> | |
| <div class="field"> | |
| <label>--optimizer_type</label> | |
| <select id="opttype" onchange="onOptimizerChange()"> | |
| <option value="adamw8bit" selected>adamw8bit</option> | |
| <option value="adamw">adamw</option> | |
| <option value="adafactor">adafactor</option> | |
| <option value="prodigy">prodigy</option> | |
| <option value="came">came</option> | |
| <option value="Lion">Lion</option> | |
| <option value="Lion8bit">Lion8bit</option> | |
| </select> | |
| </div> | |
| <div class="field"> | |
| <label>--learning_rate <span class="hint" id="lr-hint"></span></label> | |
| <select id="lr" onchange="toggleLRCustom()"> | |
| <option value="1e-4" selected>1e-4</option> | |
| <option value="5e-5">5e-5(安定)</option> | |
| <option value="1e-5">1e-5(Lion推奨)</option> | |
| <option value="2e-4">2e-4(高速)</option> | |
| <option value="1e-3">1e-3</option> | |
| <option value="custom">カスタム入力</option> | |
| </select> | |
| </div> | |
| <div class="field" id="lr_custom_wrap" style="display:none"> | |
| <label>カスタム値</label> | |
| <input type="text" id="lr_custom" placeholder="例: 3e-5"> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- LR SCHEDULER --> | |
| <div class="section"> | |
| <div class="section-title">LRスケジューラ</div> | |
| <div class="grid g4"> | |
| <div class="field"> | |
| <label>--lr_scheduler</label> | |
| <select id="lr_sched" onchange="onSchedChange()"> | |
| <option value="constant" selected>constant</option> | |
| <option value="cosine">cosine</option> | |
| <option value="cosine_with_restarts">cosine_with_restarts</option> | |
| <option value="constant_with_warmup">constant_with_warmup</option> | |
| <option value="linear">linear</option> | |
| <option value="polynomial">polynomial</option> | |
| </select> | |
| </div> | |
| <div class="field" id="warmup_wrap" style="display:none"> | |
| <label>--lr_warmup_steps</label> | |
| <input type="number" id="lr_warmup" value="100" min="0" step="10"> | |
| </div> | |
| <div class="field" id="restart_wrap" style="display:none"> | |
| <label>--lr_scheduler_num_cycles</label> | |
| <input type="number" id="lr_cycles" value="1" min="1"> | |
| </div> | |
| <div class="field" id="poly_wrap" style="display:none"> | |
| <label>--lr_scheduler_power</label> | |
| <input type="number" id="lr_power" value="1.0" min="0.1" step="0.1"> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- TIMESTEP --> | |
| <div class="section"> | |
| <div class="section-title">Timestep / Flow</div> | |
| <div class="grid g3"> | |
| <div class="field"> | |
| <label>--timestep_sampling</label> | |
| <select id="ts"> | |
| <option value="shift" selected>shift(Z-Image推奨)</option> | |
| <option value="sigmoid">sigmoid</option> | |
| <option value="uniform">uniform</option> | |
| <option value="logit_normal">logit_normal</option> | |
| </select> | |
| </div> | |
| <div class="field"> | |
| <label>--discrete_flow_shift <span class="hint">Z-Image: 2.0</span></label> | |
| <select id="dfs" onchange="toggleDFSCustom()"> | |
| <option value="2.0" selected>2.0</option> | |
| <option value="1.0">1.0</option> | |
| <option value="3.0">3.0</option> | |
| <option value="custom">カスタム入力</option> | |
| </select> | |
| </div> | |
| <div class="field" id="dfs_custom_wrap" style="display:none"> | |
| <label>カスタム値</label> | |
| <input type="text" id="dfs_custom" placeholder="例: 1.5"> | |
| </div> | |
| <div class="field"> | |
| <label>--weighting_scheme</label> | |
| <select id="ws"> | |
| <option value="none" selected>none</option> | |
| <option value="sigma_sqrt">sigma_sqrt</option> | |
| <option value="logit_normal">logit_normal</option> | |
| <option value="mode">mode</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- PRECISION --> | |
| <div class="section"> | |
| <div class="section-title">精度 / アテンション</div> | |
| <div class="grid g2"> | |
| <div class="field"> | |
| <label>--mixed_precision</label> | |
| <select id="mp"> | |
| <option value="bf16" selected>bf16(推奨)</option> | |
| <option value="fp16">fp16</option> | |
| <option value="no">no (fp32)</option> | |
| </select> | |
| </div> | |
| <div class="field"> | |
| <label style="margin-bottom:8px;">アテンション</label> | |
| <div class="check-group"> | |
| <label class="check-item"><input type="checkbox" id="sdpa" checked> --sdpa</label> | |
| <label class="check-item"><input type="checkbox" id="xf"> --xformers</label> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- VRAM --> | |
| <div class="section"> | |
| <div class="section-title">VRAM最適化</div> | |
| <div class="check-group" style="margin-bottom:14px;"> | |
| <label class="check-item"><input type="checkbox" id="gc" checked> --gradient_checkpointing</label> | |
| <label class="check-item"><input type="checkbox" id="pdlw" checked> --persistent_data_loader_workers</label> | |
| <label class="check-item"><input type="checkbox" id="fp8"> --fp8_base</label> | |
| <label class="check-item"><input type="checkbox" id="fp8te"> --fp8_llm</label> | |
| <label class="check-item"><input type="checkbox" id="bswap_patch"> --block_swap_optimizer_patch_params <span style="color:#f97316;font-size:0.65rem;margin-left:4px;">Lion+blocks_to_swap時に必要</span></label> | |
| </div> | |
| <div class="grid g4"> | |
| <div class="field"> | |
| <label>--blocks_to_swap <span class="hint">0=無効</span></label> | |
| <input type="number" id="bswap" value="0" min="0" max="36"> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ACCELERATE --> | |
| <div class="section"> | |
| <div class="section-title">accelerate設定</div> | |
| <div class="grid g4"> | |
| <div class="field"> | |
| <label>--num_cpu_threads_per_process</label> | |
| <input type="number" id="cpu_t" value="1" min="1"> | |
| </div> | |
| <div class="field"> | |
| <label>--num_processes <span class="hint">マルチGPU時</span></label> | |
| <input type="number" id="nproc" value="1" min="1"> | |
| </div> | |
| </div> | |
| </div> | |
| <button class="gen-btn" onclick="gen()">▶ コマンドを生成</button> | |
| <div class="output-wrap"> | |
| <div class="output-header"> | |
| <span class="output-label">Generated Command</span> | |
| <button class="copy-btn" id="copybtn" onclick="copyCmd()">Copy</button> | |
| </div> | |
| <pre id="output"></pre> | |
| </div> | |
| </div> | |
| <!-- TOAST --> | |
| <div class="toast" id="toast"></div> | |
| <script> | |
| let OS = 'win'; | |
| // ===== SETTINGS KEYS ===== | |
| // すべてのフォームIDを列挙(テキスト/数値/select) | |
| const TEXT_IDS = ['dit','vae','te','ds','outdir','outname','lr_custom','dfs_custom']; | |
| const SELECT_IDS = ['netmod','ndim','nalpha','opttype','lr','lr_sched','ts','dfs','ws','mp']; | |
| const NUMBER_IDS = ['steps','save_steps','seed','workers','lr_warmup','lr_cycles','lr_power','cpu_t','nproc','bswap']; | |
| const CHECK_IDS = ['sdpa','xf','gc','pdlw','fp8','fp8te','bswap_patch','use_loha']; | |
| // ===== EXPORT ===== | |
| function exportSettings() { | |
| const data = { _version: 1, os: OS }; | |
| TEXT_IDS.forEach(id => { const el = document.getElementById(id); if(el) data[id] = el.value; }); | |
| SELECT_IDS.forEach(id => { const el = document.getElementById(id); if(el) data[id] = el.value; }); | |
| NUMBER_IDS.forEach(id => { const el = document.getElementById(id); if(el) data[id] = el.value; }); | |
| CHECK_IDS.forEach(id => { const el = document.getElementById(id); if(el) data[id] = el.checked; }); | |
| const json = JSON.stringify(data, null, 2); | |
| const blob = new Blob([json], { type: 'application/json' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| const name = (document.getElementById('outname').value || 'lora-settings').replace(/[\\/:*?"<>|]/g,'_'); | |
| a.href = url; | |
| a.download = name + '.json'; | |
| a.click(); | |
| URL.revokeObjectURL(url); | |
| const btn = document.getElementById('exportBtn'); | |
| btn.classList.add('done'); | |
| btn.textContent = '✓ 保存しました'; | |
| setTimeout(() => { btn.classList.remove('done'); btn.textContent = '⬇ 設定をJSONで保存'; }, 2200); | |
| } | |
| // ===== IMPORT ===== | |
| function applySettings(data) { | |
| if (!data || data._version !== 1) { showToast('⚠ 形式が不正なファイルです', true); return; } | |
| if (data.os) setOS(data.os); | |
| TEXT_IDS.forEach(id => { if (data[id] !== undefined) { const el = document.getElementById(id); if(el) el.value = data[id]; } }); | |
| SELECT_IDS.forEach(id => { | |
| if (data[id] !== undefined) { | |
| const el = document.getElementById(id); | |
| if (!el) return; | |
| const exists = Array.from(el.options).some(o => o.value === String(data[id])); | |
| el.value = exists ? data[id] : el.options[0].value; | |
| } | |
| }); | |
| NUMBER_IDS.forEach(id => { | |
| if (data[id] !== undefined) { const el = document.getElementById(id); if(el) el.value = data[id]; } | |
| }); | |
| CHECK_IDS.forEach(id => { if (data[id] !== undefined) { const el = document.getElementById(id); if(el) el.checked = !!data[id]; } }); | |
| // UI状態を再同期 | |
| onSchedChange(); | |
| onOptimizerChange(); | |
| toggleLRCustom(); | |
| toggleDFSCustom(); | |
| gen(); | |
| showToast('✓ 設定を読み込みました'); | |
| } | |
| function loadFile(file) { | |
| if (!file) return; | |
| // 拡張子またはMIMEタイプで判定(.jsonまたはapplication/json) | |
| const isJson = file.name.toLowerCase().endsWith('.json') || file.type === 'application/json'; | |
| if (!isJson) { showToast('⚠ JSONファイルを選択してください', true); return; } | |
| const reader = new FileReader(); | |
| reader.onload = e => { | |
| try { | |
| const raw = e.target.result; | |
| const data = JSON.parse(raw); | |
| applySettings(data); | |
| const dz = document.getElementById('dropZone'); | |
| dz.classList.add('loaded'); | |
| dz.querySelector('.dz-text').innerHTML = `✓ <span>${file.name}</span> を読み込みました`; | |
| } catch(err) { | |
| console.error('JSON parse error:', err); | |
| showToast('⚠ ファイルの読み込みに失敗しました: ' + err.message, true); | |
| } | |
| }; | |
| reader.onerror = () => showToast('⚠ ファイルの読み取りに失敗しました', true); | |
| reader.readAsText(file, 'UTF-8'); | |
| } | |
| // ===== DRAG & DROP ===== | |
| const dropZone = document.getElementById('dropZone'); | |
| ['dragenter','dragover'].forEach(ev => { | |
| dropZone.addEventListener(ev, e => { e.preventDefault(); dropZone.classList.add('drag-over'); }); | |
| }); | |
| ['dragleave','dragend'].forEach(ev => { | |
| dropZone.addEventListener(ev, () => dropZone.classList.remove('drag-over')); | |
| }); | |
| dropZone.addEventListener('drop', e => { | |
| e.preventDefault(); | |
| dropZone.classList.remove('drag-over'); | |
| const file = e.dataTransfer.files[0]; | |
| if (file) loadFile(file); | |
| }); | |
| // ===== TOAST ===== | |
| function showToast(msg, isErr = false) { | |
| const t = document.getElementById('toast'); | |
| t.textContent = msg; | |
| t.className = 'toast' + (isErr ? ' err' : ''); | |
| void t.offsetWidth; | |
| t.classList.add('show'); | |
| setTimeout(() => t.classList.remove('show'), 2800); | |
| } | |
| // ===== OS ===== | |
| function setOS(os) { | |
| OS = os; | |
| document.getElementById('btn-win').classList.toggle('active', os==='win'); | |
| document.getElementById('btn-lnx').classList.toggle('active', os==='lnx'); | |
| gen(); | |
| } | |
| function syncAlpha() { | |
| document.getElementById('nalpha').value = document.getElementById('ndim').value; | |
| gen(); | |
| } | |
| function onSchedChange() { | |
| const v = document.getElementById('lr_sched').value; | |
| const needsWarmup = ['constant_with_warmup','cosine_with_restarts','cosine','linear','polynomial'].includes(v); | |
| const needsCycles = v === 'cosine_with_restarts'; | |
| const needsPoly = v === 'polynomial'; | |
| document.getElementById('warmup_wrap').style.display = needsWarmup ? 'block' : 'none'; | |
| document.getElementById('restart_wrap').style.display = needsCycles ? 'block' : 'none'; | |
| document.getElementById('poly_wrap').style.display = needsPoly ? 'block' : 'none'; | |
| gen(); | |
| } | |
| function onOptimizerChange() { | |
| const opt = document.getElementById('opttype').value; | |
| const lrHint = document.getElementById('lr-hint'); | |
| if (opt === 'Lion' || opt === 'Lion8bit') { | |
| lrHint.textContent = 'Lion推奨: 1e-5〜1e-4'; | |
| lrHint.style.color = '#f97316'; | |
| } else { | |
| lrHint.textContent = ''; | |
| } | |
| gen(); | |
| } | |
| function toggleLRCustom() { | |
| document.getElementById('lr_custom_wrap').style.display = | |
| document.getElementById('lr').value === 'custom' ? 'block' : 'none'; | |
| gen(); | |
| } | |
| function toggleDFSCustom() { | |
| document.getElementById('dfs_custom_wrap').style.display = | |
| document.getElementById('dfs').value === 'custom' ? 'block' : 'none'; | |
| gen(); | |
| } | |
| function getLR() { | |
| const v = document.getElementById('lr').value; | |
| return v === 'custom' ? (document.getElementById('lr_custom').value || '1e-4') : v; | |
| } | |
| function getDFS() { | |
| const v = document.getElementById('dfs').value; | |
| return v === 'custom' ? (document.getElementById('dfs_custom').value || '2.0') : v; | |
| } | |
| function esc(s) { return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); } | |
| // ===== COMMAND GENERATOR ===== | |
| function gen() { | |
| const cont = OS === 'win' ? '^' : '\\'; | |
| const C = ` ${cont}`; | |
| const v = id => document.getElementById(id)?.value ?? ''; | |
| const c = id => document.getElementById(id)?.checked ?? false; | |
| const dit=v('dit'), vae=v('vae'), te=v('te'), ds=v('ds'); | |
| const outdir=v('outdir'), outname=v('outname'); | |
| const netmod=v('netmod'), ndim=v('ndim'), nalpha=v('nalpha'); | |
| const steps=v('steps'), saveS=v('save_steps'), seed=v('seed'), workers=v('workers'); | |
| const opttype=v('opttype'), mp=v('mp'), ts=v('ts'), ws=v('ws'); | |
| const cpuT=v('cpu_t'), nproc=v('nproc'), bswap=parseInt(v('bswap'))||0; | |
| const lr_sched=v('lr_sched'), lr_warmup=v('lr_warmup'), lr_cycles=v('lr_cycles'), lr_power=v('lr_power'); | |
| const dfs=getDFS(); | |
| let raw = ''; | |
| raw += `accelerate launch --num_cpu_threads_per_process ${cpuT} --mixed_precision ${mp}`; | |
| if (parseInt(nproc) > 1) raw += ` --num_processes ${nproc}`; | |
| raw += `${C}\n`; | |
| raw += ` src/musubi_tuner/zimage_train_network.py${C}\n`; | |
| raw += ` --dit ${dit}${C}\n`; | |
| raw += ` --vae ${vae}${C}\n`; | |
| raw += ` --text_encoder ${te}${C}\n`; | |
| raw += ` --dataset_config ${ds}${C}\n`; | |
| let attn = []; | |
| if (c('sdpa')) attn.push('--sdpa'); | |
| if (c('xf')) attn.push('--xformers'); | |
| attn.push(`--mixed_precision ${mp}`); | |
| raw += ` ${attn.join(' ')}${C}\n`; | |
| raw += ` --timestep_sampling ${ts} --weighting_scheme ${ws} --discrete_flow_shift ${dfs}${C}\n`; | |
| raw += ` --optimizer_type ${opttype} --learning_rate ${getLR()}`; | |
| if (c('gc')) raw += ' --gradient_checkpointing'; | |
| raw += `${C}\n`; | |
| if (lr_sched !== 'constant') { | |
| let sl = ` --lr_scheduler ${lr_sched}`; | |
| const nw = ['constant_with_warmup','cosine_with_restarts','cosine','linear','polynomial'].includes(lr_sched); | |
| if (nw && parseInt(lr_warmup) > 0) sl += ` --lr_warmup_steps ${lr_warmup}`; | |
| if (lr_sched === 'cosine_with_restarts') sl += ` --lr_scheduler_num_cycles ${lr_cycles}`; | |
| if (lr_sched === 'polynomial') sl += ` --lr_scheduler_power ${lr_power}`; | |
| raw += `${sl}${C}\n`; | |
| } | |
| raw += ` --max_data_loader_n_workers ${workers}`; | |
| if (c('pdlw')) raw += ' --persistent_data_loader_workers'; | |
| raw += `${C}\n`; | |
| raw += ` --network_module ${netmod}${C}\n`; | |
| raw += ` --network_dim ${ndim} --network_alpha ${nalpha}${C}\n`; | |
| raw += ` --max_train_steps ${steps} --save_every_n_steps ${saveS} --seed ${seed}`; | |
| if (c('fp8')) raw += `${C}\n --fp8_base`; | |
| if (c('fp8te')) raw += `${C}\n --fp8_llm`; | |
| if (bswap > 0) raw += `${C}\n --blocks_to_swap ${bswap}`; | |
| if (c('bswap_patch'))raw += `${C}\n --block_swap_optimizer_patch_params`; | |
| raw += `${C}\n --output_dir ${outdir} --output_name ${outname}`; | |
| document.getElementById('output').innerHTML = esc(raw) | |
| .replace(/(accelerate launch)/g, '<span class="c">$1</span>') | |
| .replace(/(--[\w_-]+)/g, '<span class="k">$1</span>') | |
| .replace(/(\^|\\)(?=\n)/g, '<span class="ct">$1</span>'); | |
| } | |
| function copyCmd() { | |
| const text = document.getElementById('output').innerText; | |
| navigator.clipboard.writeText(text).then(() => { | |
| const btn = document.getElementById('copybtn'); | |
| btn.textContent = '✓ Copied'; | |
| btn.classList.add('copied'); | |
| setTimeout(() => { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 2000); | |
| }); | |
| } | |
| document.querySelectorAll('input:not([type=file]), select').forEach(el => { | |
| el.addEventListener('change', gen); | |
| el.addEventListener('input', gen); | |
| }); | |
| onSchedChange(); | |
| gen(); | |
| </script> | |
| </body> | |
| </html> | |