-
Notifications
You must be signed in to change notification settings - Fork 666
Add model deployment best practice section in user guide #4399
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,216 @@ | ||
| /* ================================================================ | ||
| LMDeploy Interactive Configuration Generator – pill-bar layout | ||
| Matches the SGLang Cookbook segmented-control style | ||
| ================================================================ */ | ||
|
|
||
| /* Wrapper: full content width, no card background */ | ||
| .cg-wrapper { | ||
| width: 100%; | ||
| margin: 0; | ||
| padding: 0; | ||
| } | ||
|
|
||
| /* ── Dimension row ─────────────────────────────────────────────── */ | ||
| .cg-row { | ||
| margin-bottom: 1.25em; | ||
| } | ||
|
|
||
| .cg-label { | ||
| font-weight: 600; | ||
| font-size: 0.95em; | ||
| margin-bottom: 0.4em; | ||
| color: var(--pst-color-text-base, #24292e); | ||
| } | ||
|
|
||
| /* ── Pill bar (segmented control) ──────────────────────────────── */ | ||
| .cg-pill-bar { | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| width: 100%; | ||
| border: 1px solid var(--pst-color-border, #d1d5db); | ||
| border-radius: 6px; | ||
| overflow: hidden; | ||
| background: var(--pst-color-surface, #ffffff); | ||
| } | ||
|
|
||
| .cg-pill { | ||
| flex: 1 1 0; | ||
| min-width: 0; | ||
| padding: 0.55em 0.4em; | ||
| margin: 0; | ||
| border: none; | ||
| border-right: 1px solid var(--pst-color-border, #d1d5db); | ||
| background: transparent; | ||
| color: var(--pst-color-text-base, #24292e); | ||
| font-size: 0.88em; | ||
| font-weight: 500; | ||
| cursor: pointer; | ||
| text-align: center; | ||
| transition: background 0.15s ease, color 0.15s ease; | ||
| white-space: nowrap; | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| line-height: 1.4; | ||
| } | ||
|
|
||
| .cg-pill:last-child { | ||
| border-right: none; | ||
| } | ||
|
|
||
| .cg-pill:hover { | ||
| background: rgba(3, 102, 214, 0.08); | ||
| } | ||
|
|
||
| .cg-pill.active { | ||
| background: #0366d6; | ||
| color: #ffffff; | ||
| font-weight: 600; | ||
| } | ||
|
|
||
| .cg-pill.active:hover { | ||
| background: #0256c2; | ||
| } | ||
|
|
||
| /* ── Command output section ────────────────────────────────────── */ | ||
| .cg-command-section { | ||
| margin-top: 1.5em; | ||
| } | ||
|
|
||
| .cg-command-label { | ||
| font-weight: 600; | ||
| font-size: 0.95em; | ||
| margin-bottom: 0.4em; | ||
| color: var(--pst-color-text-base, #24292e); | ||
| } | ||
|
|
||
| .cg-command-box { | ||
| background: #1e1e1e; | ||
| border-radius: 6px; | ||
| padding: 1em 1em 1em 1.2em; | ||
| position: relative; | ||
| width: 100%; | ||
| box-sizing: border-box; | ||
| } | ||
|
|
||
| .cg-command-box pre { | ||
| margin: 0; | ||
| padding: 0; | ||
| padding-right: 4.5em; /* space for the copy button */ | ||
| background: transparent; | ||
| overflow-x: auto; | ||
| } | ||
|
|
||
| .cg-command-box code { | ||
| color: #d4d4d4; | ||
| font-family: 'SFMono-Regular', 'Consolas', 'Liberation Mono', 'Menlo', monospace; | ||
| font-size: 0.88em; | ||
| line-height: 1.6; | ||
| white-space: pre; | ||
| } | ||
|
|
||
| .cg-copy-btn { | ||
| position: absolute; | ||
| top: 0.6em; | ||
| right: 0.6em; | ||
| background: #0366d6; | ||
| color: #ffffff; | ||
| border: none; | ||
| border-radius: 4px; | ||
| padding: 0.35em 0.9em; | ||
| cursor: pointer; | ||
| font-size: 0.8em; | ||
| font-weight: 500; | ||
| transition: background 0.2s ease; | ||
| } | ||
|
|
||
| .cg-copy-btn:hover { | ||
| background: #0256c2; | ||
| } | ||
|
|
||
| .cg-copy-btn:active { | ||
| background: #014a9e; | ||
| } | ||
|
|
||
| /* ── Responsive: stack pills on narrow screens ─────────────────── */ | ||
| @media (max-width: 640px) { | ||
| .cg-pill-bar { | ||
| flex-direction: column; | ||
| } | ||
|
|
||
| .cg-pill { | ||
| border-right: none; | ||
| border-bottom: 1px solid var(--pst-color-border, #d1d5db); | ||
| } | ||
|
|
||
| .cg-pill:last-child { | ||
| border-bottom: none; | ||
| } | ||
| } | ||
|
|
||
| /* ── Dark-mode overrides (sphinx-book-theme data-theme) ────────── */ | ||
| html[data-theme="dark"] .cg-label, | ||
| html[data-theme="dark"] .cg-command-label { | ||
| color: #f0f6fc; | ||
| } | ||
|
|
||
| html[data-theme="dark"] .cg-pill-bar { | ||
| border-color: #30363d; | ||
| background: #161b22; | ||
| } | ||
|
|
||
| html[data-theme="dark"] .cg-pill { | ||
| color: #c9d1d9; | ||
| border-color: #30363d; | ||
| } | ||
|
|
||
| html[data-theme="dark"] .cg-pill:hover { | ||
| background: rgba(88, 166, 255, 0.12); | ||
| } | ||
|
|
||
| html[data-theme="dark"] .cg-pill.active { | ||
| background: #1f6feb; | ||
| color: #ffffff; | ||
| } | ||
|
|
||
| html[data-theme="dark"] .cg-command-box { | ||
| background: #0d1117; | ||
| } | ||
|
|
||
| html[data-theme="dark"] .cg-command-box code { | ||
| color: #c9d1d9; | ||
| } | ||
|
|
||
| /* Also handle prefers-color-scheme for themes without data-theme */ | ||
| @media (prefers-color-scheme: dark) { | ||
| .cg-label, | ||
| .cg-command-label { | ||
| color: #f0f6fc; | ||
| } | ||
|
|
||
| .cg-pill-bar { | ||
| border-color: #30363d; | ||
| background: #161b22; | ||
| } | ||
|
|
||
| .cg-pill { | ||
| color: #c9d1d9; | ||
| border-color: #30363d; | ||
| } | ||
|
|
||
| .cg-pill:hover { | ||
| background: rgba(88, 166, 255, 0.12); | ||
| } | ||
|
|
||
| .cg-pill.active { | ||
| background: #1f6feb; | ||
| color: #ffffff; | ||
| } | ||
|
|
||
| .cg-command-box { | ||
| background: #0d1117; | ||
| } | ||
|
|
||
| .cg-command-box code { | ||
| color: #c9d1d9; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| // LMDeploy Interactive Configuration Generator — Generic Engine | ||
| // Model-specific configurations are loaded from js/models/*.js via | ||
| // the window.LMDeployModelConfigs global registry. | ||
| (function() { | ||
| 'use strict'; | ||
|
|
||
| function initConfigGenerator() { | ||
| var container = document.getElementById('lmdeploy-config-generator'); | ||
| if (!container) return; | ||
|
|
||
| // ── Read model config from registry ────────────────────────── | ||
| var configKey = container.getAttribute('data-model-config') || 'qwen3'; | ||
| var configs = window.LMDeployModelConfigs || {}; | ||
| var config = configs[configKey]; | ||
| if (!config) { | ||
| container.textContent = 'Unknown model config: ' + configKey + | ||
| '. Available: ' + Object.keys(configs).join(', '); | ||
| return; | ||
| } | ||
|
|
||
| // ── TP estimation (generic) ───────────────────────────────── | ||
| function getRecommendedTP(sel) { | ||
| var mem = (config.gpuMem || {})[sel.hardware] || 80; | ||
| var need = (config.modelMem || {})[sel.model_size] || 16; | ||
| if (sel.quantization === 'awq' || sel.quantization === 'gptq') { | ||
| need *= 0.3; | ||
| } else if (sel.quantization === 'fp8') { | ||
| need *= 0.55; | ||
| } | ||
| var tp = 1; | ||
| while (tp * mem < need * 1.15 && tp < 8) { | ||
| tp *= 2; | ||
| } | ||
| return tp; | ||
|
Comment on lines
+30
to
+34
|
||
| } | ||
|
|
||
| // ── Generate command ──────────────────────────────────────── | ||
| function generateCommand() { | ||
| var sel = {}; | ||
| container.querySelectorAll('.cg-pill-bar').forEach(function(bar) { | ||
| var key = bar.getAttribute('data-key'); | ||
| var active = bar.querySelector('.cg-pill.active'); | ||
| if (active) sel[key] = active.getAttribute('data-value'); | ||
| }); | ||
|
|
||
| var modelPath = config.buildModelPath(sel); | ||
| var tp = getRecommendedTP(sel); | ||
| var parts = ['lmdeploy serve api_server ' + modelPath]; | ||
|
|
||
| if (tp > 1) parts.push('--tp ' + tp); | ||
|
|
||
| var extraFlags = config.buildExtraFlags ? config.buildExtraFlags(sel) : []; | ||
| parts = parts.concat(extraFlags); | ||
|
|
||
| if (parts.length <= 2) return parts.join(' '); | ||
| return parts[0] + ' \\\n' + | ||
| parts.slice(1).map(function(p) { return ' ' + p; }).join(' \\\n'); | ||
| } | ||
|
|
||
| // ── Update command display ────────────────────────────────── | ||
| function updateCommand() { | ||
| var el = container.querySelector('.cg-generated-command'); | ||
| if (el) el.textContent = generateCommand(); | ||
| } | ||
|
|
||
| // ── Render a single dimension row ─────────────────────────── | ||
| function renderDimension(dim) { | ||
| var row = document.createElement('div'); | ||
| row.className = 'cg-row'; | ||
|
|
||
| var label = document.createElement('div'); | ||
| label.className = 'cg-label'; | ||
| label.textContent = dim.label; | ||
| row.appendChild(label); | ||
|
|
||
| var bar = document.createElement('div'); | ||
| bar.className = 'cg-pill-bar'; | ||
| bar.setAttribute('data-key', dim.key); | ||
|
|
||
| dim.options.forEach(function(opt) { | ||
| var pill = document.createElement('button'); | ||
| pill.className = 'cg-pill'; | ||
| pill.setAttribute('data-value', opt.value); | ||
| pill.textContent = opt.label; | ||
| if (opt.value === dim.default) pill.classList.add('active'); | ||
|
|
||
| pill.addEventListener('click', function() { | ||
| bar.querySelectorAll('.cg-pill').forEach(function(p) { | ||
| p.classList.remove('active'); | ||
| }); | ||
| pill.classList.add('active'); | ||
| updateCommand(); | ||
| }); | ||
|
|
||
| bar.appendChild(pill); | ||
| }); | ||
|
|
||
| row.appendChild(bar); | ||
| return row; | ||
| } | ||
|
|
||
| // ── Build the full UI ─────────────────────────────────────── | ||
| var wrapper = document.createElement('div'); | ||
| wrapper.className = 'cg-wrapper'; | ||
|
|
||
| config.dimensions.forEach(function(dim) { | ||
| wrapper.appendChild(renderDimension(dim)); | ||
| }); | ||
|
|
||
| // Command output section | ||
| var cmdSection = document.createElement('div'); | ||
| cmdSection.className = 'cg-command-section'; | ||
|
|
||
| var cmdLabel = document.createElement('div'); | ||
| cmdLabel.className = 'cg-command-label'; | ||
| cmdLabel.textContent = 'Generated Command'; | ||
| cmdSection.appendChild(cmdLabel); | ||
|
|
||
| var cmdBox = document.createElement('div'); | ||
| cmdBox.className = 'cg-command-box'; | ||
|
|
||
| var pre = document.createElement('pre'); | ||
| var code = document.createElement('code'); | ||
| code.className = 'cg-generated-command'; | ||
| pre.appendChild(code); | ||
| cmdBox.appendChild(pre); | ||
|
|
||
| var copyBtn = document.createElement('button'); | ||
| copyBtn.className = 'cg-copy-btn'; | ||
| copyBtn.textContent = 'Copy'; | ||
| copyBtn.addEventListener('click', function() { | ||
| var text = code.textContent; | ||
| navigator.clipboard.writeText(text).then(function() { | ||
| copyBtn.textContent = 'Copied!'; | ||
| setTimeout(function() { copyBtn.textContent = 'Copy'; }, 2000); | ||
| }).catch(function() { | ||
| // Fallback for older browsers | ||
| var ta = document.createElement('textarea'); | ||
| ta.value = text; | ||
| ta.style.position = 'fixed'; | ||
| ta.style.left = '-9999px'; | ||
| document.body.appendChild(ta); | ||
| ta.select(); | ||
| document.execCommand('copy'); | ||
| document.body.removeChild(ta); | ||
| copyBtn.textContent = 'Copied!'; | ||
| setTimeout(function() { copyBtn.textContent = 'Copy'; }, 2000); | ||
| }); | ||
| }); | ||
| cmdBox.appendChild(copyBtn); | ||
|
|
||
| cmdSection.appendChild(cmdBox); | ||
| wrapper.appendChild(cmdSection); | ||
|
|
||
| container.appendChild(wrapper); | ||
| updateCommand(); | ||
| } | ||
|
|
||
| // Initialize when DOM is ready | ||
| if (document.readyState === 'loading') { | ||
| document.addEventListener('DOMContentLoaded', initConfigGenerator); | ||
| } else { | ||
| initConfigGenerator(); | ||
| } | ||
| })(); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
@media (prefers-color-scheme: dark)block can conflict with the theme's manual light/dark toggle. If a user has their OS set to dark mode but has manually selected the light theme insphinx_book_theme(which setsdata-theme="light"on<html>), the bare selectors inside this media query will still apply dark colors to the config generator, while the rest of the page stays light-themed. Consider scoping these rules to only apply whendata-themeis not explicitly set (e.g.,html:not([data-theme]) .cg-label) or removing this fallback block entirely sincesphinx_book_themealways setsdata-theme.