Skip to content

refactor: run mlxcel generate on the engine and retire CxxGenerator #2176

Closed

Description

@inureyes

Part of #2166. Phase 6. Depends on #2173 (Phase 5: run and chat on the server engine), and through it on the engine API from #2172.

Problem / Background

After Phase 5, only mlxcel generate (raw completion, --profile, benchmarks) and the speculative CLI features still decode through CxxGenerator. While those four loops exist, every decode fix can still need a CLI-only twin, and bench_decode keeps timing a path the server does not run. llama.cpp kept a thin raw loop, llama-completion, for exactly this use after its CLI moved onto the server (ggml-org/llama.cpp#17824). In mlxcel that raw path should run on the engine at B=1.

Current Behavior

  • CxxGenerator has four decode loops in src/lib/mlxcel-core/src/generate.rs: :1914, :2233, :2416 and :2683. It also has evaluate_loglikelihoods at :2991, reached through MlxInferenceSession (src/lib/mlxcel-core/src/session.rs:338) and src/backend/session.rs:437.
  • src/bin/bench_decode.rs:381 and :412 construct CxxGenerator directly, and src/bin/speculative_bench.rs uses it too.
  • The CLI-only speculative features are prompt-lookup decoding (PromptLookupGenerator, src/lib/mlxcel-core/src/speculative/prompt_lookup.rs:669, wired by PromptLookupOptions in src/main.rs:376) and the classic draft-model SpeculativeGenerator (src/lib/mlxcel-core/src/speculative/mod.rs:225). The server never uses the latter: SpeculativeDispatch::Classic falls back to plain decode (src/server/batch/speculative_burst.rs:924).
  • mlxcel generate --draft-kind mtp does not use CxxGenerator: run_offline_mtp (src/commands/generate.rs:2233, routed at :1791) drives MtpGenerator over the same per-target adapters the server burst uses. Since fix(gemma4): make MTP verify byte-identical to decode on CUDA #2185 the Gemma 4 adapter takes an optional history-boundary prefill split (with_prefill_boundary, src/models/gemma4_mtp_target.rs:392). The server sets it and the offline CLI path leaves it unset, which matches classic CLI prefill (never split there).
  • src/backend/mod.rs:31-43 and session.rs:47-60 describe the CLI as wrapping CxxGenerator.

Proposed Solution

  1. mlxcel generate runs on the Engine from refactor: batch-native engine step API with BatchScheduler on top #2172 at B=1: open, then prefill through the PrefillPlan, then step until StepOutcome.finish is Some, then close. --profile and TTFT phase reporting (TtftPhases, generate.rs:1608) read timings from the engine. VLM embeddings come from the server's prepare_request_vlm_embeddings, as Phase 5 set up.
  2. MlxInferenceSession wraps Engine instead of CxxGenerator. evaluate_loglikelihoods becomes an engine prefill-only scoring call.
  3. Prompt-lookup decoding is ported to an engine-level drafter that implements the same Drafter interface the server's MTP and DFlash bursts use, so the server can offer it too. Rejected alternative: keeping it CLI-only, which would recreate a CLI-only loop.
  4. Deprecate the classic draft-model SpeculativeGenerator. Using it prints a deprecation notice that points to --draft-kind mtp or dflash. Remove it in the next minor release, and record the removal version in CHANGELOG.md. If the maintainer prefers a port instead, the issue comment that decides this replaces this item.
  5. bench_decode and speculative_bench measure the engine path.
  6. Remove the four CxxGenerator loops, then the type, or keep a shim with a removal date in its doc comment. Remove dead helpers and the stale comments that describe the old split (including src/server/model_worker.rs:1115, and src/backend/mod.rs and session.rs above).

Scope

In scope: src/commands/generate.rs, generate_vlm.rs and generate_dflash.rs; src/bin/bench_decode.rs and speculative_bench.rs; the session wrappers; the prompt-lookup drafter; the SpeculativeGenerator deprecation; removing CxxGenerator; and docs.

Out of scope: the remote --server-base client, and new speculative methods.

Implementation Notes

  • Reuse: the engine API, the Drafter trait the server burst uses (src/lib/mlxcel-core/src/drafter/mod.rs:840), and the existing bench_decode CSV schema, so historical results stay comparable. Add one column that names the path.
  • Constraints: generate output under MLXCEL_SDPA_DETERMINISTIC=1 must equal run -p and the server for the same request. Single-stream decode must stay within the Phase 0 threshold of the pre-epic baseline.
  • Edge cases: families with model-owned state that today reset through CxxGenerator (src/models/gemma4.rs:6210, src/vision/gemma4_vl.rs:652) must reset through the engine's open and close. Gemma 3 builds its CLI fallback slot without the rotating undo log (make_fallback_caches, src/models/gemma3.rs:1569, used by reset_caches) and its scheduler sequences with it when every sliding layer is FP16 (make_configured_caches, :1555, perf(scheduler): restore Gemma 3 decode lookahead via ring rewind #2182). Once generate opens engine sequences it gets the second form. Row copies happen only inside the lookahead's DecodeLookaheadAppendScope, so a B=1 run without lookahead pays only the cursor bookkeeping. Confirm that with the bench_decode run below.

Acceptance Criteria

  • rg -n 'CxxGenerator' src returns nothing, or only a documented shim with a removal date
  • mlxcel generate, --profile, bench_decode, speculative_bench and --prompt-lookup work on the engine, each checked on a real checkpoint
  • Using the classic draft-model path prints the deprecation notice, and CHANGELOG.md records it
  • Under MLXCEL_SDPA_DETERMINISTIC=1, the Phase 0 harness shows generate, run -p and the server giving identical greedy output on Qwen3-1.7B and Llama-3.2-1B
  • bench_decode on the engine is within the Phase 0 threshold of the pre-epic CxxGenerator baseline at short and long context. The results are committed under docs/benchmark_results/
  • No stale comment describes a CLI/server decode split. rg -n 'CxxGenerator|SpeculativeGenerator' src/server returns nothing

Verification

make verify                                   # macOS gate
make verify-fmt verify-test-cuda              # Linux/CUDA gate
MLXCEL_SDPA_DETERMINISTIC=1 make <Phase 0 parity target> MODEL=<Qwen3-1.7B-4bit path>
./scripts/bench_decode.sh <Llama-3.2-1B-4bit path>

References verified against main at fc21784 (2026-10-07).

Activity

  1. added
    type:refactorCode restructuring without changing functionality
    status:backlogIn the backlog, not yet ready
    area:cliCommand-line interface / CLI flags
    area:benchmarkBenchmark harness and performance measurement (bench_*.sh, /update-benchmarks)
    on Oct 6, 2026
  2. added and removed
    status:backlogIn the backlog, not yet ready
    on Oct 7, 2026
  3. added a commit that references this issue on Oct 12, 2026
    30ceea2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:benchmarkBenchmark harness and performance measurement (bench_*.sh, /update-benchmarks)area:cliCommand-line interface / CLI flagspriority:mediumMedium prioritystatus:doneCompletedtype:refactorCode restructuring without changing functionality

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions