Metrics
Quail records execution time, row counts, and token counts for each query. The report includes totals for the query and measurements for individual operators in its plan.
Read a query report
Call query.run() to execute a query. The returned QueryResult has a
report containing the measurements from that run.
For example, imagine a reviews table registered on session, with
review IDs in id and review text in body. Run a filter and read its
execution time and token count:
query = session.sql("""
SELECT r.id
FROM reviews r
WHERE AI.IF(PROMPT('Does this review discuss the ending? {0}', r.body))
""", dialect="bq")
result = query.run()
report = result.report
print("Query time in seconds:", report["wall_s"])
print("Model startup time in seconds:", report["boot_s"])
print("Tokens computed:", report["fresh_tokens"])To compare measurements with the plan's estimates, use
result.explain().
Query totals
Execution time
| Field | Meaning |
|---|---|
wall_s | Query execution time in seconds. Excludes model startup and result collection. |
boot_s | Model startup time in seconds, including loading weights and preparing GPU kernels. |
worker_total_s | Time in seconds from calling run() until it returns. Includes planning, input preparation, model startup, and preparing the result. Excludes result collection. |
boot_kind | "cold" when the model or kernels needed preparation; "warm" when they were already ready. |
To inspect startup time, read report["boot"].
It includes load_model_s for loading weights, arena_s for allocating
KV memory, and warm_kernels_s for preparing kernels. Each is in seconds.
Token counts
A fresh token is a token position computed by the model rather than read from saved KV. Computing the same position again counts again.
| Field | Meaning |
|---|---|
fresh_tokens | Total token positions computed by the model. |
cached_tokens | Total token positions read from KV, when available. |
fresh_tokens counts model computation. It does not count every token
in every prompt when the model reuses KV.
Operator metrics
report["node_metrics"] contains measurements for each physical operator.
The keys are the operator IDs shown by explain(verbose=True).
For example, "ai_filter:r" identifies a model filter on table alias r.
for operator_id, metrics in report["node_metrics"].items():
print(operator_id, metrics["input_rows"], metrics["output_rows"])| Field | Meaning |
|---|---|
wall_s | Operator time in seconds. |
input_rows | Rows or pairs received by the operator. |
output_rows | Rows or pairs returned by the operator. |
evaluated_documents | Documents evaluated by a model filter, score, or classification operator. |
evaluated_document_pairs | Document pairs evaluated by a model join or classification operator. |
fresh_tokens, cached_tokens | Token counts for the operator, with the same definitions as the query totals. |
The available measurements depend on the operator.
Use the query's wall_s for total execution time rather than adding
operator times.
Filter and join predicates
report["stages"] records measurements for individual filter and join
predicates. A predicate is a condition that keeps or removes rows.
A query with several AI.IF conditions can have several stage records
for one model filter operator.
Selectivity is the fraction of documents or pairs that pass a predicate. Compare the estimated and measured fractions to see whether a predicate kept more input than expected.
| Field | Meaning |
|---|---|
op | "filter" or "join". |
written_position | Predicate position in the query, starting at zero. explain() displays positions starting at one. |
provided_selectivity | Selectivity supplied in the query, or None if omitted. |
observed_selectivity | Measured fraction of documents or pairs that passed. |
evaluated | Documents evaluated by a filter predicate. |
tuples | Document pairs evaluated by a join predicate. |
alias | Table alias for a filter. |
anchor, partners | Table aliases for a join. |
Use written_position to match a record to its condition in the query.
The execution order can differ from the written order.
Score and classification measurements are in node_metrics.
GPU cost
Compute GPU cost from query time, GPU count, and the hourly price per GPU. For an H100 query, use Quail's configured H100 price:
from quail.specs import H100_USD_PER_HOUR
gpus = query.session.config.gpus
cost_usd = (report["wall_s"] / 3600) * gpus * H100_USD_PER_HOUR
print("GPU cost in dollars:", cost_usd)The calculation excludes model startup and result collection. Use the hourly price for your device when running on another GPU type.