Skip to content

[FEATURE] Add compact binary model format (.namb) for fast embedded loading#222

Closed
jfsantos wants to merge 6 commits intosdatkinson:mainfrom
jfsantos:feature/binary_loader
Closed

[FEATURE] Add compact binary model format (.namb) for fast embedded loading#222
jfsantos wants to merge 6 commits intosdatkinson:mainfrom
jfsantos:feature/binary_loader

Conversation

@jfsantos
Copy link
Contributor

Introduces a binary serialization format that eliminates the need for
JSON parsing (no nlohmann/json dependency in the loader), achieving
80-89% file size reduction. Supports all architectures including
WaveNet with nested condition_dsp.

New files:

  • NAM/namb_format.h: format constants, CRC32, binary reader/writer
  • NAM/get_dsp_namb.h/.cpp: binary loader (zero JSON dependency)
  • tools/nam2namb.cpp: JSON-to-binary converter CLI tool
  • tools/test/test_namb.cpp: round-trip, validation, and size tests

Developed with support and sponsorship from TONE3000

…oading

  Introduces a binary serialization format that eliminates the need for
  JSON parsing (no nlohmann/json dependency in the loader), achieving
  80-89% file size reduction. Supports all architectures including
  WaveNet with nested condition_dsp.

  New files:
  - NAM/namb_format.h: format constants, CRC32, binary reader/writer
  - NAM/get_dsp_namb.h/.cpp: binary loader (zero JSON dependency)
  - tools/nam2namb.cpp: JSON-to-binary converter CLI tool
  - tools/test/test_namb.cpp: round-trip, validation, and size tests
@mikeoliphant
Copy link
Contributor

Is this intended as a new public distribution format? Or just as a tool for situations where using json is impractical?

A huge benefit of the json format is that it makes the .nam format very open and easy to integrate with. A custom binary format is much less so.

uint32_t crc_empty = nam::namb::crc32(nullptr, 0);
assert(crc_empty == 0x00000000u);

std::cout << " PASS" << std::endl;
Copy link
Owner

Choose a reason for hiding this comment

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

Can you make the tests silent on happy path?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oops, I did it again. I always mean to delete those after making sure everything works, and always forget.

<< std::fixed << std::setprecision(1) << reduction << "% reduction)" << std::endl;

// .namb should always be smaller than .nam
assert(namb_size < nam_size);
Copy link
Owner

Choose a reason for hiding this comment

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

Hmm...while it's true, it feels wrong to me to be asserting this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The assertions were to make sure the binary conversion wasn't doing anything silly... but we can remove them.

assert(namb_size < nam_size);

// Should be at least 50% reduction (typically ~85%)
assert(reduction > 50.0);
Copy link
Owner

Choose a reason for hiding this comment

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

...and this

@sdatkinson
Copy link
Owner

A few high-level questions to ponder @jfsantos:

  1. Will this be forward-compatible? I think so (magic number, file version) but give it a thought and lmk if you can think of any gotchas.
  2. It'd be nice to discern file type and route to the appropriate methods...but as I sit and think about this I think we're on the path to doing that optionally in a future PR, right?
  3. Any reasons you can think of why this shouldn't be in the main repo? Like I'm thinking that maybe this shouldn't be "the" binary format but one example / make some pattern for others to make their own? Or even better: consider how I routinely extend this library to make custom models. What would the experience doing so look like? Would it be necessary to change this library? The big example of this in the past was "registering" new models to load. In the current version of the code, I can create new architectures and register their factories for get_dsp to recognize; does this "box out" from doing that? Would it be much work to change?
    3.1. If I squint at this, it's kind of like we're defining new factories for models. Now, the current factory makes some kind of restrictive assumptions (e.g. that it's getting a JSON object etc as input), but is there some "gentle" nudge to that (ok if breaking) where it paves the way for these .namb loaders to be on equal footing to the existing factories?

Thoughts appreciated.

@sdatkinson
Copy link
Owner

sdatkinson commented Feb 12, 2026

@mikeoliphant the former--the current JSON-subset ".nam" format will still be the recommended "lingua franca" for models. I agree with you--it's very handy to be able to look at the files as a human :) and this feels like a nice "add-on".

In fact, this might be entirely separate from this "core" library. Arguments why / why not to do that are welcome.

@mikeoliphant
Copy link
Contributor

I'm thinking about it from a support perspective for NAM implementations that don't use NAM Core (I've got mine, Dimehead has theirs, etc.)

If this new format is going to be used as a new distribution format (ie: on Tone3000), then we'll need to add support for it.

@jfsantos
Copy link
Contributor Author

@mikeoliphant this is intended for low memory platforms where parsing JSON is not feasible because of the high memory costs. It will never be the main distribution system; that will still be JSON-based.

@mikeoliphant
Copy link
Contributor

@jfsantos 👍

@jfsantos
Copy link
Contributor Author

@sdatkinson thanks for your questions!

  1. It is meant to be forward compatible, hence the numbers. I think the approach should be to have it be forward compatible with anything that comes after A2.

  2. Yes, the reason I didn't change anything other than loadmodel to call the method for the correct file format is that I do not think core users in general will need to support this and will continue loading from .nam files. We could definitely do it to simplify code and have a simple get_dsp_from_file which routes to the right implementation.

  3. I think the ideal design would be to have some sort of ModelConfig object derived from either a .nam or .namb file, and have a single factory method that takes that object and instantiates a model. I reimplemented the factories for the binary format because it would make no sense to reuse the previous factories and this was the safest path that didn't risk breaking anything that already works. I guess one simpler approach would be to refactor the path that expects a JSON object to instead expect something based on std::map?

João Felipe Santos and others added 5 commits February 12, 2026 10:48
  Introduce typed config structs per architecture (LinearConfig, LSTMConfig,
  ConvNetConfig, WaveNetConfig) and a single create_dsp() function that both
  the JSON and .namb binary loaders feed into. This eliminates duplicated
  construction logic — adding a new architecture now only requires a format-
  specific parser, not separate factory code in each loader.

  - Add config structs and parse_config_json() to each architecture header/impl
  - Add NAM/model_config.h with ModelConfig variant, ModelMetadata, create_dsp()
  - Refactor get_dsp(dspData&) to use parse_model_config_json() → create_dsp()
  - Refactor get_dsp_namb.cpp load_*() to return typed configs → create_dsp()
  - Register missing Linear factory in FactoryRegistry
  - Silence test_namb.cpp output on success to match other tests
@jfsantos jfsantos closed this Feb 13, 2026
@jfsantos
Copy link
Contributor Author

Closed in favour of #227

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.

3 participants