Status: In progress on branch
llm-refactor. Last updated: 2026-05-11 Commits:ffe4984(annotations/re-exports),7e4c7e1(split compiled.rs),84dd468(extract expr/tape)
Generated by the LLM AI Coding Agent audit checklist. Each finding is evaluated against the principles in the skill: predictable structure, flat explicit architecture, linear functions with explicit state, regenerability, and comment hygiene.
File: src/multi/compiled.rs (4,615 lines)
This single file holds: WGPU backend, SIMD x2/x4 backends, scalar backend, device planning types, batch buffer types, CompiledGraph, BackendKind dispatch, OpCode/FlatInstruction, and all execution logic. Five distinct concerns are interleaved in one file.
Proposed split:
src/multi/
backend/
mod.rs → pub mod + re-exports
types.rs → OpCode, FlatInstruction, BackendCapabilities, Instruction
scalar.rs → ScalarBackend
simd.rs → SimdBackend (f64x2, f64x4)
wgpu.rs → WgpuBackend, WgpuBuffer, WgpuBufferSet (feature-gated)
device.rs → DeviceBatchPlan, DeviceBuffer, DeviceBufferSet,
DeviceMemoryLocation, DeviceTransferKind, etc.
dispatch.rs → BackendKind, BackendSupportReport, auto-dispatch logic
mock.rs → MockDeviceBackend, DeviceExecutionTrace
compiled.rs → keep: CompiledGraph, CompiledWorkspace, BatchInputs,
BatchValues, BatchGradients, metadata
Benefit: Each backend can be regenerated independently without understanding the others. A future LLM working on WGPU won't need to parse SIMD dispatch logic.
File: src/multi/compiled.rs, line ~1,852
The single largest function in the codebase. It dispatches to Scalar, SIMD x2, SIMD x4, WGPU paths with inline per-backend logic.
Proposed fix:
fn gradient_batch_auto_into(compiled, batch, buf) -> Result<()> {
let backend = compiled.recommended_batch_gradient_backend();
match backend {
BackendKind::Scalar => gradient_batch_scalar_into(compiled, batch, buf),
BackendKind::SimdF64x2 => gradient_batch_simd_f64x2_into(compiled, batch, buf),
// ...
}
}
// Each arm becomes a standalone 20-50 line function.Benefit: A future LLM replacing the SIMD backend only needs to read one small function, not parse 1,000 lines of unrelated dispatch code.
File: src/multi/graph.rs (6,315 lines)
Contains: Graph (core API, 1,550-line impl), Tape/TapeWorkspace,
ExprGraph/ExprNode (expression graph with operator overloading),
GradientCheckEntry/GradientCheckReport, GraphNode, NodeId,
DomainPolicy, GraphStats, WGPU helpers (cfg-gated), serde support
(cfg-gated).
Proposed split:
src/multi/
graph/
mod.rs → pub mod + re-exports
core.rs → Graph, GraphNode, NodeId, DomainPolicy, GraphStats
tape.rs → Tape, TapeWorkspace
expr.rs → ExprGraph, ExprNode, operator overloads
check.rs → GradientCheckEntry, GradientCheckReport
graph.rs → keep if preferred, or delete
Benefit: The Graph impl shrinks from 1,550 lines to ~400 lines of core
logic. ExprGraph changes won't touch Graph internals.
File: src/multi.rs, lines 51-55
#[allow(unused_imports)]
pub use multi_ad_fr::MultiAD2FR;
#[allow(unused_imports)]
pub use multi_ad_rf::MultiAD2RF;
#[allow(unused_imports)]
pub use multi_ad_rr::MultiAD2RR;These types are re-exported but never used internally in multi.rs. The
#[allow(unused_imports)] is noise. Replace with a module-level
#![allow(unused_imports)] or better: re-export them through lib.rs only and
remove from multi.rs since they're already in lib.rs's pub use block.
Affected files: src/mono/mono_fn.rs, src/multi/multi_fn.rs,
src/mono/types.rs, src/forward.rs, src/multi/builder.rs
Public library methods tagged #[allow(dead_code)] signal that no internal
code calls them. This is expected for a library, but the annotations clutter
the code. Options:
- Move to a single
#![allow(dead_code)]at crate root - Or remove the annotations (dead_code doesn't apply to
pubitems when building as a library, only as a binary)
Files: src/mono/mono_hessian_common.rs and
src/multi/multi_hessian_common.rs
Both define:
struct Dual { val: f64, tan: f64 }
enum OpKind { Sin, Cos, Tan, Exp, Neg, Ln, Sqrt, Abs, ... }The two Dual types are nearly identical (mono adds Dual::constant,
multi adds Add/Sub/Mul/Div impls). The OpKind enums have different
variants (mono has 8, multi has 16+).
Proposal: Move Dual to src/types.rs with both constant and arithmetic
impls. Keep OpKind separate since the variants differ, but name them
MonoOpKind and MultiOpKind to clarify which is which.
Files: src/lib.rs (30-line pub use block), src/multi.rs (30-line
pub use block)
Half the types are re-exported in both files. A future LLM needs to read both to understand what's publicly available.
Proposal: Use pub use multi::*; in lib.rs for most types, with explicit
re-exports only for items that need renaming or feature-gating. Keep multi.rs
as the single source of truth for what multi exports.
Files: src/mono/mf1.rs through mf4.rs (4 files), src/multi/f1.rs
through f3.rs (3 files)
Each file is ~30 lines: one struct + one trait impl, used in exactly one test. Seven extra files for 210 lines of test code.
Proposal: Move the struct definitions into the test module that uses them
(src/mono/tests.rs, src/multi/tests.rs) as module-level items, or
consolidate into a single src/mono/tests/examples.rs.
File: src/forward.rs (840 lines)
Contains both ForwardAD::differentiate (mono) and
ForwardAD::directional_derivative (multi), plus graph-based forward mode.
Three related but distinct APIs in one file.
Proposal: Keep src/forward.rs for the ForwardAD struct and shared
types. Move mono differentiation to src/mono/forward.rs and multi
directional derivative to src/multi/forward.rs.
File: src/tests_comprehensive.rs, lines ~630-640
// Note: F1, F2, F3 are private modules, tested through multi::tests
// since MF1-4 are private implementation detailsThese comments document where other types are tested — information that belongs in those test modules, not in the comprehensive test file. Remove or move.
File: src/multi/compiled.rs, fn batch_plan (135 lines)
This function builds a DeviceBatchPlan for each backend variant with inline
code for every variant. Extract:
fn plan_scalar(graph, batch_size) -> DeviceBatchPlan { ... }
fn plan_simd_f64x2(graph, batch_size) -> DeviceBatchPlan { ... }
fn plan_wgpu(graph, batch_size) -> DeviceBatchPlan { ... }File: src/mono/mf4.rs, line 19
&mono_ops![sin, neg,]The trailing comma after neg is technically valid in Rust macros but
inconsistent with other mono_ops! calls. Remove for consistency.
File: src/macros.rs (107 lines)
The file defines mono_ops! and multi_ops! macros but has no module-level
doc comment. Add a brief explanation of the macro API and syntax.
Files: src/mono/types.rs, src/multi/types.rs
Both define different types but share the same name. When reading code via
use super::types::*, it's ambiguous which module is being referenced.
Proposal: Rename to mono_types.rs and multi_types.rs, or use
mono::types / multi::types consistently in imports (already done, but
the filename convention would help).
Files: src/mono/mf1.rs through mf4.rs, src/multi/f1.rs through f3.rs
These files are declared as #[cfg(test)] mod mf1; in mono.rs, but each
struct inside also has #[cfg(test)]. The outer annotation already gates
the entire module. Remove the inner annotations.
| Priority | Items | Effort Estimate |
|---|---|---|
| High | 5 | 2-3 days (split compiled.rs + graph.rs) |
| Medium | 6 | 1-2 days |
| Lower | 4 | 0.5 day |
The highest-value change is splitting compiled.rs into backend modules.
This alone addresses the core violation of the "predictable structure" and
"flat, explicit architecture" principles. A future LLM can work on the
WGPU backend without touching SIMD logic, and vice versa.