Prefix Cache Dataset Generation, Benchmarking, and Hit-Rate Analysisο
Overviewο
The AISBench Prefix Cache plugin generates datasets with controlled shared prefixes, calculates the theoretical Prefix Cache hit rate, and then uses AISBench and vLLM to collect the actual hit rate. It evaluates how input lengths, shared-prefix ratios, Prefix Groups, request ordering, and multiple DP ranks behind one endpoint affect cache hits.
The plugin provides five commands:
inspect: preview the scenario, reachable range, and length distributions;prepare: generate formal requests, a Manifest, and theoretical analysis;validate: detect modified, truncated, reordered, or inconsistent artifacts;run: probe and reset the service, warm every group on every DP rank, and run the formal AISBench benchmark;analyze: recompute actual hit rates offline from two Prometheus snapshots.
inspect, prepare, and validate are offline. Only run connects to vLLM. One HTTP endpoint with one or more internal DP ranks is supported; multiple independent inference-server instances are not.
Prerequisitesο
Python 3.10 or later.
An AISBench checkout whose dependencies can be imported.
The same tokenizer as the target vLLM server. A mismatch changes token counts, Block boundaries, and the theoretical hit rate.
A GSM8K JSONL corpus. Every non-empty line must be a JSON object containing the text field selected by
corpus.field, which defaults toquestion.The correct Prefix Cache Block size.
tokenizer.block_sizemust match the target server.Service capabilities for online run:
/v1/completions,/metrics, optionally/reset_prefix_cache, plusX-data-parallel-rankrouting and per-DPenginemetric labels for multi-DP.
Installationο
The following commands assume that the current directory is the AISBench repository root:
python -m venv .venv
source .venv/bin/activate
python -m pip install -e .
python -m pip install -e ./plugins/prefix_cache
ais-bench-prefix-cache --help
The editable (-e) installs normally make source changes available without reinstalling the packages.
Quick Startο
Copy the example Scenario:
cp ./plugins/prefix_cache/config_examples/scenario.example.json ./scenario.json
At minimum, review tokenizer.path, tokenizer.block_size, and corpus.path. When modeling cold multi-DP routing or producing a warmup plan, also set service.dp_size to the number of DP ranks on the target server.
Before an online benchmark, also review the service URLs, service.model, and aisbench.config.
A minimal example is shown below:
{
"schema_version": "1.0",
"run": {
"run_id": "gsm8k-prefix-cache-60",
"random_seed": 42,
"output_dir": "./outputs/gsm8k-prefix-cache-60"
},
"tokenizer": {
"path": "/path/to/tokenizer",
"block_size": 16
},
"corpus": {
"path": "./GSM8K.jsonl",
"field": "question",
"selection": {"mode": "random"}
},
"requests": {
"count": 100,
"input_length": {"mode": "fixed", "value": 1024},
"output_length": {"mode": "fixed", "value": 32}
},
"prefix_cache": {
"mode": "warmup",
"target_hit_rate": 0.6,
"seed_blocks": 1,
"groups": {"count": 1, "assignment": {"mode": "uniform"}},
"order": {"strategy": "interleave"}
},
"service": {"dp_size": 2}
}
Run the commands in order:
ais-bench-prefix-cache inspect --scenario ./scenario.json
ais-bench-prefix-cache prepare --scenario ./scenario.json
ais-bench-prefix-cache validate --manifest \
./outputs/gsm8k-prefix-cache-60_<timestamp>/result/gsm8k-prefix-cache-60_<timestamp>.manifest.json
ais-bench-prefix-cache run --scenario ./scenario.json
Formal AISBench requests use vLLM SSE streaming by default (aisbench.model.stream=true). Request-start, first-chunk, and subsequent chunk timestamps are used to produce TTFT, TPOT, ITL, E2EL, and throughput metrics. Prefix Cache hit rate can still be measured when this option is false, but complete chunk-based TTFT, TPOT, and ITL metrics are unavailable. Precheck and plugin warmup use separate non-streaming requests before the baseline and are excluded from formal performance statistics.
With saved Prometheus snapshots, recompute without contacting vLLM:
ais-bench-prefix-cache analyze \
--manifest <manifest-path> \
--baseline ./baseline.prom \
--after ./after.prom
run: Execute an Online Prefix Cache Benchmarkο
ais-bench-prefix-cache run --scenario ./scenario.json
ais-bench-prefix-cache run --scenario ./scenario.json --config ./my_prefix_cache_perf.py
--scenario supplies dataset construction, service, validation, and AISBench settings. The optional --config overrides only the AISBench Python template for this invocation and does not modify the Scenario. run reuses a matching inspect/prepared Manifest and automatically runs prepare when the timestamped task directory does not yet contain formal artifacts.
The complete online sequence is:
flowchart LR
S[Load Scenario] --> P[Reuse or auto-prepare]
P --> V[Validate artifacts]
V --> C[Precheck every DP]
C --> R[Reset Prefix Cache]
R --> W{Warmup mode?}
W -->|Yes| U[Warm every Group Γ DP]
W -->|No| B[Capture baseline]
U --> B
B --> G[Render temporary AISBench config]
G --> F[AISBench perf formal requests]
F --> K[Periodic KV sampling]
K --> A[Capture after]
A --> D[after - baseline]
D --> O[Update analysis.json]
The phase boundaries are:
prechecksends a probe to every DP rank and verifies that query, hit, and KV metrics can be parsed. Multi-DP requests useX-data-parallel-rankfor deterministic routing.resetcallsservice.reset_url. If it is missing or fails, execution continues with a warning only whenservice.assume_empty_cache=true.In warmup mode, every
Prefix Group Γ DP rankis warmed according to the Manifest plan. The baseline is captured after plugin warmup, so cumulative counters produced by probes and warmup are removed by subtraction.The formal phase renders
aisbench.dataset,aisbench.model, artifact paths, and service settings into a temporary Python configuration and starts AISBench inperfmode. KV Cache gauge values are sampled everyservice.poll_interval_seconds; set it to0to disable periodic sampling.After AISBench exits successfully, the plugin captures the after snapshot, computes per-DP and global queries, hits, and hit rate from
after - baseline, and writes theory-versus-actual differences back to analysis.
Plugin warmup and AISBenchβs own --num-warmups are independent mechanisms. To ensure that only formal requests occur after the baseline, configure:
"aisbench": {
"extra_args": ["--num-warmups", "0"]
}
Prefix Cache phase logs are written only to log/<run_id>.run.log. The AISBench child process inherits stdout/stderr, so its progress and performance output remain visible in the terminal. On success, the final stdout object is the complete analysis JSON. A non-zero AISBench exit code, missing service capability, or artifact validation failure makes run fail.
analyze: Recompute from Prometheus Snapshots Offlineο
ais-bench-prefix-cache analyze \
--manifest <manifest-path> \
--baseline ./baseline.prom \
--after ./after.prom
Use analyze when pre- and post-benchmark /metrics text has already been saved and the result must be recomputed with the current parser or independently checked. It does not connect to vLLM, send requests, or launch AISBench.
--manifest: a prepared Manifest. The command first validates full/requests row counts, order, and SHA-256, then readsdp_size,engine_label_map, and warning thresholds fromeffective_config.service.--baseline: complete Prometheus text captured before the formal measurement window.--after: complete Prometheus text captured after the window. Queries and hits are cumulative counters, so their values must not be lower than the baseline.
The command parses both snapshots, subtracts per-DP queries and hits, aggregates actual.global_hit_rate, compares it with theoretical_hit_rate, and emits ACTUAL_DEVIATION when required. It writes status="analyzed" to the analysis artifact indexed by the Manifest; runtime contains only metrics_baseline and metrics_after. Offline snapshots have no formal-run sampling sequence, so runtime.kv_cache_polling and run-time KV averages/peaks are not produced. Prometheus metrics also have no Prefix Group label, so actual statistics are per-DP and global, while group-level statistics remain theoretical.
The current CLI uses the same log/<run_id>.validate.log filename for analyze and validate; the later command recreates that file. Stdout contains the updated complete analysis JSON. Target or actual deviations produce only PASS_WITH_WARNING and do not change an otherwise successful exit code.
How It Worksο
flowchart LR
S[Scenario] --> I[inspect preview]
I --> P[prepare prompts]
P --> G[Shared prefix]
P --> U[Globally unique seed]
P --> N[Natural GSM8K suffix]
G --> T[Order-aware watermark simulation]
U --> T
N --> T
T --> A[full / requests / Manifest / analysis]
A --> V[validate artifacts]
Every formal request consists of three regions:
shared prefix + globally unique seed + natural GSM8K suffix
The shared prefix is aligned to
block_sizeand is the main source of theoretical hits.The seed length is
seed_blocks Γ block_size. It is globally unique for every request, preventing accidental sharing beyond the intended prefix.The natural suffix is selected, concatenated, and truncated from GSM8K questions so the non-shared region remains natural-language content.
The plugin solves for the shared-prefix length of every request from the target global hit rate, then simulates cache watermarks in final request order. The nearest reachable final hit-token total remains the hard constraint. Among schedules with the same final total, warmup balances prefixes across cumulative input, while cold mode uses Prefix Group/DP-lane watermarks to minimize cumulative-rate overshoot and decline before tracking the target. A strictly monotonic path can be impossible when a late lane has a compulsory first miss or insufficient capacity, but the solver no longer defaults to front-loading hits and compensating with short tail prefixes.
Core Scenario Configurationο
See the complete Scenario field reference.
Complete Field Indexο
Configuration path |
Brief description |
|---|---|
|
Scenario configuration format version. |
|
Task identity, randomness, and output controls. |
|
Task name; a timestamp is appended at execution. |
|
Global seed for generation and random selection. |
|
Base directory for Prefix Cache artifacts. |
|
Whether existing formal artifacts may be overwritten. |
|
Tokenizer loading and Block configuration. |
|
Tokenizer model or directory path. |
|
Prefix Cache Block size in tokens. |
|
Tokenizer repository revision; |
|
Whether to trust custom Tokenizer code. |
|
Natural-suffix corpus configuration. |
|
Path to the GSM8K JSONL file. |
|
JSONL field containing question text. |
|
GSM8K sample-selection rule. |
|
Selection mode: random, index, hash, or mixed. |
|
Index or hash list for a non-mixed mode. |
|
Zero-based index list for mixed mode. |
|
Question SHA-256 list for mixed mode. |
|
Formal request count and length distributions. |
|
Total number of formal requests. |
|
Input-token length generation rule. |
|
Input-length mode. |
|
Fixed length used by fixed mode. |
|
Length list used by explicit mode. |
|
Range list used by range mode. |
|
Lower bound of one sampling range. |
|
Upper bound of one sampling range. |
|
Number of requests generated from one range. |
|
Lower bound of the truncated normal distribution. |
|
Upper bound of the truncated normal distribution. |
|
Mean of the truncated normal distribution. |
|
Standard deviation of the truncated normal distribution. |
|
Length-file path used by csv mode. |
|
Maximum output-token length generation rule. |
|
Output-length mode. |
|
Fixed length used by fixed mode. |
|
Lower bound for uniform/truncated-normal mode. |
|
Upper bound for uniform/truncated-normal mode. |
|
Mean of the truncated normal distribution. |
|
Standard deviation of the truncated normal distribution. |
|
Length-file path used by csv mode. |
|
Field controls for the compact request file. |
|
Optional output-length key; |
|
Prefix Cache data and runtime strategy. |
|
Cache mode: |
|
Requested global Prefix Cache hit rate. |
|
Number of Blocks used by each unique seed. |
|
Minimum non-shared tokens retained per request. |
|
Prefix Group count, assignment, and overrides. |
|
Total number of Prefix Groups. |
|
Rule assigning requests to Groups. |
|
Group-assignment mode. |
|
Hotspot exponent for Zipf assignment. |
|
Relative Group weights for weights mode. |
|
Optional per-Group overrides. |
|
Input-length rule for one Group. |
|
Output-length rule for one Group. |
|
Corpus-selection rule for one Group. |
|
Formal request ordering rule. |
|
Sequential, interleaved, shuffled, or length-ascending order. |
|
Online inference service and metric collection. |
|
vLLM inference endpoint. |
|
Prometheus metrics endpoint. |
|
Prefix Cache reset endpoint. |
|
Service model name sent in the request body. |
|
Number of DP ranks inside one instance. |
|
Assume an empty cache when reset is unavailable. |
|
Mapping from Prometheus engine label to DP rank. |
|
Timeout for probes, warmup, and metrics requests. |
|
Service credential; never persisted in plaintext. |
|
KV metric sampling interval during the formal run. |
|
Result-deviation warning thresholds. |
|
Warning threshold for theory-versus-target deviation. |
|
Warning threshold for actual-versus-theory deviation. |
|
AISBench formal benchmark launch configuration. |
|
Path to the AISBench Python configuration template. |
|
Base directory for AISBench results. |
|
Arguments appended to the AISBench command. |
|
Dataset reader and Prompt mapping configuration. |
|
Dataset display name; |
|
Input columns used by the Dataset reader. |
|
Reference-answer column used by the reader. |
|
Prompt template for formal requests. |
|
Role name assigned to predictions. |
|
AISBench API Model configuration. |
|
Model display name; |
|
Model attribute; currently must be |
|
Whether to use SSE streaming responses. |
|
Model-level fallback maximum output length. |
|
Number of retries after API failures. |
|
Base AISBench API concurrency. |
|
Generation arguments forwarded to vLLM. |
Unknown Scenario fields are rejected. Offline calculations use service.dp_size; run consumes the service URLs, model, reset/empty-cache policy, metric mapping, timeout, API key, and the complete aisbench section.
Input and Output Lengthsο
requests.input_length supports:
fixed: one fixed length;explicit: an explicit list of lengths;range: sampling from one or more inclusive ranges;truncated_normal: a bounded normal distribution;csv: values frominput_prompt_tokens,content_tokens, orinput_tokens.
requests.output_length supports:
fixed;uniform;truncated_normal;csv, using anoutput_tokenscolumn.
All lengths must be positive integers. Global explicit lists, range counts, and CSV row counts must equal requests.count. A group override must instead produce exactly the number of requests assigned to that group.
GSM8K Selectionο
corpus.selection.mode supports:
random: deterministic shuffling based onrun.random_seed;indices: zero-based GSM8K line numbers;question_sha256: SHA-256 of normalized question text;mixed: appendindicesfirst andquestion_sha256second.
When fewer records are specified than required, the selected sequence is reused cyclically. Both mixed-mode lists cannot be empty.
Prefix Groupsο
prefix_cache.groups.assignment.mode supports:
uniform: distribute requests as evenly as possible;zipf: useexponentto control hotspot concentration;weights: provide relative group weights inweights.
Each Prefix Group has its own canonical prefix, cache watermark, and theoretical statistics. groups.overrides.group-N can override input lengths, output lengths, and corpus selection for one group.
requests.jsonl Output Fieldο
"output": {"output_key": null}
With the default null, each row contains only question and answer. Set the value to "max_tokens" or "output_tokens" to append that third key; both carry the internal maximum output-token value. full.jsonl.max_tokens is always retained, and AISBench reads the generation length from full, so omitting the public field does not affect execution.
Request Orderingο
prefix_cache.order.strategy supports:
sequential;within_group_shuffle;interleave;global_shuffle;input_len_asc.
The theoretical hit rate is always recomputed using the final reordered request sequence. To model an unwarmed cache growing from short to long requests, combine prefix_cache.mode="cold" with order.strategy="input_len_asc". Prepare writes requests in ascending input-length order within each group, and at run time LaneSequencer releases the next request on a (group_id, dp_rank) lane only after its predecessor finishes. Independent Group/DP caches may still run concurrently.
Cold and Warmup Modesο
coldο
Every
(group_id, dp_rank)lane starts from an empty cache watermark.Requests in a group are routed round-robin to DP ranks in group occurrence order.
full.jsonlrecordsdp_rankandlane_sequence.Each lane is simulated independently and then aggregated using token weighting.
warmupο
One warmup item is generated for every
Prefix Group Γ DP rank.The plan is written to
warmup.planin the Manifest.Warmup requests are not written to
requests.jsonland are excluded from the formal request count and theoretical denominator.prepareonly generates the plan;runsends every item to its designatedPrefix Group Γ DP rankbefore the formal baseline.
Theoretical Hit Rate and Reachabilityο
For an independent cache lane, if the watermark before a request is watermark and the request has shared_prefix_tokens, the theoretical hit count is:
hit_tokens = min(shared_prefix_tokens, watermark)
watermark_after = max(watermark, shared_prefix_tokens)
The global result is token weighted:
global_hit_rate = sum(theoretical_hit_tokens) / sum(actual_input_tokens)
The plugin reports:
requested_target_hit_rate: the requested Scenario target;effective_target_hit_rate: the nearest reachable target selected by the solver;theoretical_hit_rate: the value simulated in final request order;reachable_minandreachable_max: the theoretical range under current constraints;target_reachable: whether the requested target falls within that range.
Block alignment, unique seeds, natural suffixes, Prefix Groups, ordering, and cold DP lanes can all make a target unreachable.
Output Layout and Timestampsο
Timestamps use _YYYYMMDD_HHMMSS. In the recommended workflow, inspect creates the timestamp and a lightweight Manifest; prepare and run reuse the task through that Manifest:
outputs/gsm8k-prefix-cache-60_20260825_123456/
βββ log/
β βββ gsm8k-prefix-cache-60_20260825_123456.inspect.log
β βββ gsm8k-prefix-cache-60_20260825_123456.prepare.log
β βββ gsm8k-prefix-cache-60_20260825_123456.validate.log
β βββ gsm8k-prefix-cache-60_20260825_123456.run.log
βββ result/
βββ gsm8k-prefix-cache-60_20260825_123456.full.jsonl
βββ gsm8k-prefix-cache-60_20260825_123456.requests.jsonl
βββ gsm8k-prefix-cache-60_20260825_123456.manifest.json
βββ gsm8k-prefix-cache-60_20260825_123456.analysis.json
No standalone <output_dir>.inspect.json is created. inspect stores its summary in result/<timestamped_run_id>.manifest.json with status="inspected". When the Scenario SHA-256, timestamped run/output values, and status match, prepare upgrades that file in place to status="prepared"; run can then reuse it. A Scenario content change automatically makes the old Manifest ineligible.
Artifactsο
Artifact |
Purpose |
|---|---|
|
Complete audit rows: group, DP lane, input lengths, shared prefix, unique seed, GSM8K sources, theoretical watermark, and collision state. |
|
Minimal AISBench requests. Rows contain |
|
Effective configuration, input hashes, tokenizer fingerprint, length distributions, reachable ranges, groups, DP, warmup plan, and artifact hashes. |
|
Requested/effective/theoretical/actual rates, baseline/after snapshots, theoretical group statistics, theoretical/actual per-DP statistics, differences, validation state, and warnings. |
The plaintext service.api_key is not stored in the Manifest; only api_key_configured is recorded.
The fixed field index is listed below. Fields marked as optional or phase-specific appear only under the corresponding configuration or execution phase.
requests.jsonl Fieldsο
Field |
Brief description |
|---|---|
|
Complete Prompt sent to the model. |
|
Reference answer used by AISBench. |
|
Optional maximum output-token count. |
|
Optional alias for |
output.output_key may append either max_tokens or output_tokens; neither is present by default.
full.jsonl Fieldsο
Field |
Brief description |
|---|---|
|
Globally unique request identifier. |
|
Global index in final send order. |
|
Prefix Group containing the request. |
|
Occurrence index inside the Group. |
|
Target DP rank in cold mode. |
|
Sequence number within a |
|
Configured target input-token count. |
|
Actual input tokens verified by the Tokenizer. |
|
Maximum tokens allowed for this response. |
|
Number of reusable shared-prefix tokens. |
|
Number of globally unique seed tokens. |
|
Number of natural-suffix tokens. |
|
Complete Prompt composed of prefix, seed, and suffix. |
|
AISBench reference answer. |
|
Zero-based GSM8K rows used by the natural suffix. |
|
SHA-256 values of the suffix source questions. |
|
Digest of the Group canonical prefix. |
|
Digest of this requestβs unique seed. |
|
Random seed derived for this request. |
|
Simulated cache-lane watermark before the request. |
|
Tokens theoretically hit by this request. |
|
Simulated cache-lane watermark after the request. |
|
Theoretical hit rate of this request. |
|
Digest used to validate the divergence Block. |
|
Whether the divergence Block is globally unique. |
|
Prefix or seed collision-check result. |
manifest.json Top-Level Fieldsο
Field |
Brief description |
|---|---|
|
Manifest data-structure version. |
|
Plugin version that produced the artifacts. |
|
|
|
Timestamped task identifier. |
|
Path to the original Scenario file. |
|
Digest of the original Scenario file. |
|
Effective configuration after defaults are applied. |
|
Digest of the effective configuration. |
|
Digest of the GSM8K corpus file. |
|
Tokenizer identity and Block information. |
|
Request count, token total, and length summaries. |
|
Mode, hit rates, and reachability results. |
|
Independent statistics for each Prefix Group. |
|
DP count and cold-routing strategy. |
|
Group Γ DP warmup plan. |
|
Seed/divergence-block uniqueness summary. |
|
Paths, sizes, and digests of generated artifacts. |
|
Preview information in an inspect-only Manifest. |
A prepared Manifest uses all fields above except inspect. An inspect-only Manifest has status="inspected" and stores its preview under inspect.summary.
analysis.json Top-Level Fieldsο
Field |
Brief description |
|---|---|
|
Analysis data-structure version. |
|
Corresponding timestamped task identifier. |
|
|
|
Hit-rate target requested by the Scenario. |
|
Nearest reachable target chosen by the solver. |
|
Rate simulated in final request order. |
|
Absolute percentage-point gap between theory and target. |
|
Signed theory-minus-target gap in percentage points. |
|
Absolute theory-versus-target gap in percentage points. |
|
Reachability, status, and warning policy. |
|
Theoretical token, Group, and DP statistics. |
|
Warnings produced by this run. |
|
Run phases, warmup, and metric snapshots. |
|
Actual hit statistics computed from metric deltas. |
|
Absolute actual-versus-theory gap in percentage points. |
|
Signed actual-minus-theory gap in percentage points. |
|
Absolute actual-versus-theory gap in percentage points. |
runtime, actual, and the three theory_actual_* fields are appended by the run/analyze phase.
inspect stdout Fieldsο
Field |
Brief description |
|---|---|
|
Scenario task name before timestamping. |
|
|
|
Hit rate requested by the user. |
|
Nearest reachable target hit rate. |
|
Predicted theoretical hit rate. |
|
Minimum globally reachable hit rate. |
|
Maximum globally reachable hit rate. |
|
Whether the target is inside the reachable range. |
|
Reachable range of every Group. |
|
Request count for every Group. |
|
Input-length statistics and total token count. |
|
Output-length statistics and total token count. |
|
Formal request count for every DP rank. |
|
Whether online requests are sent; always false for inspect. |
|
Path to the inspect log file. |
|
Path to the inspect-only Manifest. |
See the Prefix Cache plugin README and complete Scenario reference for field types and nested semantics.
Warnings and Exit Codesο
Warning |
Condition |
|---|---|
|
The requested target is outside |
|
The absolute difference between theory and target exceeds |
|
The absolute difference between actual and theory exceeds |
These warnings only change the displayed validation state to PASS_WITH_WARNING. warning_only=true and affects_exit_code=false, so they do not change an otherwise successful exit code. Scenario, artifact, service-capability, or AISBench execution errors return a non-zero exit code.
Troubleshootingο
Why does the theoretical rate not exactly match the target?ο
Shared prefixes must be Block aligned, and space must remain for the unique seed and natural suffix. Cold mode also pays for initial misses and is constrained by request order, groups, and DP-lane watermarks. Run inspect first and check reachable_min, reachable_max, and target_reachable.
Why is warmup excluded from formal statistics?ο
Warmup exists only to establish cache state. Counting it in request volume, throughput, latency, or hit rate would mix setup cost into the formal benchmark window.
Why does prepare report that artifacts already exist?ο
prepare may have reused an inspect timestamp that already contains formal artifacts. Run inspect again to obtain a new timestamp. Use the following command only when intentionally rebuilding the same directory:
ais-bench-prefix-cache prepare --scenario ./scenario.json --overwrite
Why does tokenizer round-trip validation fail?ο
The plugin requires canonical prefixes, seeds, and final prompts to survive tokenizer encode/decode round trips. Verify that the tokenizer files are complete, trust_remote_code is configured correctly, and the tokenizer version matches the target service.
Current Scopeο
Supports data planning for multiple DP ranks behind one HTTP endpoint.
Does not support multiple independent inference-server instances.
runwarms every Prefix Group on every DP rank, executes AISBench, and collects Prometheus metrics. Warmup completes before the formal baseline and is excluded from formal statistics.analyzerecomputes results offline from saved baseline/after.promfiles.The complete configuration and JSON contracts are defined by the Prefix Cache plugin README and complete Scenario reference.