feat(engine): stage D6 - qwen3_5 dense full-attn deltas#99
Conversation
Enable pure-dense MODEL_QWEN3_5 on the dense forward path: attn_output_gate (q_proj split + sigmoid gate) and partial_rotary_factor rope dims, plus weights descriptor, tiny fixtures, support-gate allow/reject matrix (R1 hybrid reject), and parity table id. Real hybrid checkpoints remain Stage E.
Code Review: PR #99 -- Stage D6 qwen3_5 dense full-attn deltasI read the full diff (16 files, +728/-19) and examined the relevant source code in depth. Overall this is a well-structured, well-documented change that cleanly extends the existing qwen3 dense path with the qwen3_5 deltas. The Below are my findings, ordered by severity. Issue 1 (Medium-Hard): Double
|
| # | Severity | Area | Description |
|---|---|---|---|
| 1 | Medium-Hard | forward.c + test |
gate and is_finite_f32 leak empty mlx_array handle on mlx_reshape overwrite (pre-existing pattern, one new instance) |
| 2 | Medium | forward.c |
KV-shared + partial_rotary_factor compatibility trap for Stage E (no bug now, document it) |
| 3 | Medium-Hard | Test coverage | No forward pass for attn_output_gate=false path (acceptable if documented as qwen3 coverage) |
| 4 | Low | forward.c |
rope_dims should floor to even for safety |
| 5 | Low | gen_tiny_ckpt |
Redundant flat rope_theta in generated configs |
| 6 | Low | test_weights.c |
Redundant weight_prefix = "model" |
| 7 | Nit | test_emodel_gate.c |
Test scope narrowing after family promotion |
The PR is solid. Issues 1-2 are the ones I'd prioritize discussing. The structural decisions (no reject_dense_common, inlined gate, name-table reuse) are all correct and well-commented.
Code review: PR #99 (Stage D6 - qwen3_5 dense full-attn deltas)Thanks for the thorough PR description with the audit table and Decision R1 -- it made the review significantly easier to navigate. The implementation is clean and well-scoped to the pure-dense path. Below are findings organized by severity. Blocking / needs verification1. mlx_vector_array split_vec = mlx_vector_array_new();
// ... mlx_split fills split_vec with 2 new arrays ...
mlx_vector_array_get(&queries, split_vec, 0);
mlx_vector_array_get(&gate_4d, split_vec, 1);
mlx_vector_array_free(split_vec); // <-- frees the container + its arraysThen This pattern is new to the codebase -- there are zero prior callers of Based on mlx's C++ semantics, arrays are 2.
Medium3. Redundant When "rope_theta": 10000000.0,
"rope_parameters": {
"rope_theta": 10000000.0,
"partial_rotary_factor": 0.25
}Not a bug (the model.c parser reads top-level first, then 4. No The PR test plan lists " 5.
Minor6. Gate redundancy: The gate checks:
Check (3) is the most precise. Checks (1) and (2) are useful early-exits for malformed or unparseable configs, but their overlap with (3) for well-formed configs adds maintenance weight. Consider a single unified hybrid gate function when Stage E adds linear-attention support. 7. Error message drift in default case (emodel.c:137-138) This string now requires updating every time a new family is added to the switch. Since it only fires for families that hit 8. The maxdiff tolerance for 9. Duplicated Same as item (3), but applied to the checked-in fixtures. Not a correctness issue but adds noise. Nit10. fprintf(stderr, "failed to load %s: %s\n", path, err);
assert(0);The error message is lost in Things I verified and found correct
SummaryThe implementation is solid. The forward-path gate split logic is new API territory for the codebase and needs an explicit ASan confirmation on items (1) and (2). The remaining items are maintainability nits that don't block merge. |
This comment was marked as resolved.
This comment was marked as resolved.
Hard-skip qwen3_5 hybrid parity via canonical path; honor parsed attn_output_gate and reject gate-off at support gate; reject full_attention_interval > 1 and odd/degenerate partial rope dims; refactor reject_dense_common with allow flags; clean tiny fixture generator (no flat rope_theta, emit interval:1); add mlx-lm golden logits tests for both tiny qwen3_5 fixtures.
Reply to review (comment 5018437321)Addressed findings 1-8 on Per finding
Verification
|
Gate-side REJECT for gemma4 plain partial_rotary_factor (was silently accepted while fwd_attention would take the qwen3_5 dims path). Dedup is_finite_f32 into tests/gpu_test_util.h, replace fprintf+assert(0) with abort() so load-failure messages survive NDEBUG, and document the shared 5e-2f bf16 prefill/decode tolerance on the qwen3_5 GPU suite.
Reply to second review (comment 5018410477)Verified every finding against HEAD after ceeb350 (first-review fixes) and addressed the remaining valid ones in 2abb872. Per finding
Verification
|
Summary
Implements Stage D6 (#83): pure-dense
MODEL_QWEN3_5on the dense forward path.attn_output_gate(doubledq_proj-> split queries/gate ->o_proj(attn * sigmoid(gate))) andpartial_rotary_factorrope dimslayer_typesweights_expected_namesshares the qwen3 descriptor tables (gate is shape, not a new name)gen_tiny_ckpt+ GPU smoke (test_forward_qwen3_5_gpu)mlx-community/Qwen3.5-0.8B-4bit; byte parity deferred to Stage EDecision R1
num_experts > 0=>MODEL_QWEN3_5_MOE). Linear-bearing non-MoE configs are not reclassified as MoE.MODEL_QWEN3_5(linear_num_*, hybridlayer_types,has_hybrid_layers).Audit vs
mlx-community/Qwen3.5-0.8B-4bit(config.json)model_typeqwen3qwen3_5, textqwen3_5_textweight_prefixmodellanguage_model.modelwhen nestedhead_dimpartial_rotary_factorrope_parametersattn_output_gatehas_qk_normnum_expertsMODEL_QWEN3_5linear_num_*/layer_typesrope_parameters.mrope_*Deferred (Stage E / #56)
Test plan
make tests/test_emodel_gate && ./tests/test_emodel_gatemake tests/test_model_config && ./tests/test_model_configmake tests/test_weights && ./tests/test_weightsmake tests/test_forward_qwen3_5_gpu && ./tests/test_forward_qwen3_5_gpumake test/make test-gpuin CI (local: known pre-existing env flakes in registry discover / deepseek GGUF message / e2e-oracle-500)Closes #83