Skip to content

Fix shell-out helpers and pass read-only parameters by const reference - #6

Merged
grorge123 merged 2 commits into
mainfrom
fix/code-quality
Oct 26, 2026
Merged

grorge123 merged 2 commits into
mainfrom
fix/code-quality

Conversation

@grorge123

Copy link
Copy Markdown
Owner

Summary

Code-quality fixes from a static-analysis pass (GitRoll / clang-tidy). There are no API changes for callers.

1. Stop shelling out (fix: commit)

  • fnv1aHash() (mlx/base.cpp) saved the array to ./temp_array.npy and ran popen("python3.10 ../hash_script.py ..."). hash_script.py doesn't exist in the repo, so debugArray() always threw. It now computes a 64-bit FNV-1a hash over the array's row-major bytes in C++, using mx::contiguous + eval(). There's no temp file, no Python, and no shell.
    • The old script is missing, so I can't confirm the new value matches what it returned. This is a debug helper, so I don't expect anything to depend on the exact value.
  • example/vlm.cpp used std::system("python3.10 ../example/decode.py " + ModelPath + " output.npy"). It now runs the same command through posix_spawnp with an argv array. ModelPath is passed verbatim, so paths with spaces or shell characters work and nothing is interpreted by a shell. The interpreter (python3.10), script and output file are unchanged. If the spawn fails, it logs an error, as before.

2. const & for read-only parameters (refactor: commit)

About 45 mx::array / std::string / std::shared_ptr parameters that are only read now take const &. Header declarations and definitions are changed together.

I deliberately left these alone:

  • Virtual functions: Embedding::forward, Linear::forward, Prompt::prepare. Their overrides (QuantizedEmbedding, QuantizedLinear, the *Prompt classes) would also have to change, so they're out of scope here.
  • "pass by value and move" suggestions: these need std::move rather than const &, so they belong in a separate change.

Not changed

  • bugprone-branch-clone: an earlier scan flagged 4 "identical if/else branches", in llm/transformer.cpp:104, llm/transformer.h:177, mllama/language.cpp:335 and vlm_base.cpp:351. All 4 are false positives. They came from a run that was missing the spdlog/fmt include paths. The branches really do differ: nn::RMSNorm vs the Gemma llm::RMSNorm, RopeScaling vs {}, and Cross vs Self decoder layers. With the real compile_commands.json, clang-tidy reports none of them.
  • whisper.cpp integer division (Channels / 2 - 1, NState / NHead): this matches the reference Python's //, so it's intended.
  • whisper_transcribe.cpp:46 still builds the ffmpeg command string and calls popen, so FilePath isn't quoted. Fixing that needs a spawn plus a pipe, which is a bigger change. I'd suggest a follow-up.

Verification

All of this ran on Linux x86_64 with clang 23 and MLX 0.32.4 (CPU wheel):

  • Build: fresh CMake/Ninja builds before and after (-Wall -Wextra -Wpedantic -Werror). The same 29 project translation units compile in both. The ones that fail are the same before and after, and none are caused by this PR:
    • fmt and sentencepiece, which don't build with clang 23 / libstdc++ 15
    • gemma3.cpp, which hits a libstdc++ 15 std::optional constraint error. Its error output is identical before and after.
  • clang-tidy with the real compile database: findings go from 135 → 54.
    • performance-unnecessary-value-param: 93 → 14 (the virtual and std::move cases above)
    • bugprone-command-processor: 3 → 1 (the ffmpeg one)
  • fnv1aHash: I compiled the new function on its own against libmlx and compared it to a NumPy reference. It matches for a 3-D float32 array, a transposed non-contiguous view of it, and an int32 vector.
  • spawn: the same posix_spawnp code received "../my models/x; echo INJECTED" as a single argv entry. A missing binary takes the error branch.

Not verified: macOS / Metal, linking and running the example binaries, and model inference. CI on this PR covers the Ubuntu and macOS builds.

🤖 Generated with Claude Code

grorge123 and others added 2 commits October 26, 2026 19:39
fnv1aHash() piped through `python3.10 ../hash_script.py`, a script that
is not in the repository, so debugArray() always threw. Compute the
64-bit FNV-1a hash of the array's row-major bytes directly instead.

The VLM example built a shell command by concatenating ModelPath. Spawn
decode.py with posix_spawnp and an argv array so the path is passed
verbatim and no shell is involved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pass mx::array, std::string and std::shared_ptr parameters that are
only read by const reference instead of by value, as reported by
clang-tidy performance-unnecessary-value-param. Declarations and
definitions are updated together.

Virtual functions (Embedding::forward, Linear::forward and
Prompt::prepare) are left unchanged so their overrides keep matching.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The helper replacements are focused, the signature changes are aligned, and no blocking correctness issues were identified.

No open issues 🎉

What changed in this PR

This PR removes shell dependencies from two helpers and reduces unnecessary parameter copies across the MLX layers and model code.

Changes:

  • Replaces Python-based array hashing with an in-process 64-bit FNV-1a implementation.
  • Uses posix_spawnp to launch the VLM detokenizer without shell interpretation.
  • Changes read-only parameters to const references, keeping declarations and definitions aligned and leaving virtual interfaces unchanged.
File Description
model/​whisper/​decoding.h Updates decoding declarations to const references.
model/​whisper/​decoding.cpp Matches decoding definitions to updated declarations.
model/​whisper_transcribe.h Updates model parameters in transcription declarations.
model/​whisper_transcribe.cpp Matches transcription definitions to updated declarations.
model/​vlm_base.h Updates the attention-mask input declaration.
model/​vlm_base.cpp Passes the attention-mask input by const reference.
model/​mllama/​language.h Updates attention and decoder cache declarations.
model/​mllama/​language.cpp Passes cache pointers by const reference.
model/​llm/​transformer.h Updates transformer array parameter declarations.
model/​llm/​transformer.cpp Uses const references for arrays and helper strings.
model/​converter.h Updates weight-path parameter declarations.
model/​converter.cpp Passes weight paths by const reference.
mlx/​quantized.h Updates quantization factory declarations.
mlx/​quantized.cpp Passes source module pointers by const reference.
mlx/​positional_encoding.h Updates the RoPE input declaration.
mlx/​positional_encoding.cpp Passes RoPE input by const reference.
mlx/​normalization.h Updates normalization input declarations.
mlx/​normalization.cpp Passes normalization inputs by const reference.
mlx/​embedding.h Updates the non-virtual asLinear declaration.
mlx/​embedding.cpp Passes asLinear input by const reference.
mlx/​convolution.h Updates convolution input declarations.
mlx/​convolution.cpp Passes convolution inputs by const reference.
mlx/​base.h Updates module registration and parameter interfaces.
mlx/​base.cpp Implements native hashing and matching parameter changes.
mlx/​activations.h Updates activation input declarations.
mlx/​activations.cpp Passes activation inputs by const reference.
example/​vlm.cpp Launches the detokenizer with explicit process arguments.

Review effort: Balanced

@grorge123
grorge123 merged commit 5543238 into main Oct 26, 2026
4 checks passed
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.

2 participants