Quail
User guide

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

FieldMeaning
wall_sQuery execution time in seconds. Excludes model startup and result collection.
boot_sModel startup time in seconds, including loading weights and preparing GPU kernels.
worker_total_sTime 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.

FieldMeaning
fresh_tokensTotal token positions computed by the model.
cached_tokensTotal 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"])
FieldMeaning
wall_sOperator time in seconds.
input_rowsRows or pairs received by the operator.
output_rowsRows or pairs returned by the operator.
evaluated_documentsDocuments evaluated by a model filter, score, or classification operator.
evaluated_document_pairsDocument pairs evaluated by a model join or classification operator.
fresh_tokens, cached_tokensToken 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.

FieldMeaning
op"filter" or "join".
written_positionPredicate position in the query, starting at zero. explain() displays positions starting at one.
provided_selectivitySelectivity supplied in the query, or None if omitted.
observed_selectivityMeasured fraction of documents or pairs that passed.
evaluatedDocuments evaluated by a filter predicate.
tuplesDocument pairs evaluated by a join predicate.
aliasTable alias for a filter.
anchor, partnersTable 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.

On this page