177 lines
7.9 KiB
Markdown
177 lines
7.9 KiB
Markdown
# 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.
|