Skip to content

[WIP] Input-source metadata for module tree definitions (shared-input groundwork) - #3051

Closed
Qubitium wants to merge 6 commits into
mainfrom
devin/1788500745-input-source-metadata
Closed

[WIP] Input-source metadata for module tree definitions (shared-input groundwork)#3051
Qubitium wants to merge 6 commits into
mainfrom
devin/1788500745-input-source-metadata

Conversation

@Qubitium

@Qubitium Qubitium commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Groundwork for deduplicating Hessian / activation-statistic collection across modules that consume the exact same input activation. This PR adds the generic metadata layer + a forward-time validator (no GPTQ math or hook-sharing changes yet):

  • numeric flag :N keeps meaning "calculation/replay subset"
  • new :input = this module has its own unique input source
  • new :input=<name> = named alternate input shared by same-named modules in the same structural scope
  • default rule: same structural container (tree scope) + same subset id ⇒ same input source

Existing definition strings ("q_proj:0", roles :q/:k/:v/:gate/:up, :!, :?, :moe) are unchanged and build_layer_modules() output is byte-identical (input tokens are metadata-only and never emitted).

What Changed

  • gptqmodel/models/input_source.py (new, GPTQ-agnostic):
    UniqueInput() | NamedInput(name)            # InputSpec; None == default rule
    parse_input_flag(flags) -> (InputSpec|None, remaining_flags)
    ModuleTreeEntry(full_path, scope, subset_id, input_spec, not_quantized, capture_only)
    InputSourceId(scope, subset_id=None | name=None | module=None)   # frozen/hashable, .kind
    resolve_input_source(NamedModule) -> InputSourceId
    group_input_sources(Iterable[NamedModule]) -> dict[InputSourceId, list[NamedModule]]
  • BaseQModel: _parse_module_spec() (name, flags, input_spec); _parse_module_flags() now hides input tokens from all existing flag consumers. _build_layer_modules_for_tree(..., entries_out=) records a ModuleTreeEntry per path; build_module_tree_entries() / resolve_module_tree_entry(name) (cached per class; mlp.experts.{expert_index}.up_proj matches mlp.experts.7.up_proj and yields scope mlp.experts.7, so expert 0 and expert 1 never share a source).
  • NamedModule gains tree_scope_id, subset_id, input_spec; ModuleLooper.create_named_modules fills them from the model definition (tree_scope_id = f"{layer_prefix}.{entry.scope}"). Unknown/lm_head modules resolve to a unique (never-merged) source.
  • SubsetPlan.input_sources: dict[InputSourceId, list[NamedModule]], recomputed by for_modules() chunks.
  • gptqmodel/looper/input_source_validator.py (new): InputSourceCapture (forward pre-hooks recording each module's input tensor), validate_input_sources(groups, captured) -> InputSourceValidationReport (identity/storage fast path → shape → dtype → torch.equal; reasons call_count|shape|dtype|value), InputSourceValidationError naming source, modules and shapes.
  • QuantizeConfig.validate_input_sources: bool = False (debug mode, serialized like auto_forward_data_parallel). When on, build_subset_plan forces serial forward and _run_single_subset_pass captures + validates every multi-module input-source group of the subset, logs Input-source validation: layer=… checked_sources=… checked_modules=… and raises on mismatch.
  • Model definitions annotated where equal subset id ≠ equal input (verified against the transformers modeling code by CPU forward simulation where the family exists in transformers 5.16): DeepSeek V2/V3/V3.2/V4, DeepSeek-VL2, GLM4-MoE-Lite, GLM-MoE-DSA, GLM5-Next, Kimi-K2.5, LongCat-Flash, MiniCPM3, AXK2. Pattern: q_b_proj/kv_b_proj consume normalized latents → :input; DSA indexer.wq_b shares the Q latent with q_b_proj:input=q_latent on both. Dense Llama/Qwen definitions untouched.

Out of scope (later PRs): shared Hessian accumulator per InputSourceId, hook dedup (one capture owner per source), lifecycle/ownership, placement optimizations.

Tests

  • I added a new simple/fast unit test for this change, or documented why that is not applicable.
  • I ran the new targeted test locally before opening this PR.
  • I ran any other directly relevant local tests.

All CPU-only, tiny synthetic configs (hidden 32, 1 layer, 4 experts), real transformers modeling code, experts unfused via defuser.convert_model exactly as the loader does:

  • tests/module_tree/test_input_source.py — parser cases (q_proj:0, :input, :input=foo, with/without role tags, !/?, error cases), block-output back-compat, entry/scope resolution incl. expert templates, InputSourceId grouping on tiny Llama / DeepSeek-V3 / Qwen2-MoE / Qwen3-MoE, SubsetPlan.input_sources + chunk recomputation.
  • tests/module_tree/test_input_source_validator.py — validator unit tests (identity fast path, equal values, shape/dtype/value/call_count reasons, max_calls, kwargs capture).
  • tests/module_tree/test_input_source_forward.py — full tiny-model forward with capture hooks, asserting every declared group actually receives identical inputs: Llama (q/k/v, gate/up), Qwen2-MoE, Qwen3-MoE, Mixtral (per-expert gate/up + shared expert), DeepSeek-V3, MiniCPM3, DeepSeek-V3.2, GLM-MoE-DSA (q_b_proj+indexer.wq_b share q_latent, kv_b_proj alone), GLM4-MoE-Lite. Negative tests prove the validator catches the pre-annotation groupings (q_b_proj+kv_b_proj, expert0+expert1 gate_proj, q_proj+o_proj). DeepSeek-V4 and GLM5-Next are skipped with reason (tiny-config constraints in transformers 5.16).
  • tests/module_tree/test_input_source_quantize.py — end-to-end CPU GPTQ quantize of a tiny Llama with validate_input_sources=True; asserts the validation log lines and config round-trip.
cd tests && python -m pytest module_tree/test_input_source*.py module_tree/test_subset.py -q \
  --deselect module_tree/test_subset.py::test_qwen3_5_moe_subset_early_stop_follows_module_tree_execution_order
# 44 passed, 2 skipped, 1 deselected   (deselected test is CUDA-only; env is CPU torch)
python -m pytest module_tree -q   # 1 pre-existing failure on main: test_moe_flag_parsing::test_get_moe_module_name_none_tree

Review Requirements

  • I personally reviewed every file in this diff.
  • I checked that the code matches existing project structure, APIs, and conventions.
  • I avoided unnecessary monkeypatching and used the project's normal extension points where possible.

Notes

WIP. Semantics are additive; nothing consumes SubsetPlan.input_sources for Hessian sharing yet — that is the next phase. deepseek_vl_v2 annotation follows the DeepSeek-V2 MLA pattern but could not be validated by forward simulation (no modeling code in transformers 5.16).

@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration
devin-ai-integration Bot deleted the devin/1788500745-input-source-metadata branch September 4, 2026 06:12
@Qubitium Qubitium closed this Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant