Repository navigation
[diskann-benchmark] Add type reflection to enable documentation of benchmark inputs. - #1459
Mark Hildebrand (hildebrandmw) wants to merge 43 commits into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Recursive derive bounds, input compatibility, and output redirection issues remain unresolved.
3 open issues
What changed in this PR
Adds runtime type reflection so benchmark users can inspect input fields, enum choices, and documentation through the CLI.
Changes:
- Introduces the
Reflectderive macro, type metadata, rendering, and transactional registration. - Adds
type-infoand extends input examples with documentation. - Updates benchmark inputs and adds compile-time and CLI regression coverage.
| File | Description |
|---|---|
| diskann-macros/tests/ui/pass/supported.rs | Tests supported derive attributes. |
| diskann-macros/tests/ui/fail/unsupported_union.stderr | Records union rejection diagnostic. |
| diskann-macros/tests/ui/fail/unsupported_union.rs | Tests unsupported unions. |
| diskann-macros/tests/ui/fail/tagging.stderr | Records tagging diagnostics. |
| diskann-macros/tests/ui/fail/tagging.rs | Tests invalid tagging combinations. |
| diskann-macros/tests/ui/fail/serde_attributes.stderr | Records Serde attribute diagnostics. |
| diskann-macros/tests/ui/fail/serde_attributes.rs | Tests unsupported Serde attributes. |
| diskann-macros/tests/ui/fail/reflect_attributes.stderr | Records reflection attribute diagnostics. |
| diskann-macros/tests/ui/fail/reflect_attributes.rs | Tests invalid reflection attributes. |
| diskann-macros/tests/ui/fail/name_collisions.stderr | Records name-collision diagnostics. |
| diskann-macros/tests/ui/fail/name_collisions.rs | Tests renamed field and variant collisions. |
| diskann-macros/tests/compile.rs | Runs compile-pass and compile-fail fixtures. |
| diskann-macros/src/reflect/mod.rs | Connects parsing to macro expansion. |
| diskann-macros/src/reflect/attributes.rs | Parses documentation and supported attributes. |
| diskann-macros/src/lib.rs | Exposes the derive macro. |
| diskann-macros/Cargo.toml | Defines the procedural macro crate. |
| diskann-inmem/integration/support/tolerance.rs | Reflects empty tolerance inputs. |
| diskann-inmem/integration/support/datatype.rs | Reflects integration data types. |
| diskann-inmem/integration/store/simple.rs | Reflects simple-store inputs. |
| diskann-inmem/integration/store/mod.rs | Reflects shared store setup. |
| diskann-inmem/integration/store/intrusive.rs | Reflects intrusive-store inputs. |
| diskann-inmem/integration/store/checked.rs | Reflects checked-store inputs. |
| diskann-inmem/integration/index/runner.rs | Reflects index inputs and adopts required options. |
| diskann-benchmark/src/utils/mod.rs | Reflects similarity measures. |
| diskann-benchmark/src/multi_vector/driver.rs | Reflects multi-vector tolerances. |
| diskann-benchmark/src/main.rs | Adds registration timing output. |
| diskann-benchmark/src/inputs/multi_vector.rs | Reflects multi-vector configuration. |
| diskann-benchmark/src/inputs/graph_index.rs | Reflects graph inputs and simplifies serialization. |
| diskann-benchmark/src/inputs/flat.rs | Reflects flat-search inputs. |
| diskann-benchmark/src/inputs/filters.rs | Reflects filter inputs and removes a default. |
| diskann-benchmark/src/inputs/exhaustive.rs | Reflects exhaustive-search configuration. |
| diskann-benchmark/src/inputs/disk.rs | Adds a disk reflection placeholder. |
| diskann-benchmark/src/inputs/bftree.rs | Reflects BfTree inputs and adopts required options. |
| diskann-benchmark/src/index/inmem2.rs | Reflects inputs and changes quantization optionality. |
| diskann-benchmark/src/index/inmem/scalar.rs | Adapts start-point strategy access. |
| diskann-benchmark/src/index/inmem/product.rs | Adapts start-point strategy access. |
| diskann-benchmark/src/index/bftree/streaming_utils.rs | Uses the resolved ground-truth path directly. |
| diskann-benchmark/src/index/benchmarks.rs | Adapts graph input consumers. |
| diskann-benchmark/src/disk_index/benchmarks.rs | Reflects disk tolerances. |
| diskann-benchmark/example/graph-index-bftree-stream.json | Supplies the consolidation threshold. |
| diskann-benchmark-simd/src/lib.rs | Reflects and documents SIMD inputs. |
| diskann-benchmark-runner/tests/rendered_reflections.txt | Adds rendering baselines. |
| diskann-benchmark-runner/tests/benchmark/type-info-missing/stdout.txt | Records unknown-type output. |
| diskann-benchmark-runner/tests/benchmark/type-info-missing/stdin.txt | Exercises unknown-type lookup. |
| diskann-benchmark-runner/tests/benchmark/type-info-list/stdout.txt | Records registered type names. |
| diskann-benchmark-runner/tests/benchmark/type-info-list/stdin.txt | Exercises type listing. |
| diskann-benchmark-runner/tests/benchmark/type-info-list-all-features/stdout.txt | Records feature-enabled type names. |
| diskann-benchmark-runner/tests/benchmark/type-info-list-all-features/stdin.txt | Exercises feature-enabled listing. |
| diskann-benchmark-runner/tests/benchmark/type-info-list-all-features/features.txt | Enables fixture features. |
| diskann-benchmark-runner/tests/benchmark/type-info-describe/stdout.txt | Records type documentation output. |
| diskann-benchmark-runner/tests/benchmark/type-info-describe/stdin.txt | Exercises named-type documentation. |
| diskann-benchmark-runner/tests/benchmark/test-3/stdout.txt | Updates input documentation baseline. |
| diskann-benchmark-runner/tests/benchmark/test-2/stdout.txt | Updates optional-field documentation baseline. |
| diskann-benchmark-runner/src/ux.rs | Shares baseline-test helpers. |
| diskann-benchmark-runner/src/utils/required.rs | Adds required nullable values and tests. |
| diskann-benchmark-runner/src/utils/num.rs | Reflects constrained numeric values. |
| diskann-benchmark-runner/src/utils/mod.rs | Exports RequiredOption. |
| diskann-benchmark-runner/src/utils/datatype.rs | Reflects shared data types. |
| diskann-benchmark-runner/src/test/typed.rs | Reflects and documents typed fixtures. |
| diskann-benchmark-runner/src/test/gated.rs | Reflects feature-gated fixtures. |
| diskann-benchmark-runner/src/test/dim.rs | Reflects dimension fixtures. |
| diskann-benchmark-runner/src/registry.rs | Registers input and tolerance type metadata. |
| diskann-benchmark-runner/src/reflect/tree.rs | Defines reflection metadata structures. |
| diskann-benchmark-runner/src/reflect/test/unit.rs | Tests derived metadata. |
| diskann-benchmark-runner/src/reflect/test/mod.rs | Connects reflection test modules. |
| diskann-benchmark-runner/src/reflect/render.rs | Renders type documentation with baselines. |
| diskann-benchmark-runner/src/reflect/mod.rs | Defines reflection traits and traversal. |
| diskann-benchmark-runner/src/lib.rs | Exports reflection APIs. |
| diskann-benchmark-runner/src/internal/typemap.rs | Adds transactional type registration. |
| diskann-benchmark-runner/src/internal/mod.rs | Connects the type map. |
| diskann-benchmark-runner/src/input.rs | Requires reflection for raw inputs. |
| diskann-benchmark-runner/src/files.rs | Reflects input file paths. |
| diskann-benchmark-runner/src/app.rs | Adds type documentation commands. |
| diskann-benchmark-runner/dev/main.rs | Clarifies feature argument parsing. |
| diskann-benchmark-runner/Cargo.toml | Adds reflection and testing dependencies. |
| Cargo.toml | Registers the macro workspace crate. |
| Cargo.lock | Records dependency changes. |
Review effort: Balanced
| for ty in self.types.iter() { | ||
| predicates.push(parse_quote!(#ty: #path::Reflect)); |
| pub(super) build: BuildParams, | ||
| pub(super) search: KnnSearch, | ||
| pub(super) quantization: Option<Quantization>, | ||
| pub(super) quantization: RequiredOption<Quantization>, |
| filters::register_benchmarks(&mut registry)?; | ||
| multi_vector::register_benchmarks(&mut registry)?; | ||
|
|
||
| println!("registration took {}us", now.elapsed().as_micros()); |
Codecov Report❌ Patch coverage is ❌ Your patch status has failed because the patch coverage (88.09%) is below the target coverage (90.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #1459 +/- ##
==========================================
- Coverage 92.10% 92.04% -0.07%
==========================================
Files 590 601 +11
Lines 121495 123884 +2389
==========================================
+ Hits 111905 114028 +2123
- Misses 9590 9856 +266
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
| /// | ||
| /// * `#[serde(rename = "name")]`: Describe with the given name instead of its Rust name. | ||
| /// | ||
| /// * `#[serde(rename_all = "...)]`: Rename all fiels of this struct variant with the given |
There was a problem hiding this comment.
nit: typo p
| /// | ||
| /// * `#[serde(rename = "name")]`: Describe with the given name instead of its Rust name. | ||
| /// | ||
| /// ## Reflect Attributes |
There was a problem hiding this comment.
Just for understanding - when would you want to use Reflection attributes over Serde attributes?
Aditya Krishnan (arkrishn94)
left a comment
There was a problem hiding this comment.
Thanks Mark, this is quite a feature. I have a super high level question - for folks using and contributing to benchmark for the first time, how can we make this feature discoverable? I'm trying to make sure that folks who aren't deeply familiar with the intricacies of diskann-benchmark can add define them correctly without much external review/feedback.


This has been a bugbear of mine for quite some time. It's been sitting mostly done for long enough that maybe it's just time to either get it in, or abandon it all together.
This implements type inspection for inputs in
diskann-benchmark. Currently, we can get example JSON input schemas using (for example)While this serves as a starting point, it does not document what the various fields are supposed to do. This is particularly annoying for enums, where the accepted values or syntaxes have to basically be reverse engineered from the source code.
Running the above command with this PR will also bring up an imperfect but IMO acceptable list of the high-level types. If there were rust-doc strings on these fields, they would also be rendered, providing an easy way to document fields and types for benchmark users.
A full list of registered types (again, far from perfectly rendered, but adequate) can be retrieved with
Documentation for specific types can be retrieved by naming it:
The heavy lifting here is done with a procedural macro
Reflect(implemented here indiskann-macros). Most code can be updated by derivingReflectand calling it a day.Please see the docstring on
Reflectindiskann-benchmark-runner/src/reflect/mod.rsfor the trait definition and supported attributes.Only
serde(rename_all = "..."),serde(rename = "..."), and the enum relatedserde(tag = "...")/serde(content = "...")are supported. Attributes likeserde(default),serde(try_from = "...")are not supported because benchmark inputs should be as simple as possible. Fancy constructs like this are a sign that the input DTO is too complicated and should be simplified.Why is this PR so damn big?
That is an excellent question. A large amount of the complexity in this PR comes from needing to support
serde's annoying corner cases and testing the infrastructure. The filediskann-macros/src/reflect/repr.rshas a good summary of the weirdness that arises fromserdesupport.Other than that, there is a chicken and egg problem. The proc-macro is useless without
diskann-benchamrk-runner/src/reflect/tree.rs. The constructs in said file are really annoying to build properly without the proc-macro. Further, the AST structures intree.rsneed theReflecttrait (viaReflection) to work. Finally, all of this needs to be wired in through the benchmark registry and applied to our existing inputs, otherwise it's a bunch of code doing nothing.Suggested Reviewing Order
I would recommend starting with
diskann_benchmark_runner/src/reflect/mod.rs. This:Reflecttrait that is used for type inspection.Reflectiontype for working with type-erasedReflectimplementations.visit_withfor exploring transitively reachable types from a givenReflection.Reflectfor a number of primitive types.The implementation of the
Reflectiontype is as a reference to a vtable, initialized by a concreteReflectimplementation.Next, I would look at
diskann-benchmark_runner/src/reflect/tree.rs. This introduces theTypeenum that is used to represent rust types we wish to reflect over. You will observe that these types are an absolute pain to implement manually. Must more so to keep in-sync with an actual type. That's where the procedural macro comes in.Switch to
diskann-macros/src/reflect/mod.rs. This is, for all intents and purposes, the entry point for the macro. All it does is try to parse aDeriveInputand emit the corresponding code.diskann-macros/src/reflect/repr.rsintroduces an intermediate representation used to parse input syntax, process the supported attributes, legalize the attributes, and finally emit the builders for theTypes. The structure of this is very similar totree.rs. You can explorediskann-macros/src/reflect/attributes.rsat your leisure. This is what handles parsing the supportedserdeandreflectattributes and does some level of sanity checking on these.diskann-macros/tests/*includes compile tests for the various error cases related to incorrect or otherwise illegal attributes. These validate the error messages associated with these cases, helping ensure that we provide somewhat actionable errrors.Back in
diskann-benchmark-runnerI suggest looking atsrc/reflect/test/unit.rsfor unit tests related to successful applications of theReflectmacro. It's neighborserde.rsis kind of a mess and mainly tries to ensure that the representation of theTypeemitted byReflectagrees with howserdehandles the same data structure.Next,
src/internal/typemap.rsis a registry for associatingReflect::type_name()with the associatedReflectiontype. The biggest service this provides is to support transactional registrations. The functionTypeMap::registertakes a list ofReflections and registers all transitively reachable types. So for example, registeringFoowhereFoois defined aswill also attempt to register
usizeandString. If two types have the same typename but different type-ids, this is an error and all the types registered during the call are rolled back and removed. Additionally, the returnedTransactionwill roll-back a registration if dropped without callingTransaction::complete. This is useful for staging changes inRegistry.Finally,
src/registry.rswires upTypeMapand automatically registers reachable types for inputs.src/app.rsmakes the type map reachable from the CLI.In the rest of
diskann-benchmark-runner:src/utils/required.rsaddsRequiredOption. This behaves likeOptionbut with a critical difference for deserialization. Whereserdetreats a missing fields whose value isOption<T>asNone,RequiredOptionrequires an explicitnullin the input. This is meant to handle the lack of macro support forserde(deserialize_with = ...), which we currently use to achieve the same effect.Reflecttodiskann-benchmark-runnerexported types.The changes in
diskann-benchmarkare all about applying theReflecttrait. Many cases simply require derivingReflectand usingRequiredOption. There are some additional cases that deserve special attention:diskann-benchamrk/src/inputs/graph_index.rs:StartPointStrategy. This currently usesserde(remote = "...")to deserialize to the type indiskann. This PR changes this to be a DTO for thediskanntype.diskann-benchamrk/src/inputs/graph_index.rs:DynamicRunbookParams. This type had aresolved_gt_directoryfield that was lazily initialized and not serialized. This PR reuses thegt_directoryfields (behaving likeInputFile) and stores the resolve path there directly.diskann-benchmark/src/inputs/disk.rs: TheDiskIndexOperationdoes a bunch of crazy things involving serde features, conditionally compiled fields, and deserializing remote types. ThisReflectimplementation is left as a stub.