Request more efficient configuration loading for PromptKit

This commit is contained in:
2026-10-08 23:50:02 +00:00
parent a22aaa5dfa
commit d53021ea2c

View File

@@ -0,0 +1,176 @@
# PromptKit Request: More Efficient Configuration Construction
Status: Submitted for ConfigKit's consideration; no upstream design or delivery
commitment is assumed.
Date: 2026-10-08.
## Consumer Need
PromptKit uses ConfigKit to assemble configuration for an importable Go LLM
prompting library. Catalog and consumer sources supply backend, profile, and
versioned prompt definitions. ConfigKit owns source composition, inheritance,
typed decoding, resolution, and final configuration publication. PromptKit
interprets the final configuration and captures execution-ready definitions.
We request consideration of lower CPU and allocation costs for this existing
loading workflow, particularly with hundreds of small definitions. The goal is
to benefit consumers through the existing public API, while preserving the
configuration semantics and inspection capabilities they rely on. Acceptance
and the implementation approach remain ConfigKit decisions.
## Why This Matters
PromptKit ordinarily constructs an engine once and reuses its captured
configuration across many calls. Construction is therefore startup work, not a
cost repeated for each prompt. Current indexed prompt/profile selection is
already fast, and this request does not concern upstream inference latency.
Nevertheless, cumulative allocation during construction can matter for large
catalogs, applications creating multiple engines, and repeated configuration
loads. Reducing that work can improve startup and reduce garbage-collection
pressure without requiring every downstream library to own a specialized
configuration loader.
PromptKit retains ConfigKit's inspection data for explicit operator APIs.
Ordinary prompt execution uses captured definitions rather than analyzing
configuration provenance. Inspection remains useful even though it is not on
the normal execution path.
## Observed Workload
PromptKit's committed `internal/configuration/benchmark_test.go` contains
`BenchmarkConfigurationConstruction`. Its `independent` fixture has:
- 128 independently named profiles, each with an endpoint and model;
- 128 independently named prompts, each with version `1`;
- one message per prompt, containing the small template `Hello {{.name}}`;
- a default-profile reference and a text/no-validation output declaration per
prompt; and
- one in-memory mapping source, with no underlays, imports, inheritance,
external file reads, credentials, or model calls.
The benchmark reports 256 input and effective entries: 128 profiles plus 128
prompt versions. This is a definition count, not a count of all configuration
fields or metadata paths.
Each timed iteration calls PromptKit's real configuration adapter. That adapter
uses `ConfigKit.LoadLayered` with resolution and validation callbacks, then
captures prompt content, compiled templates, hashes, and execution targets.
Input fixture construction occurs outside the timed iteration. Consequently,
the measurements include downstream adapter work and are not a standalone
ConfigKit benchmark.
Other committed fixtures cover file-backed messages, shared-base profile
inheritance, and four-tier layering. They provide additional downstream cases
for assessing proposed improvements; the principal evidence below uses the
simple independent fixture.
## Measurements And Reproduction
Original PromptKit review baseline:
`de6443f3aef4a66169e323e9f9a5f41594fbface`.
The request preparation repeated the benchmark at PromptKit revision
`eb830bb61346672f0f6c5bce6119b76ccc6d61ac`. The benchmark, configuration adapter,
and `go.mod` are unchanged between these revisions.
Reproduction environment:
- ConfigKit: `gitea.maximumdirect.net/eric/ConfigKit v0.5.0`.
- Toolchain: `go1.26.8`, `linux/amd64`.
- Processor: Intel Core i5-7260U CPU at 2.20 GHz.
- Benchmark process parallelism: 4, as reported by the benchmark suffix.
- Workspace mode disabled; installed toolchain and already-acquired module
dependencies used without downloads.
From a PromptKit checkout at either recorded revision:
```sh
GOTOOLCHAIN=local GOWORK=off GOPROXY=off \
go test -mod=readonly -run '^$' \
-bench '^BenchmarkConfigurationConstruction/independent$' \
-benchmem -benchtime=500ms -count=5 ./internal/configuration
```
Repeated results:
| Run | Time per construction (ms) | Allocated bytes per construction | Allocations per construction |
| --- | ---: | ---: | ---: |
| 1 | 71.052 | 50,128,254 | 538,582 |
| 2 | 67.740 | 50,139,198 | 538,539 |
| 3 | 66.353 | 50,174,030 | 538,541 |
| 4 | 67.170 | 50,159,959 | 538,542 |
| 5 | 68.209 | 50,167,214 | 538,541 |
These agree with the original review's approximate 69 ms, 50 MB, and 539,000
allocations. They measure cumulative allocation per operation, not retained
heap or a memory leak. Timing depends on the machine and runtime environment.
### Allocation And CPU Profile Observations
A separate two-second profiled run reproduced approximately 68.7 ms,
50.1 MB, and 538,553 allocations per construction. To generate comparable
profiles from the same PromptKit checkout, placing generated artifacts outside
the repository:
```sh
profile_dir=$(mktemp -d)
GOTOOLCHAIN=local GOWORK=off GOPROXY=off \
go test -mod=readonly -run '^$' \
-bench '^BenchmarkConfigurationConstruction/independent$' \
-benchtime=2s -cpuprofile="$profile_dir/config.cpu" \
-memprofile="$profile_dir/config.mem" -o "$profile_dir/config.test" \
./internal/configuration
GOTOOLCHAIN=local go tool pprof -top -alloc_space \
"$profile_dir/config.test" "$profile_dir/config.mem"
GOTOOLCHAIN=local go tool pprof -top \
"$profile_dir/config.test" "$profile_dir/config.cpu"
```
The sampled allocation profile prominently attributes work to ConfigKit's
path and metadata machinery. Selected flat allocation shares were:
- `clonePathSegments`: approximately 29.9%.
- `ParsePath`: approximately 4.7%.
- `collectFieldInfo`: approximately 4.3%.
- `collectOrigins`: approximately 3.7%.
- `effectivePathIndex.func1`: approximately 3.7%.
The CPU profile also showed substantial allocation/garbage-collection work and
path formatting (`Path.String`, approximately 17.4% cumulative CPU samples).
These symbol names are profiling evidence, not recommendations for ConfigKit's
internal implementation. Flat allocation and cumulative CPU shares describe
different measures and should not be added together.
The profile supports investigating ConfigKit as a major contributor; it does
not attribute every byte or millisecond of this assembled benchmark to
ConfigKit. A single fixture size also does not establish an asymptotic scaling
defect. Repeated measurements across representative sizes would help assess
scaling and any proposed improvement.
## Requested Outcome
Consider reducing avoidable construction-time work and temporary allocations
for this workload while retaining the capabilities defined by ConfigKit's
[configuration contract](../config.md), including:
- layered sources and sibling inheritance;
- typed decoding and recorded application resolution;
- effective presence, explicit null, and resource/source context;
- provenance, history, and safe operator inspection; and
- established result/query ownership and secret-handling behavior.
An improvement through the existing API would be preferable for PromptKit.
The request does not prescribe a cache, data structure, new public API, or
alternative metadata mode. Any optional API change can be assessed against
the downstream need if ConfigKit determines that one is justified.
Useful evidence of success would be repeatable reductions in CPU or allocation
cost for the recorded fixture and representative related workloads, with
existing semantics intact. No machine-specific threshold, fixed allocation
budget, mandatory complexity guarantee, or additional release gate is requested.
This request can be considered independently of PromptKit's local runtime
optimizations. PromptKit will continue to use ConfigKit's final configuration
handoff and can evaluate adoption of an upstream improvement when available.