gem/scripts/analyze-decode-attention-traffic.sh
2026-08-30 18:24:55 -06:00

84 lines
3.7 KiB
Bash
Executable file

#!/bin/sh
# Derive minimum logical traffic for Gemma 4 31B split-score decode attention.
set -eu
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then
echo "usage: $0 PROFILE.json [OUTPUT.json]" >&2
exit 2
fi
profile=$1
output=${2:-}
[ -f "$profile" ] || { echo "gem: decode profile is missing: $profile" >&2; exit 2; }
if [ -n "$output" ] && { [ -e "$output" ] || [ -L "$output" ]; }; then
echo "gem: traffic analysis output already exists: $output" >&2
exit 2
fi
jq -e '
.schema_version == 1 and .engine == "gem-native-c-metal" and
.workload == "synthetic-exact-shape-decode-attention-stage-profile" and
.shape == "31b-full" and .query_heads == 32 and .kv_heads == 4 and
.head_dimension == 512 and .window == 0 and
(.live_tokens | type) == "number" and .live_tokens >= 8192 and
.live_tokens == (.live_tokens | floor) and
(.value_dimension_tile | type) == "number" and .value_dimension_tile > 0 and
.value_dimension_tile == (.value_dimension_tile | floor) and
(.head_dimension % .value_dimension_tile) == 0 and
(.score_scratch_bytes == .query_heads * .live_tokens * 4) and
((.value_partitions // 1) | type) == "number" and
(.value_partitions // 1) >= 1 and
(.value_partitions // 1) == ((.value_partitions // 1) | floor) and
(if (.value_partitions // 1) > 1 then
.value_partition_scratch_bytes ==
.query_heads * .head_dimension * (.value_partitions // 1) * 4
else true end)
' "$profile" >/dev/null || {
echo "gem: profile does not satisfy the 31B F32 decode traffic contract" >&2
exit 1
}
profile_hash=$(shasum -a 256 "$profile" | awk '{print $1}')
temporary=$(mktemp /private/tmp/gem-decode-traffic.XXXXXX)
trap 'rm -f "$temporary"' EXIT INT TERM HUP
jq --arg source "$profile" --arg profile_hash "$profile_hash" '
(.query_heads * .live_tokens * 4) as $score |
(.kv_heads * .head_dimension * .live_tokens * 4) as $cache_side |
(.head_dimension / .value_dimension_tile) as $dimension_groups |
(.value_partitions // 1) as $partitions |
(if $partitions > 1 then .value_partition_scratch_bytes else 0 end) as $partials |
(.query_heads * .head_dimension * 4) as $output |
($score * (1 + 5 + $dimension_groups)) as $score_intermediate |
($cache_side * 2 + $score_intermediate + $partials * 2 + $output) as $total |
{schema_version: 1, engine: "gem-native-c-metal",
workload: "decode-attention-minimum-logical-traffic",
source_profile: $source, source_profile_sha256: $profile_hash,
shape: {name: .shape, query_heads, kv_heads, head_dimension,
live_tokens, value_dimension_tile,
value_dimension_groups: $dimension_groups,
value_partitions: $partitions},
bytes: {key_cache_read: $cache_side, value_cache_read: $cache_side,
raw_score_write: $score,
normalize_score_io: (5 * $score),
value_score_read: ($dimension_groups * $score),
avoidable_score_intermediate: $score_intermediate,
value_partial_write: $partials, value_partial_reduce_read: $partials,
output_write: $output, modeled_total: $total},
ratios: {
score_intermediate_over_kv_cache_read_pct:
(100 * $score_intermediate / (2 * $cache_side)),
bounded_partial_over_kv_cache_read_pct:
(100 * (2 * $partials + $output) / (2 * $cache_side))},
interpretation: {
accounting: "minimum-logical-bytes-not-device-counter-traffic",
normalization_passes: ["max-read", "exp-read-write", "scale-read-write"],
value_score_reuse: "one full score-row read per output-dimension group",
promotion_eligible: false,
required_next_evidence: "Metal bandwidth/cache counters and authentic whole-token A/B"}}
' "$profile" > "$temporary"
if [ -n "$output" ]; then
mv "$temporary" "$output"
trap - EXIT INT TERM HUP
else
cat "$temporary"
fi