add model-free.
Browse filesSigned-off-by: lkk12014402 <kaokao.lv@intel.com>
- app.py +51 -6
- src/app_helpers/submissions.py +72 -3
- src/submission/check_validity.py +133 -0
- src/submission/model_analysis.py +295 -0
- src/submission/submit.py +9 -0
app.py
CHANGED
|
@@ -86,7 +86,7 @@ from src.leaderboard.read_auto_pipeline_results import (
|
|
| 86 |
)
|
| 87 |
from src.populate import get_auto_pipeline_results_df, get_evaluation_queue_df
|
| 88 |
from src.scripts.update_all_request_files import update_dynamic_files
|
| 89 |
-
from src.submission.check_validity import GPU_COUNT_CHOICES, GPU_HARDWARE_CHOICES, CONTAINER_DISK_SIZE_CHOICES
|
| 90 |
from src.tools.plots import (
|
| 91 |
compute_metrics_summary,
|
| 92 |
create_scheme_accuracy_chart,
|
|
@@ -119,7 +119,7 @@ from src.app_helpers.pipeline_table import (
|
|
| 119 |
)
|
| 120 |
from src.app_helpers.queues import filter_failed_quant_df, refresh_pipeline_leaderboard, refresh_queue_tables
|
| 121 |
from src.app_helpers.sidebar import render_sidebar_menu
|
| 122 |
-
from src.app_helpers.submissions import submit_model, submit_quant
|
| 123 |
from src.app_helpers.zip_stream import register_routes as register_zip_route
|
| 124 |
|
| 125 |
|
|
@@ -529,9 +529,9 @@ with demo:
|
|
| 529 |
elem_id="quant-scheme-select",
|
| 530 |
)
|
| 531 |
quant_method = gr.Dropdown(
|
| 532 |
-
choices=["RTN (Round-To-Nearest)", "Tuning (AutoRound, recommended)"],
|
| 533 |
label="Quantization Method",
|
| 534 |
-
info='Tuning uses AutoRound calibration (default iters=200) to significantly improve quantized model accuracy. <a href="https://github.com/intel/auto-round" target="_blank" rel="noopener">Details β</a>',
|
| 535 |
multiselect=False,
|
| 536 |
value="RTN (Round-To-Nearest)",
|
| 537 |
interactive=True,
|
|
@@ -567,6 +567,36 @@ with demo:
|
|
| 567 |
interactive=True,
|
| 568 |
elem_id="cuda-devices-quant",
|
| 569 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 570 |
with gr.Column(scale=1, min_width=160, elem_classes=["submit-col"]):
|
| 571 |
submit_button = gr.Button(
|
| 572 |
"Submit",
|
|
@@ -580,6 +610,11 @@ with demo:
|
|
| 580 |
elem_classes=["submit-star-nudge"],
|
| 581 |
)
|
| 582 |
submission_result = gr.Markdown(visible=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 583 |
dup_inline_quant = gr.Markdown(
|
| 584 |
visible=False,
|
| 585 |
elem_id="dup-inline-quant",
|
|
@@ -638,9 +673,12 @@ with demo:
|
|
| 638 |
gpu_count_quant,
|
| 639 |
container_disk_quant,
|
| 640 |
cuda_devices_quant,
|
|
|
|
|
|
|
|
|
|
| 641 |
],
|
| 642 |
[submission_result, model_name_textbox, submit_button, dup_inline_quant, license_warning_quant],
|
| 643 |
-
js="""(signal, model, revision, priv, scheme, method, size, hw, cnt, disk, cuda) => {
|
| 644 |
// Only clear pending_submit when NOT redirecting to OAuth
|
| 645 |
if (signal !== 'TRIGGER') sessionStorage.removeItem('pending_submit');
|
| 646 |
// Fallback: read model from DOM if Gradio internal state is empty
|
|
@@ -649,10 +687,17 @@ with demo:
|
|
| 649 |
var input = document.querySelector('#model-input-quant input, #model-input-quant textarea');
|
| 650 |
if (input && input.value.trim()) model = input.value.trim();
|
| 651 |
}
|
| 652 |
-
return [signal, model, revision, priv, scheme, method, size, hw, cnt, disk, cuda];
|
| 653 |
}""",
|
| 654 |
) # revision_name_textbox is hidden, always "main"
|
| 655 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 656 |
with gr.Column():
|
| 657 |
with gr.Row(elem_classes=["filter-row"]):
|
| 658 |
my_submissions_quant_cb = gr.Checkbox(
|
|
|
|
| 86 |
)
|
| 87 |
from src.populate import get_auto_pipeline_results_df, get_evaluation_queue_df
|
| 88 |
from src.scripts.update_all_request_files import update_dynamic_files
|
| 89 |
+
from src.submission.check_validity import GPU_COUNT_CHOICES, GPU_HARDWARE_CHOICES, CONTAINER_DISK_SIZE_CHOICES, EXPORT_FORMAT_CHOICES
|
| 90 |
from src.tools.plots import (
|
| 91 |
compute_metrics_summary,
|
| 92 |
create_scheme_accuracy_chart,
|
|
|
|
| 119 |
)
|
| 120 |
from src.app_helpers.queues import filter_failed_quant_df, refresh_pipeline_leaderboard, refresh_queue_tables
|
| 121 |
from src.app_helpers.sidebar import render_sidebar_menu
|
| 122 |
+
from src.app_helpers.submissions import submit_model, submit_quant, analyze_model
|
| 123 |
from src.app_helpers.zip_stream import register_routes as register_zip_route
|
| 124 |
|
| 125 |
|
|
|
|
| 529 |
elem_id="quant-scheme-select",
|
| 530 |
)
|
| 531 |
quant_method = gr.Dropdown(
|
| 532 |
+
choices=["RTN (Round-To-Nearest)", "Tuning (AutoRound, recommended)", "Model-Free (RTN, no calibration)"],
|
| 533 |
label="Quantization Method",
|
| 534 |
+
info='Tuning uses AutoRound calibration (default iters=200) to significantly improve quantized model accuracy. Model-Free is a fast weight-only RTN path for weight-only schemes (W4A16/MXFP4/MXFP8), restricted to whitelisted users. <a href="https://github.com/intel/auto-round" target="_blank" rel="noopener">Details β</a>',
|
| 535 |
multiselect=False,
|
| 536 |
value="RTN (Round-To-Nearest)",
|
| 537 |
interactive=True,
|
|
|
|
| 567 |
interactive=True,
|
| 568 |
elem_id="cuda-devices-quant",
|
| 569 |
)
|
| 570 |
+
export_format_quant = gr.Dropdown(
|
| 571 |
+
choices=EXPORT_FORMAT_CHOICES,
|
| 572 |
+
label="Export Format",
|
| 573 |
+
value=EXPORT_FORMAT_CHOICES[0],
|
| 574 |
+
info="Quantized model save format. Auto = auto_round.",
|
| 575 |
+
interactive=True,
|
| 576 |
+
elem_id="export-format-quant",
|
| 577 |
+
)
|
| 578 |
+
ignore_layers_quant = gr.Textbox(
|
| 579 |
+
label="Ignore Layers (advanced)",
|
| 580 |
+
placeholder="e.g. lm_head,block_sparse_moe.gate,self_attn",
|
| 581 |
+
info="Comma-separated module substrings to skip. Empty = recommended defaults. Overrides defaults when set.",
|
| 582 |
+
value="",
|
| 583 |
+
interactive=True,
|
| 584 |
+
elem_id="ignore-layers-quant",
|
| 585 |
+
)
|
| 586 |
+
layer_config_quant = gr.Textbox(
|
| 587 |
+
label="Layer Config β mixed precision (advanced)",
|
| 588 |
+
placeholder="e.g. {block_sparse_moe.experts:{bits:4,data_type:mx_fp}}",
|
| 589 |
+
info="auto-round layer_config JSON for per-module precision. Empty = uniform scheme.",
|
| 590 |
+
value="",
|
| 591 |
+
interactive=True,
|
| 592 |
+
elem_id="layer-config-quant",
|
| 593 |
+
)
|
| 594 |
+
analyze_button_quant = gr.Button(
|
| 595 |
+
"π Analyze Structure",
|
| 596 |
+
variant="secondary",
|
| 597 |
+
interactive=True,
|
| 598 |
+
elem_id="analyze-button-quant",
|
| 599 |
+
)
|
| 600 |
with gr.Column(scale=1, min_width=160, elem_classes=["submit-col"]):
|
| 601 |
submit_button = gr.Button(
|
| 602 |
"Submit",
|
|
|
|
| 610 |
elem_classes=["submit-star-nudge"],
|
| 611 |
)
|
| 612 |
submission_result = gr.Markdown(visible=False)
|
| 613 |
+
analysis_result_quant = gr.Markdown(
|
| 614 |
+
visible=False,
|
| 615 |
+
elem_id="analysis-result-quant",
|
| 616 |
+
elem_classes=["analysis-panel"],
|
| 617 |
+
)
|
| 618 |
dup_inline_quant = gr.Markdown(
|
| 619 |
visible=False,
|
| 620 |
elem_id="dup-inline-quant",
|
|
|
|
| 673 |
gpu_count_quant,
|
| 674 |
container_disk_quant,
|
| 675 |
cuda_devices_quant,
|
| 676 |
+
export_format_quant,
|
| 677 |
+
ignore_layers_quant,
|
| 678 |
+
layer_config_quant,
|
| 679 |
],
|
| 680 |
[submission_result, model_name_textbox, submit_button, dup_inline_quant, license_warning_quant],
|
| 681 |
+
js="""(signal, model, revision, priv, scheme, method, size, hw, cnt, disk, cuda, expfmt, ignore, layercfg) => {
|
| 682 |
// Only clear pending_submit when NOT redirecting to OAuth
|
| 683 |
if (signal !== 'TRIGGER') sessionStorage.removeItem('pending_submit');
|
| 684 |
// Fallback: read model from DOM if Gradio internal state is empty
|
|
|
|
| 687 |
var input = document.querySelector('#model-input-quant input, #model-input-quant textarea');
|
| 688 |
if (input && input.value.trim()) model = input.value.trim();
|
| 689 |
}
|
| 690 |
+
return [signal, model, revision, priv, scheme, method, size, hw, cnt, disk, cuda, expfmt, ignore, layercfg];
|
| 691 |
}""",
|
| 692 |
) # revision_name_textbox is hidden, always "main"
|
| 693 |
|
| 694 |
+
# Analyze model structure β show report + pre-fill ignore/layer_config suggestions.
|
| 695 |
+
analyze_button_quant.click(
|
| 696 |
+
fn=analyze_model,
|
| 697 |
+
inputs=[model_name_textbox, ignore_layers_quant, layer_config_quant],
|
| 698 |
+
outputs=[analysis_result_quant, ignore_layers_quant, layer_config_quant],
|
| 699 |
+
)
|
| 700 |
+
|
| 701 |
with gr.Column():
|
| 702 |
with gr.Row(elem_classes=["filter-row"]):
|
| 703 |
my_submissions_quant_cb = gr.Checkbox(
|
src/app_helpers/submissions.py
CHANGED
|
@@ -15,7 +15,12 @@ from src.app_helpers.auth import check_intel_org, is_whitelisted
|
|
| 15 |
from src.display.formatting import styled_error
|
| 16 |
from src.envs import SIZE_WHITELIST, WHITELIST
|
| 17 |
from src.submission.submit import add_new_eval, add_new_quant
|
| 18 |
-
from src.submission.check_validity import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
logger = logging.getLogger(__name__)
|
| 21 |
|
|
@@ -145,6 +150,37 @@ def _resolve_overrides(oauth_token, hardware_choice, gpu_count_choice, container
|
|
| 145 |
return hw_override, count_override, disk_override, cuda_devices
|
| 146 |
|
| 147 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
def _empty_result():
|
| 149 |
"""Five outputs that signal "no progress" to the streaming UI."""
|
| 150 |
return "", gr.update(), gr.update(interactive=True), gr.update(), gr.update()
|
|
@@ -252,6 +288,9 @@ def submit_quant(
|
|
| 252 |
signal, model, revision, private, quant_scheme, quant_method_choice, model_size_input,
|
| 253 |
hardware_choice, gpu_count_choice, container_disk_size_choice,
|
| 254 |
cuda_visible_devices_choice=None,
|
|
|
|
|
|
|
|
|
|
| 255 |
oauth_token: gr.OAuthToken | None = None,
|
| 256 |
):
|
| 257 |
"""Wrapper that parses the optional model-size field and calls
|
|
@@ -272,8 +311,35 @@ def submit_quant(
|
|
| 272 |
oauth_token, hardware_choice, gpu_count_choice, container_disk_size_choice,
|
| 273 |
cuda_visible_devices_choice)
|
| 274 |
|
| 275 |
-
#
|
| 276 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
method_override = "TUNING"
|
| 278 |
else:
|
| 279 |
method_override = "RTN"
|
|
@@ -329,6 +395,9 @@ def submit_quant(
|
|
| 329 |
gpu_count_override=count_override,
|
| 330 |
container_disk_size=disk_override,
|
| 331 |
cuda_visible_devices=cuda_devices,
|
|
|
|
|
|
|
|
|
|
| 332 |
submitted_by=username,
|
| 333 |
submitted_orgs=orgs,
|
| 334 |
user_token=oauth_token.token if oauth_token else None,
|
|
|
|
| 15 |
from src.display.formatting import styled_error
|
| 16 |
from src.envs import SIZE_WHITELIST, WHITELIST
|
| 17 |
from src.submission.submit import add_new_eval, add_new_quant
|
| 18 |
+
from src.submission.check_validity import (
|
| 19 |
+
is_b200_hardware, validate_cuda_visible_devices, resolve_export_format,
|
| 20 |
+
is_model_free_supported_scheme, MODEL_FREE_METHOD,
|
| 21 |
+
validate_ignore_layers, validate_layer_config,
|
| 22 |
+
)
|
| 23 |
+
from src.submission.model_analysis import analyze_model_structure, render_analysis_markdown
|
| 24 |
|
| 25 |
logger = logging.getLogger(__name__)
|
| 26 |
|
|
|
|
| 150 |
return hw_override, count_override, disk_override, cuda_devices
|
| 151 |
|
| 152 |
|
| 153 |
+
def analyze_model(model, ignore_current=None, layercfg_current=None,
|
| 154 |
+
oauth_token: gr.OAuthToken | None = None):
|
| 155 |
+
"""Button handler: analyze a model's structure and show it in the UI.
|
| 156 |
+
|
| 157 |
+
Returns a 3-tuple of Gradio updates for
|
| 158 |
+
``[analysis_panel, ignore_layers_textbox, layer_config_textbox]``.
|
| 159 |
+
The recommended ignore/layer_config are pre-filled ONLY when the target
|
| 160 |
+
textbox is currently empty, so a user's own edits are never clobbered.
|
| 161 |
+
Read-only and safe; gated in the UI to whitelisted users.
|
| 162 |
+
"""
|
| 163 |
+
if not model or not model.strip():
|
| 164 |
+
return gr.update(value="Please enter a model name first.", visible=True), gr.update(), gr.update()
|
| 165 |
+
|
| 166 |
+
token = oauth_token.token if oauth_token else None
|
| 167 |
+
try:
|
| 168 |
+
res = analyze_model_structure(model.strip(), token=token)
|
| 169 |
+
md = render_analysis_markdown(res)
|
| 170 |
+
except Exception as e: # never let the button crash the app
|
| 171 |
+
logger.warning("[analyze_model] failed for %s: %s", model, e)
|
| 172 |
+
return gr.update(value=f"Analysis error: {e}", visible=True), gr.update(), gr.update()
|
| 173 |
+
|
| 174 |
+
ign_upd = gr.update()
|
| 175 |
+
lc_upd = gr.update()
|
| 176 |
+
if res.ok:
|
| 177 |
+
if (not ignore_current or not ignore_current.strip()) and res.recommended_ignore_layers:
|
| 178 |
+
ign_upd = gr.update(value=res.recommended_ignore_layers)
|
| 179 |
+
if (not layercfg_current or not layercfg_current.strip()) and res.recommended_layer_config:
|
| 180 |
+
lc_upd = gr.update(value=res.recommended_layer_config)
|
| 181 |
+
return gr.update(value=md, visible=True), ign_upd, lc_upd
|
| 182 |
+
|
| 183 |
+
|
| 184 |
def _empty_result():
|
| 185 |
"""Five outputs that signal "no progress" to the streaming UI."""
|
| 186 |
return "", gr.update(), gr.update(interactive=True), gr.update(), gr.update()
|
|
|
|
| 288 |
signal, model, revision, private, quant_scheme, quant_method_choice, model_size_input,
|
| 289 |
hardware_choice, gpu_count_choice, container_disk_size_choice,
|
| 290 |
cuda_visible_devices_choice=None,
|
| 291 |
+
export_format_choice=None,
|
| 292 |
+
ignore_layers_choice=None,
|
| 293 |
+
layer_config_choice=None,
|
| 294 |
oauth_token: gr.OAuthToken | None = None,
|
| 295 |
):
|
| 296 |
"""Wrapper that parses the optional model-size field and calls
|
|
|
|
| 311 |
oauth_token, hardware_choice, gpu_count_choice, container_disk_size_choice,
|
| 312 |
cuda_visible_devices_choice)
|
| 313 |
|
| 314 |
+
# Advanced quant-only options are whitelist-gated. Non-whitelist users'
|
| 315 |
+
# selections are ignored (fields omitted β pipeline defaults).
|
| 316 |
+
export_format = None
|
| 317 |
+
ignore_layers = None
|
| 318 |
+
layer_config = None
|
| 319 |
+
if is_whitelisted(oauth_token):
|
| 320 |
+
export_format = resolve_export_format(export_format_choice)
|
| 321 |
+
ignore_layers, ign_err = validate_ignore_layers(ignore_layers_choice)
|
| 322 |
+
if ign_err:
|
| 323 |
+
raise gr.Error(f"Invalid Ignore Layers: {ign_err}")
|
| 324 |
+
layer_config, lc_err = validate_layer_config(layer_config_choice)
|
| 325 |
+
if lc_err:
|
| 326 |
+
raise gr.Error(f"Invalid Layer Config: {lc_err}")
|
| 327 |
+
|
| 328 |
+
# Parse method choice β "RTN" | "TUNING" | "MODEL_FREE"
|
| 329 |
+
if quant_method_choice and "Model-Free" in quant_method_choice:
|
| 330 |
+
# Model-Free is restricted to whitelisted users and weight-only schemes.
|
| 331 |
+
if not is_whitelisted(oauth_token):
|
| 332 |
+
raise gr.Error(
|
| 333 |
+
"Model-Free quantization is restricted to whitelisted users. "
|
| 334 |
+
"Please choose RTN or Tuning."
|
| 335 |
+
)
|
| 336 |
+
if not is_model_free_supported_scheme(quant_scheme):
|
| 337 |
+
raise gr.Error(
|
| 338 |
+
f"Model-Free does not support scheme '{quant_scheme}'. "
|
| 339 |
+
"Supported: INT4 (W4A16), MXFP4, MXFP8 (weight-only only; NVFP4/FP8 not supported)."
|
| 340 |
+
)
|
| 341 |
+
method_override = MODEL_FREE_METHOD
|
| 342 |
+
elif quant_method_choice and "Tuning" in quant_method_choice:
|
| 343 |
method_override = "TUNING"
|
| 344 |
else:
|
| 345 |
method_override = "RTN"
|
|
|
|
| 395 |
gpu_count_override=count_override,
|
| 396 |
container_disk_size=disk_override,
|
| 397 |
cuda_visible_devices=cuda_devices,
|
| 398 |
+
export_format=export_format,
|
| 399 |
+
ignore_layers=ignore_layers,
|
| 400 |
+
layer_config=layer_config,
|
| 401 |
submitted_by=username,
|
| 402 |
submitted_orgs=orgs,
|
| 403 |
user_token=oauth_token.token if oauth_token else None,
|
src/submission/check_validity.py
CHANGED
|
@@ -723,6 +723,139 @@ GPU_COUNT_CHOICES: list[str] = ["Auto", "1", "2", "4", "8"]
|
|
| 723 |
# Container disk size choices for whitelisted users (GB).
|
| 724 |
CONTAINER_DISK_SIZE_CHOICES: list[str] = ["Auto", "200", "400", "600", "800", "1000", "1500", "2000"]
|
| 725 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 726 |
# Reverse lookup: display name β short name
|
| 727 |
_GPU_DISPLAY_TO_SHORT: dict[str, str] = {v: k for k, v in GPU_DISPLAY_NAMES.items()}
|
| 728 |
|
|
|
|
| 723 |
# Container disk size choices for whitelisted users (GB).
|
| 724 |
CONTAINER_DISK_SIZE_CHOICES: list[str] = ["Auto", "200", "400", "600", "800", "1000", "1500", "2000"]
|
| 725 |
|
| 726 |
+
# Export format choices for whitelisted users (auto-round save_quantized format).
|
| 727 |
+
# "Auto" defers to the pipeline default (auto_round).
|
| 728 |
+
EXPORT_FORMAT_AUTO = "Auto (auto_round)"
|
| 729 |
+
EXPORT_FORMAT_CHOICES: list[str] = [EXPORT_FORMAT_AUTO, "auto_round", "llm_compressor"]
|
| 730 |
+
|
| 731 |
+
|
| 732 |
+
def resolve_export_format(choice: str | None) -> str | None:
|
| 733 |
+
"""Map an Export Format UI choice to an entry value.
|
| 734 |
+
|
| 735 |
+
Returns ``None`` for the "Auto" default (so the entry omits the field and
|
| 736 |
+
the pipeline uses its built-in default), else the concrete format string.
|
| 737 |
+
"""
|
| 738 |
+
if not choice or choice.startswith("Auto"):
|
| 739 |
+
return None
|
| 740 |
+
if choice in ("auto_round", "llm_compressor"):
|
| 741 |
+
return choice
|
| 742 |
+
return None
|
| 743 |
+
|
| 744 |
+
|
| 745 |
+
# ββ Model-Free quantization method βββββββββββββββββββββββββββββββββββββββ
|
| 746 |
+
# auto-round model-free is weight-only RTN and only supports these schemes
|
| 747 |
+
# (see auto_round/compressors/model_free.py). NVFP4/FP8/GGUF are NOT supported.
|
| 748 |
+
MODEL_FREE_METHOD = "MODEL_FREE"
|
| 749 |
+
_MODEL_FREE_SUPPORTED_SCHEMES = {"W2A16", "W4A16", "W8A16", "MXFP4", "MXFP8"}
|
| 750 |
+
|
| 751 |
+
# UI scheme label β normalized scheme name (mirrors auto.sh's scheme_map).
|
| 752 |
+
_UI_SCHEME_TO_NORMALIZED = {
|
| 753 |
+
"INT4 (W4A16)": "W4A16",
|
| 754 |
+
"INT8 (W8A16)": "W8A16",
|
| 755 |
+
"INT4 (W4A8)": "W4A8",
|
| 756 |
+
"MXFP4": "MXFP4",
|
| 757 |
+
"MXFP8": "MXFP8",
|
| 758 |
+
"NVFP4": "NVFP4",
|
| 759 |
+
}
|
| 760 |
+
|
| 761 |
+
|
| 762 |
+
def normalize_scheme_name(scheme_ui: str | None) -> str:
|
| 763 |
+
"""Normalize a UI scheme label (e.g. 'INT4 (W4A16)') to 'W4A16'."""
|
| 764 |
+
if not scheme_ui:
|
| 765 |
+
return ""
|
| 766 |
+
s = scheme_ui.strip()
|
| 767 |
+
return _UI_SCHEME_TO_NORMALIZED.get(s, s)
|
| 768 |
+
|
| 769 |
+
|
| 770 |
+
def is_model_free_supported_scheme(scheme_ui: str | None) -> bool:
|
| 771 |
+
"""Whether *scheme_ui* is a weight-only scheme supported by model-free."""
|
| 772 |
+
return normalize_scheme_name(scheme_ui) in _MODEL_FREE_SUPPORTED_SCHEMES
|
| 773 |
+
|
| 774 |
+
|
| 775 |
+
# ββ Custom ignore layers & mixed-precision layer_config ββββββββββββββββββ
|
| 776 |
+
# Ignore-layer tokens are module-name substrings, e.g.
|
| 777 |
+
# "lm_head,block_sparse_moe.gate,self_attn.index_q_proj". Allow only characters
|
| 778 |
+
# that appear in module names + wildcard, and reject anything shell-hostile
|
| 779 |
+
# (these values are forwarded into the quantize command line downstream).
|
| 780 |
+
_IGNORE_TOKEN_RE = re.compile(r"^[A-Za-z0-9_.*\-]+$")
|
| 781 |
+
# layer_config is auto-round's relaxed JSON, e.g.
|
| 782 |
+
# "{block_sparse_moe.experts:{bits:4,data_type:mx_fp}}". We do a syntactic
|
| 783 |
+
# sanity check here; auto-round does the authoritative parse at runtime.
|
| 784 |
+
_LAYER_CONFIG_RE = re.compile(r"^[A-Za-z0-9_.,:{}\s\"'\-]+$")
|
| 785 |
+
_MAX_IGNORE_TOKENS = 64
|
| 786 |
+
_MAX_LAYER_CONFIG_LEN = 2000
|
| 787 |
+
|
| 788 |
+
|
| 789 |
+
def validate_ignore_layers(raw: str | None) -> tuple[str | None, str | None]:
|
| 790 |
+
"""Validate/normalize a comma-separated ignore-layers list.
|
| 791 |
+
|
| 792 |
+
Returns ``(normalized, error)`` with exactly one non-None.
|
| 793 |
+
- empty/None β ``(None, None)`` (use pipeline defaults)
|
| 794 |
+
- each token must match ``[A-Za-z0-9_.*-]+``; duplicates are dropped
|
| 795 |
+
"""
|
| 796 |
+
if raw is None:
|
| 797 |
+
return None, None
|
| 798 |
+
text = raw.strip()
|
| 799 |
+
if text == "":
|
| 800 |
+
return None, None
|
| 801 |
+
tokens = [t.strip() for t in text.split(",")]
|
| 802 |
+
out: list[str] = []
|
| 803 |
+
seen: set[str] = set()
|
| 804 |
+
for tok in tokens:
|
| 805 |
+
if tok == "":
|
| 806 |
+
continue # tolerate stray/trailing commas
|
| 807 |
+
if not _IGNORE_TOKEN_RE.match(tok):
|
| 808 |
+
return None, (
|
| 809 |
+
f"Invalid ignore-layer token '{tok}': only letters, digits, '_', '.', "
|
| 810 |
+
"'*' and '-' are allowed."
|
| 811 |
+
)
|
| 812 |
+
if tok not in seen:
|
| 813 |
+
seen.add(tok)
|
| 814 |
+
out.append(tok)
|
| 815 |
+
if not out:
|
| 816 |
+
return None, None
|
| 817 |
+
if len(out) > _MAX_IGNORE_TOKENS:
|
| 818 |
+
return None, f"Too many ignore-layer tokens (max {_MAX_IGNORE_TOKENS})."
|
| 819 |
+
return ",".join(out), None
|
| 820 |
+
|
| 821 |
+
|
| 822 |
+
def validate_layer_config(raw: str | None) -> tuple[str | None, str | None]:
|
| 823 |
+
"""Syntactically validate a mixed-precision ``layer_config`` string.
|
| 824 |
+
|
| 825 |
+
Accepts auto-round's relaxed JSON, e.g.
|
| 826 |
+
``{block_sparse_moe.experts:{bits:4,data_type:mx_fp}}``. This is a sanity
|
| 827 |
+
check only (balanced braces + safe charset); auto-round performs the real
|
| 828 |
+
parse via ``parse_layer_config_arg`` at quantization time.
|
| 829 |
+
|
| 830 |
+
Returns ``(normalized, error)`` with exactly one non-None.
|
| 831 |
+
"""
|
| 832 |
+
if raw is None:
|
| 833 |
+
return None, None
|
| 834 |
+
text = raw.strip()
|
| 835 |
+
if text == "":
|
| 836 |
+
return None, None
|
| 837 |
+
if len(text) > _MAX_LAYER_CONFIG_LEN:
|
| 838 |
+
return None, f"layer_config too long (max {_MAX_LAYER_CONFIG_LEN} chars)."
|
| 839 |
+
if not (text.startswith("{") and text.endswith("}")):
|
| 840 |
+
return None, "layer_config must be a JSON-like object wrapped in { }."
|
| 841 |
+
if not _LAYER_CONFIG_RE.match(text):
|
| 842 |
+
return None, (
|
| 843 |
+
"layer_config contains disallowed characters. Use only module names, "
|
| 844 |
+
"digits, and { } : , . _ - (e.g. {experts:{bits:4,data_type:mx_fp}})."
|
| 845 |
+
)
|
| 846 |
+
# Balanced-brace check
|
| 847 |
+
depth = 0
|
| 848 |
+
for ch in text:
|
| 849 |
+
if ch == "{":
|
| 850 |
+
depth += 1
|
| 851 |
+
elif ch == "}":
|
| 852 |
+
depth -= 1
|
| 853 |
+
if depth < 0:
|
| 854 |
+
return None, "layer_config has unbalanced braces."
|
| 855 |
+
if depth != 0:
|
| 856 |
+
return None, "layer_config has unbalanced braces."
|
| 857 |
+
return text, None
|
| 858 |
+
|
| 859 |
# Reverse lookup: display name β short name
|
| 860 |
_GPU_DISPLAY_TO_SHORT: dict[str, str] = {v: k for k, v in GPU_DISPLAY_NAMES.items()}
|
| 861 |
|
src/submission/model_analysis.py
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright and license: follows the repository's existing convention.
|
| 2 |
+
"""Download-free model structure & parameter analysis for quantization planning.
|
| 3 |
+
|
| 4 |
+
Given a HuggingFace model id, this inspects the model's ``config.json`` and the
|
| 5 |
+
**safetensors headers only** (no weight download) to produce:
|
| 6 |
+
|
| 7 |
+
* a per-category parameter distribution (MoE experts, shared experts, attention,
|
| 8 |
+
router/gate, dense MLP, lm_head, embeddings, vision, norms, β¦), matching the
|
| 9 |
+
format of the mixed-precision reference docs, and
|
| 10 |
+
* recommended ``ignore_layers`` / mixed-precision ``layer_config`` presets a user
|
| 11 |
+
can drop straight into the advanced submission fields.
|
| 12 |
+
|
| 13 |
+
The heavy lifting uses ``huggingface_hub.get_safetensors_metadata`` which reads
|
| 14 |
+
only the safetensors headers (tensor name + shape + dtype), so even trillion-param
|
| 15 |
+
models are analyzed without downloading weights.
|
| 16 |
+
"""
|
| 17 |
+
from __future__ import annotations
|
| 18 |
+
|
| 19 |
+
import logging
|
| 20 |
+
import re
|
| 21 |
+
from dataclasses import dataclass, field
|
| 22 |
+
|
| 23 |
+
from huggingface_hub import get_safetensors_metadata
|
| 24 |
+
from transformers import AutoConfig
|
| 25 |
+
|
| 26 |
+
logger = logging.getLogger(__name__)
|
| 27 |
+
|
| 28 |
+
# Collapse per-layer indices so ``...layers.12.self_attn.q_proj.weight`` and
|
| 29 |
+
# ``...layers.0.self_attn.q_proj.weight`` bucket together.
|
| 30 |
+
_LAYER_IDX_RE = re.compile(r"\.\d+\.")
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@dataclass
|
| 34 |
+
class CategoryStat:
|
| 35 |
+
name: str
|
| 36 |
+
params: int = 0
|
| 37 |
+
tensors_2d: int = 0
|
| 38 |
+
tensors_total: int = 0
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
@dataclass
|
| 42 |
+
class ModelAnalysis:
|
| 43 |
+
model_id: str
|
| 44 |
+
ok: bool = True
|
| 45 |
+
error: str | None = None
|
| 46 |
+
architectures: list[str] = field(default_factory=list)
|
| 47 |
+
model_type: str = ""
|
| 48 |
+
hidden_size: int | None = None
|
| 49 |
+
num_layers: int | None = None
|
| 50 |
+
num_experts: int | None = None
|
| 51 |
+
vocab_size: int | None = None
|
| 52 |
+
total_params: int = 0
|
| 53 |
+
is_moe: bool = False
|
| 54 |
+
has_shared_experts: bool = False
|
| 55 |
+
has_attn_indexer: bool = False
|
| 56 |
+
has_vision: bool = False
|
| 57 |
+
approximate: bool = False
|
| 58 |
+
categories: list[CategoryStat] = field(default_factory=list)
|
| 59 |
+
recommended_ignore_layers: str = ""
|
| 60 |
+
recommended_layer_config: str = ""
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
# ββ Categorization ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 64 |
+
# Order matters: the first matching rule wins. Each rule is (category, predicate).
|
| 65 |
+
def _categorize(norm_name: str) -> str:
|
| 66 |
+
n = norm_name
|
| 67 |
+
# Routers / gates first (must beat gate_proj which is a normal MLP proj).
|
| 68 |
+
if (".gate." in n or n.endswith(".gate") or n.endswith(".gate.weight")
|
| 69 |
+
or ".router" in n or n.endswith(".router")) and "gate_proj" not in n and "gate_up_proj" not in n:
|
| 70 |
+
return "router_gate"
|
| 71 |
+
if "shared_expert" in n:
|
| 72 |
+
return "shared_experts"
|
| 73 |
+
if ".experts." in n or n.endswith(".experts") or ".block_sparse_moe.experts" in n:
|
| 74 |
+
return "moe_experts"
|
| 75 |
+
if "vision" in n or "visual" in n or "vit" in n:
|
| 76 |
+
return "vision"
|
| 77 |
+
if "lm_head" in n:
|
| 78 |
+
return "lm_head"
|
| 79 |
+
if "embed" in n or "wte" in n or "word_embeddings" in n:
|
| 80 |
+
return "embeddings"
|
| 81 |
+
if "self_attn" in n or ".attn." in n or "attention" in n:
|
| 82 |
+
if "index" in n or "indexer" in n:
|
| 83 |
+
return "attn_indexer"
|
| 84 |
+
if "norm" in n:
|
| 85 |
+
return "norm"
|
| 86 |
+
return "self_attn"
|
| 87 |
+
if "mtp" in n:
|
| 88 |
+
return "mtp"
|
| 89 |
+
if "projector" in n or "patch_merge" in n or "multi_modal" in n:
|
| 90 |
+
return "projector"
|
| 91 |
+
if "mlp" in n or "feed_forward" in n or "ffn" in n:
|
| 92 |
+
return "dense_mlp"
|
| 93 |
+
if "norm" in n or "layernorm" in n or "ln_" in n:
|
| 94 |
+
return "norm"
|
| 95 |
+
return "other"
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
_CATEGORY_ORDER = [
|
| 99 |
+
"moe_experts", "shared_experts", "dense_mlp", "self_attn", "attn_indexer",
|
| 100 |
+
"router_gate", "mtp", "vision", "projector", "lm_head", "embeddings",
|
| 101 |
+
"norm", "other",
|
| 102 |
+
]
|
| 103 |
+
|
| 104 |
+
_CATEGORY_LABELS = {
|
| 105 |
+
"moe_experts": "MoE routed experts",
|
| 106 |
+
"shared_experts": "MoE shared experts",
|
| 107 |
+
"dense_mlp": "Dense MLP",
|
| 108 |
+
"self_attn": "Attention (q/k/v/o)",
|
| 109 |
+
"attn_indexer": "Attention indexer (sparse)",
|
| 110 |
+
"router_gate": "Router / gate",
|
| 111 |
+
"mtp": "MTP module",
|
| 112 |
+
"vision": "Vision tower",
|
| 113 |
+
"projector": "Projector / merger",
|
| 114 |
+
"lm_head": "lm_head",
|
| 115 |
+
"embeddings": "Embeddings",
|
| 116 |
+
"norm": "Norms (1D, auto-skipped)",
|
| 117 |
+
"other": "Other",
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def _cfg_get(cfg, *attrs, default=None):
|
| 122 |
+
"""Read the first present attribute from a config or its nested text_config."""
|
| 123 |
+
sources = [cfg]
|
| 124 |
+
if hasattr(cfg, "text_config") and cfg.text_config is not None:
|
| 125 |
+
sources.append(cfg.text_config)
|
| 126 |
+
for src in sources:
|
| 127 |
+
for a in attrs:
|
| 128 |
+
v = getattr(src, a, None)
|
| 129 |
+
if v is not None:
|
| 130 |
+
return v
|
| 131 |
+
return default
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def analyze_model_structure(model_id: str, revision: str = "main", token: str | None = None) -> ModelAnalysis:
|
| 135 |
+
"""Analyze *model_id* without downloading weights. Never raises."""
|
| 136 |
+
res = ModelAnalysis(model_id=model_id)
|
| 137 |
+
|
| 138 |
+
# 1. Config (best-effort; some fields inform MoE detection & the report).
|
| 139 |
+
try:
|
| 140 |
+
cfg = AutoConfig.from_pretrained(model_id, revision=revision, token=token, trust_remote_code=True)
|
| 141 |
+
arch = getattr(cfg, "architectures", None) or []
|
| 142 |
+
res.architectures = list(arch)
|
| 143 |
+
res.model_type = getattr(cfg, "model_type", "") or ""
|
| 144 |
+
res.hidden_size = _cfg_get(cfg, "hidden_size", "n_embd", "d_model")
|
| 145 |
+
res.num_layers = _cfg_get(cfg, "num_hidden_layers", "n_layer", "num_layers")
|
| 146 |
+
res.num_experts = _cfg_get(cfg, "num_experts", "num_local_experts", "n_routed_experts", "moe_num_experts")
|
| 147 |
+
res.vocab_size = _cfg_get(cfg, "vocab_size")
|
| 148 |
+
except Exception as e:
|
| 149 |
+
logger.warning("[analyze] config load failed for %s: %s", model_id, e)
|
| 150 |
+
|
| 151 |
+
# 2. Per-tensor metadata from safetensors headers (no weight download).
|
| 152 |
+
try:
|
| 153 |
+
meta = get_safetensors_metadata(model_id, revision=revision, token=token)
|
| 154 |
+
except Exception as e:
|
| 155 |
+
res.ok = False
|
| 156 |
+
res.error = (
|
| 157 |
+
f"Could not read safetensors metadata: {e}. "
|
| 158 |
+
"The model may lack safetensors (e.g. GGUF/pytorch_model.bin) or be gated/private."
|
| 159 |
+
)
|
| 160 |
+
return res
|
| 161 |
+
|
| 162 |
+
# Map filename β {tensor_name: TensorInfo} for shapes/dtypes/param counts.
|
| 163 |
+
cats: dict[str, CategoryStat] = {c: CategoryStat(c) for c in _CATEGORY_ORDER}
|
| 164 |
+
total = 0
|
| 165 |
+
for fname, fmeta in (meta.files_metadata or {}).items():
|
| 166 |
+
for tname, tinfo in (fmeta.tensors or {}).items():
|
| 167 |
+
pc = int(getattr(tinfo, "parameter_count", 0) or 0)
|
| 168 |
+
shape = list(getattr(tinfo, "shape", []) or [])
|
| 169 |
+
norm = _LAYER_IDX_RE.sub(".N.", tname)
|
| 170 |
+
cat = _categorize(norm)
|
| 171 |
+
cs = cats[cat]
|
| 172 |
+
cs.params += pc
|
| 173 |
+
cs.tensors_total += 1
|
| 174 |
+
if len(shape) == 2:
|
| 175 |
+
cs.tensors_2d += 1
|
| 176 |
+
total += pc
|
| 177 |
+
|
| 178 |
+
res.total_params = total
|
| 179 |
+
res.categories = [cats[c] for c in _CATEGORY_ORDER if cats[c].tensors_total > 0]
|
| 180 |
+
|
| 181 |
+
# 3. Derived traits.
|
| 182 |
+
res.is_moe = cats["moe_experts"].tensors_total > 0 or bool(res.num_experts)
|
| 183 |
+
res.has_shared_experts = cats["shared_experts"].tensors_total > 0
|
| 184 |
+
res.has_attn_indexer = cats["attn_indexer"].tensors_total > 0
|
| 185 |
+
res.has_vision = cats["vision"].tensors_total > 0
|
| 186 |
+
|
| 187 |
+
# 4. Recommendations.
|
| 188 |
+
res.recommended_ignore_layers, res.recommended_layer_config = _recommend(res, cats)
|
| 189 |
+
return res
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def _recommend(res: ModelAnalysis, cats: dict[str, CategoryStat]) -> tuple[str, str]:
|
| 193 |
+
"""Produce a recommended ignore_layers list + mixed-precision layer_config.
|
| 194 |
+
|
| 195 |
+
Heuristic (from the reference mixed-precision docs):
|
| 196 |
+
* Always skip/ignore: lm_head, embeddings (type-skipped), router/gate, norms (1D).
|
| 197 |
+
* Vision tower, projectors, attention indexer β ignore (keep bf16).
|
| 198 |
+
* MoE routed experts β low-bit (MXFP4); shared experts β higher precision
|
| 199 |
+
via layer_config (MXFP8) when present.
|
| 200 |
+
"""
|
| 201 |
+
ignore: list[str] = []
|
| 202 |
+
if cats["lm_head"].tensors_total:
|
| 203 |
+
ignore.append("lm_head")
|
| 204 |
+
if cats["router_gate"].tensors_total:
|
| 205 |
+
# Common router names; users can trim to their model's exact name.
|
| 206 |
+
ignore.append("gate")
|
| 207 |
+
if cats["vision"].tensors_total:
|
| 208 |
+
ignore.append("vision_tower")
|
| 209 |
+
if cats["projector"].tensors_total:
|
| 210 |
+
ignore.append("multi_modal_projector")
|
| 211 |
+
ignore.append("patch_merge")
|
| 212 |
+
if cats["attn_indexer"].tensors_total:
|
| 213 |
+
ignore.append("indexer")
|
| 214 |
+
# Deduplicate, preserve order.
|
| 215 |
+
seen = set()
|
| 216 |
+
ignore = [x for x in ignore if not (x in seen or seen.add(x))]
|
| 217 |
+
|
| 218 |
+
layer_config = ""
|
| 219 |
+
if res.is_moe and cats["moe_experts"].tensors_total:
|
| 220 |
+
# Route the routed experts to MXFP4; leave the rest at the global scheme.
|
| 221 |
+
# 'experts' matches routed experts without touching 'shared_experts'.
|
| 222 |
+
layer_config = "{experts:{bits:4,data_type:mx_fp}}"
|
| 223 |
+
|
| 224 |
+
return ",".join(ignore), layer_config
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
# ββ Markdown rendering βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 228 |
+
def _fmt_b(params: int) -> str:
|
| 229 |
+
if params >= 1e9:
|
| 230 |
+
return f"{params / 1e9:.2f} B"
|
| 231 |
+
if params >= 1e6:
|
| 232 |
+
return f"{params / 1e6:.2f} M"
|
| 233 |
+
return str(params)
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def render_analysis_markdown(res: ModelAnalysis) -> str:
|
| 237 |
+
if not res.ok:
|
| 238 |
+
return f"### β οΈ Model structure analysis failed\n\n{res.error}"
|
| 239 |
+
|
| 240 |
+
lines: list[str] = []
|
| 241 |
+
lines.append(f"### π Model structure β `{res.model_id}`\n")
|
| 242 |
+
|
| 243 |
+
# Overview
|
| 244 |
+
lines.append("| Field | Value |")
|
| 245 |
+
lines.append("|---|---|")
|
| 246 |
+
if res.architectures:
|
| 247 |
+
lines.append(f"| Architecture | `{', '.join(res.architectures)}` |")
|
| 248 |
+
if res.model_type:
|
| 249 |
+
lines.append(f"| model_type | `{res.model_type}` |")
|
| 250 |
+
lines.append(f"| Total parameters (measured) | **{_fmt_b(res.total_params)}** |")
|
| 251 |
+
if res.num_layers is not None:
|
| 252 |
+
lines.append(f"| Layers | {res.num_layers} |")
|
| 253 |
+
if res.num_experts:
|
| 254 |
+
lines.append(f"| Experts | {res.num_experts} |")
|
| 255 |
+
if res.hidden_size is not None:
|
| 256 |
+
lines.append(f"| hidden_size | {res.hidden_size} |")
|
| 257 |
+
if res.vocab_size is not None:
|
| 258 |
+
lines.append(f"| vocab_size | {res.vocab_size} |")
|
| 259 |
+
traits = []
|
| 260 |
+
if res.is_moe:
|
| 261 |
+
traits.append("MoE")
|
| 262 |
+
if res.has_shared_experts:
|
| 263 |
+
traits.append("shared-experts")
|
| 264 |
+
if res.has_attn_indexer:
|
| 265 |
+
traits.append("sparse-attn-indexer")
|
| 266 |
+
if res.has_vision:
|
| 267 |
+
traits.append("vision")
|
| 268 |
+
if traits:
|
| 269 |
+
lines.append(f"| Traits | {', '.join(traits)} |")
|
| 270 |
+
lines.append("")
|
| 271 |
+
|
| 272 |
+
# Parameter distribution
|
| 273 |
+
lines.append("#### Parameter distribution by module category\n")
|
| 274 |
+
lines.append("| Category | Params | Share | 2D-linear tensors |")
|
| 275 |
+
lines.append("|---|---:|---:|---:|")
|
| 276 |
+
total = res.total_params or 1
|
| 277 |
+
for cs in sorted(res.categories, key=lambda c: c.params, reverse=True):
|
| 278 |
+
share = 100.0 * cs.params / total
|
| 279 |
+
label = _CATEGORY_LABELS.get(cs.name, cs.name)
|
| 280 |
+
lines.append(f"| {label} | {_fmt_b(cs.params)} | {share:.2f}% | {cs.tensors_2d} |")
|
| 281 |
+
lines.append("")
|
| 282 |
+
|
| 283 |
+
# Recommendations
|
| 284 |
+
lines.append("#### Recommended quantization controls\n")
|
| 285 |
+
if res.recommended_ignore_layers:
|
| 286 |
+
lines.append(f"- **Ignore Layers:** `{res.recommended_ignore_layers}`")
|
| 287 |
+
else:
|
| 288 |
+
lines.append("- **Ignore Layers:** *(defaults are fine β nothing extra to skip)*")
|
| 289 |
+
if res.recommended_layer_config:
|
| 290 |
+
lines.append(f"- **Layer Config (mixed precision):** `{res.recommended_layer_config}`")
|
| 291 |
+
lines.append(" - routes MoE routed experts to MXFP4 while the rest stay at the global scheme.")
|
| 292 |
+
lines.append("")
|
| 293 |
+
lines.append("> These are suggestions β review against your model's exact module names before submitting. "
|
| 294 |
+
"Norms (1D) and embeddings are skipped automatically by AutoRound.")
|
| 295 |
+
return "\n".join(lines)
|
src/submission/submit.py
CHANGED
|
@@ -722,6 +722,9 @@ def add_new_quant(
|
|
| 722 |
gpu_count_override: int | None = None,
|
| 723 |
container_disk_size: str | None = None,
|
| 724 |
cuda_visible_devices: str | None = None,
|
|
|
|
|
|
|
|
|
|
| 725 |
submitted_by: str = "",
|
| 726 |
submitted_orgs: list[str] | None = None,
|
| 727 |
user_token: str | None = None,
|
|
@@ -964,6 +967,12 @@ def add_new_quant(
|
|
| 964 |
}
|
| 965 |
if container_disk_size:
|
| 966 |
quant_entry["container_disk_size"] = container_disk_size
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 967 |
_apply_b200_local_fields(quant_entry, quant_gpu_type, cuda_visible_devices)
|
| 968 |
|
| 969 |
supplementary = _get_supplementary_info(
|
|
|
|
| 722 |
gpu_count_override: int | None = None,
|
| 723 |
container_disk_size: str | None = None,
|
| 724 |
cuda_visible_devices: str | None = None,
|
| 725 |
+
export_format: str | None = None,
|
| 726 |
+
ignore_layers: str | None = None,
|
| 727 |
+
layer_config: str | None = None,
|
| 728 |
submitted_by: str = "",
|
| 729 |
submitted_orgs: list[str] | None = None,
|
| 730 |
user_token: str | None = None,
|
|
|
|
| 967 |
}
|
| 968 |
if container_disk_size:
|
| 969 |
quant_entry["container_disk_size"] = container_disk_size
|
| 970 |
+
if export_format:
|
| 971 |
+
quant_entry["export_format"] = export_format
|
| 972 |
+
if ignore_layers:
|
| 973 |
+
quant_entry["ignore_layers"] = ignore_layers
|
| 974 |
+
if layer_config:
|
| 975 |
+
quant_entry["layer_config"] = layer_config
|
| 976 |
_apply_b200_local_fields(quant_entry, quant_gpu_type, cuda_visible_devices)
|
| 977 |
|
| 978 |
supplementary = _get_supplementary_info(
|