Skip to content

examples/ruvLLM/esp32-flash: example fails to build against its own library; no ESP32 firmware in any release #409

@williavs

Description

@williavs

Context

We were evaluating examples/ruvLLM/esp32-flash as a candidate set of primitives (HNSW vector index, RAG, anomaly detection, on-device inference) for an embedded agent harness running on an ESP32-S3-class board. We cloned ruvnet/ruvector at commit 20aca12 and tried both the documented host-side test path and the documented "1-click web flasher" path.

Both paths failed before any code ran on the device. Filing this so the gap is visible.

Observation 1 — host-test feature does not build

Cargo.toml advertises a host-test feature ("for development testing only", per src/main.rs:732). Out of the box, cargo build --no-default-features --features host-test fails for two distinct reasons.

1a. build.rs unconditionally calls an espidf-only API:

// build.rs
fn main() {
    embuild::espidf::sysenv::output();
}

embuild::espidf is gated behind the espidf feature on embuild, which is not enabled in host-test mode, so the build script itself fails to compile:

error[E0433]: cannot find `espidf` in `embuild`
 --> build.rs:2:14
  |
2 |     embuild::espidf::sysenv::output();
  |              ^^^^^^ could not find `espidf` in `embuild`

A #[cfg(target_os = "espidf")] guard on that call lets the build script proceed in host mode.

1b. After fixing 1a, src/main.rs does not compile against src/lib.rs: 27 errors. The example is using an API that the library no longer exports. Examples (line numbers in src/main.rs):

main.rs reference Actual signature in lib.rs / submodules
LoRAConfig { rank, alpha, input_dim, output_dim } (L242) LoRAConfig { rank, dim, scale, frozen } (src/optimizations/micro_lora.rs:9)
MicroLoRA::new(c) (L197) MicroLoRA::new(config, seed) -> Result<Self> (micro_lora.rs:31)
lora.forward(&attn_out) (L207) method is apply(&mut self, input, output) (micro_lora.rs:62)
PQConfig { dim, num_subspaces, num_centroids } (L249–251) PQConfig does not have num_subspaces / num_centroids
DraftVerifyConfig { draft_length, max_rejections, temperature, verify_all } (L340–344) DraftVerifyConfig has none of those last three fields
BinaryVector::new() / ProductQuantizer::new() both are generic structs requiring const generics; no zero-arg new()
SparseAttention::get_mask(seq_pos) (L125) method does not exist on SparseAttention
LayerPruner::is_pruned(idx) (L166) method does not exist on LayerPruner
MemoryType::Factual (L417) variant does not exist on MemoryType
engine.add_knowledge(...)? (L417) underlying error type does not implement From<…> for ruvllm_esp32::Error, so ? does not compile

Full error count from cargo build --no-default-features --features host-test --target x86_64-unknown-linux-gnu: 27 errors, 2 warnings.

Observation 2 — No ESP32 firmware exists in any GitHub release

npm/web-flasher/index.html advertises a 1-click flasher and downloads firmware from:

const FIRMWARE_BASE_URL = 'https://github.com/ruvnet/ruvector/releases/latest/download';
const firmwareUrl = `${FIRMWARE_BASE_URL}/ruvllm-esp32-${target}`;

Querying the GitHub API for all 28 releases (gh api repos/ruvnet/ruvector/releases), zero of them contain any asset whose name matches esp32 or ruvllm. Latest release v2.2.0 (2026-04-20) only ships ruvector-attention-* and ruvector-math-wasm archives. The web flasher's fetch URL therefore 404s for every supported target (esp32, esp32s2, esp32s3, esp32c3, esp32c6).

The fallback in npm/bin/cli.js's flash command is to build from source first — which fails per Observation 1.

Observation 3 — README/docs vs. code

src/main.rs is presented as a "Full-Feature LLM v0.2" with "INT8/Binary quantized transformer inference", "MicroLoRA on-device adaptation", "Sparse attention patterns", and "Speculative decoding". Reading the example as written, those claims don't hold up:

  • Weights are never loaded; they're generated from index arithmetic. QuantizedWeights::new (L62–70) initializes every weight to ((i * 17 + 31) % 256) - 64. EmbeddingTable::new (L82–90) is the same shape. The LoRA a_weights PRNG (micro_lora.rs:38–44) is also seeded deterministically. There is a MicroLoRA::from_weights constructor (micro_lora.rs:53) and a from_weights-style entry would be the natural place to load a checkpoint, but nothing in the example crate calls it and there is no checkpoint format / loader / weight file in tree. So the "model" running on the device is a fixed function of source code, not learned parameters.
  • MicroAttention::forward (L123–139) is not attention. No Q·Kᵀ, no softmax, no V multiply. The body is output[i] = (input[i] * weight[i % len]) >> 7 gated by a sparse mask — i.e. a single elementwise multiply, not scaled-dot-product attention over a sequence.
  • FeedForward::forward (L161–176) is not a 2-layer MLP. It does one elementwise multiply against w1, ReLU, and writes out — no expansion to 4*EMBED_DIM, no second projection through w2. w2 is allocated and never read.
  • TinyModel::forward (L269–299) takes a single token and returns a single token deterministically. No KV cache, no sequence context beyond the sparse-mask seq_pos index. As a result, gen <text> produces the same byte sequence on every call for a given input.

If the intent is that this file is a structural skeleton / sizing demo rather than a working inference path, that's fine, but the README and the in-source doc comment ("Full-featured LLM inference engine for ESP32 with INT8/Binary quantized transformer inference") read as a real model. A one-line note in the example README ("this example demonstrates the on-device control surface and memory layout; weights are placeholders, not a trained model") would prevent people from going down the same evaluation path we did.

Reproduction

git clone https://github.com/ruvnet/ruvector.git
cd ruvector/examples/ruvLLM/esp32-flash

# Path A — host test (per Cargo.toml feature comment)
cargo build --no-default-features --features host-test
# => build.rs E0433, then 27 example errors after patching build.rs

# Path B — web flasher
# Open npm/web-flasher/index.html, click flash → 404 on ruvllm-esp32-* asset

# Path C — npm cli (compiles from source first)
node npm/bin/cli.js flash --target esp32s3
# => same 27 errors as Path A

Related

May overlap with the recently filed series #399, #400, #401, #402 reporting that other published demo/CLI surfaces are missing assets or out of sync with the library. Filing separately because those are about ruvector-cli, not examples/ruvLLM/esp32-flash.

Environment

  • Linux x86_64, rustc 1.95.0
  • ruvnet/ruvector at 20aca12 (latest main at time of filing)
  • ESP32-S3 device present and detected at /dev/ttyACM0 (USB ID 303a:1001); not flashed because no firmware could be produced.

What would unblock evaluation

  1. A host-test build that compiles end-to-end (or removal of the feature if it's no longer supported).
  2. Either a published ESP32 firmware asset matching the URL pattern in npm/web-flasher/index.html, or an updated flasher that points at where firmware actually lives.
  3. A note in examples/ruvLLM/esp32-flash/README.md clarifying whether the in-tree main.rs is meant to demonstrate inference behaviour or is a structural placeholder pending a separate model-loader path.

Happy to test fixes against the same hardware.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions