TPU Monitoring Library
Unlock deep insights into your Cloud TPU hardware's performance and behavior with advanced TPU monitoring capabilities, built directly upon the foundational software layer, LibTPU. While LibTPU encompasses drivers, networking libraries, the XLA compiler, and TPU runtime for interacting with TPUs, the focus of this document is the TPU Monitoring Library.
The TPU Monitoring Library provides:
Comprehensive observability: Gain access to the telemetry API and metrics suite, which provides detailed insights into the operational performance and specific behaviors of your TPUs.
Diagnostic toolkits: Provides an SDK and command-line interface (CLI) designed to enable debugging and in-depth performance analysis of your TPU resources.
These monitoring features are designed to be a top-level, customer-facing solution, providing you with the essential tools to optimize your TPU workloads effectively across frameworks such as JAX and PyTorch (TorchTPU).
The TPU Monitoring Library gives you detailed information on how machine learning workloads are performing on TPU hardware. It's designed to help you understand your TPU utilization, identify bottlenecks, and debug performance issues. It gives you more detailed information than interruption metrics, goodput metrics, and other metrics.
Get started with the TPU Monitoring Library
Accessing these insights is straightforward. The TPU monitoring functionality is
integrated with the LibTPU SDK, so it is included when you install LibTPU or
when you install a framework such as JAX or TorchTPU (torch_tpu) that uses
LibTPU.
Install LibTPU
pip install libtpu
Install a framework
LibTPU updates are coordinated with framework releases. Choose your framework to install a compatible LibTPU version and its monitoring features:
JAX
When you install the latest JAX release (released monthly), it pins a compatible LibTPU version and its monitoring features:
pip install -U "jax[tpu]"
TorchTPU
For PyTorch workloads, TorchTPU (torch_tpu)
declares libtpu as a package dependency and uses the libtpu package
installed in your environment. When installed, torch_tpu automatically
registers the "tpu" device backend with PyTorch on import torch and
configures the LibTPU library path:
pip install torch_tpu
Import the library in Python
To start using the TPU Monitoring Library, import the tpumonitoring module in
your Python code. This API is identical whether your workload uses JAX or
PyTorch (torch_tpu).
from libtpu.sdk import tpumonitoring
List all supported functionality
List all metric names and the functionality they support:
from libtpu.sdk import tpumonitoring
tpumonitoring.help()
"""
libtpu.sdk.tpumonitoring.help():
List all supported functionality.
libtpu.sdk.tpumonitoring.list_supported_metrics()
List supported metric names in the list of str format.
libtpu.sdk.tpumonitoring.get_metric(metric_name:str)
Get metric data with metric name. It represents the snapshot mode.
The metric data is a object with `description()` and `data()` methods,
where the `description()` returns a string describe the format of data
and data unit, `data()` returns the metric data in the list in str format.
"""
Supported metrics
The following code sample shows how to list all supported metric names:
from libtpu.sdk import tpumonitoring
tpumonitoring.list_supported_metrics()
["duty_cycle_pct", "tensorcore_util", "hbm_util", ...]
The following table shows commonly used metrics and their definitions. Call
tpumonitoring.list_supported_metrics() for the complete list of metrics in
your libtpu version, and call get_metric(name).description() to see the
format of each metric.
| Metric | Definition | Metric name for API | Example values |
|---|---|---|---|
| Tensor Core Utilization | Measures the percentage of your TensorCore usage, calculated as the percentage of operations that are part of the TensorCore operations. Sampled 10 microseconds every 1 second. You cannot modify the sampling rate. This metric lets you monitor the efficiency of your workloads on TPU devices. |
tensorcore_util
|
['1.11', '2.22', '3.33', '4.44']
# utilization percentage for accelerator ID 0-3. |
| Duty Cycle Percentage | Percentage of time over the past sample period (every 5 seconds; can
be tuned by setting the LIBTPU_INIT_ARGS environment variable) during which
the accelerator was actively processing (recorded with cycles used to
execute HLO programs over the last sampling period). This metric
represents how busy a TPU is. The metric is emitted per chip.
|
duty_cycle_pct
|
['10.00', '20.00', '30.00', '40.00']
# Duty cycle percentage for accelerator ID 0-3. |
| HBM Capacity Total | This metric reports the total HBM capacity in bytes. |
hbm_capacity_total
|
['30000000000', '30000000000', '30000000000', '30000000000']
# Total HBM capacity in bytes that attached to accelerator ID 0-3. |
| HBM Capacity Usage | This metric reports the usage of HBM capacity in bytes over the past
sample period (every 5 seconds; can be tuned by setting
the LIBTPU_INIT_ARGS environment variable).
|
hbm_capacity_usage
|
['100', '200', '300', '400']
# Capacity usage for HBM in bytes that attached to accelerator ID 0-3. |
| Buffer transfer latency | Network transfer latencies for megascale multislice traffic. This visualization lets you understand the overall network performance environment. |
buffer_transfer_latency
|
["'8MB+', '2233.25', '2182.02', '3761.93', '19277.01', '53553.6'"]
# buffer size, mean, p50, p90, p99, p99.9 of network transfer latency distribution. |
| Inbound buffer transfer latency | Measures the time it takes to receive data buffers over the network, measured in microseconds. The metric is useful for detecting network performance problems and potential faulty network hardware. | inbound_buffer_transfer_latency |
["'8MB+', '4723.59', '4500.22', '6210.11', '7100.45', '8510.99'"]
# buffer size, mean, p50, p90, p99, p99.9 of inbound DCN network traffic transfer latency distribution. |
| Host compute latency | Measures the time it takes to compute a reduction operation in microseconds. The metric is useful for detecting performance issues and potential CPU or memory hardware faults on individual hosts participating in distributed computations. | host_compute_latency |
["'8MB+', '2408.17', '2105.40', '3600.22', '4800.15', '15000.80'"]
# buffer size, mean, p50, p90, p99, p99.9 of host compute latency distribution. |
| High Level Operation Execution Time Distribution Metrics | Provides granular performance insights into the HLO compiled binary execution status, enabling regression detection and model-level debugging. |
hlo_execution_timing
|
["'tensor_core-0', '10.00', '10.00', '20.00', '30.00', '40.00'"]
# The HLO execution time duration distribution for CoreType-CoreID with mean, p50, p90, p95, p999. |
| High Level Optimizer queue size | HLO execution queue size monitoring tracks the number of compiled HLO programs waiting or undergoing execution. This metric reveals execution pipeline congestion, enabling identification of performance bottlenecks in hardware execution, driver overhead, or resource allocation. |
hlo_queue_size
|
["tensor_core-0: 1", "tensor_core-1: 2"]
# Measures queue size for CoreType-CoreID. |
| Collective End to End Latency | This metric measures the end-to-end collective latency over DCN in microseconds, from the host initiating the operation to all peers receiving the output. It includes host-side data reduction and sending output to the TPU. Results are strings detailing buffer size, type, and mean, p50, p90, p95, and p99.9 latencies. |
collective_e2e_latency
|
["8MB+-ALL_REDUCE, 1000, 2000, 3000, 4000, 5000", …]
# Transfer size-collective op, mean, p50, p90, p95, p999 of collective end to end latency. |
| Round Trip Latency at Transport Layer | Distribution of the minimum Round Trip Times (RTT) observed on TCP connections used by gRPC for multislice TPU traffic. |
tcp_min_rtt
|
['27.63, 29.03, 38.52, 41.63, 52.74']
# Represents the distribution's mean, p50, p90, p95, and p99.9 percentiles in microseconds (µs). |
| Throughput at Transport Layer | Cumulative distribution of the recent throughput of TCP connections used by gRPC for multislice TPU traffic. |
tcp_delivery_rate
|
['11354.89, 10986.35, 18239.55, 25718.35, 44841.55']
# Represents the distribution's mean, p50, p90, p95, and p99.9 percentiles in megabits per second (Mbps). |
Read metric data
To read metric data, specify the metric name when you call the
tpumonitoring.get_metric function. You can insert ad hoc metric checks into
low-performance code to identify whether performance issues stem from software
or hardware.
The following code sample shows how to read the duty_cycle metric:
from libtpu.sdk import tpumonitoring
metric = tpumonitoring.get_metric("duty_cycle_pct")
metric.description()
"The metric provides a list of duty cycle percentages, one for each
accelerator (from accelerator_0 to accelerator_x). The duty cycle represents
the percentage of time an accelerator was actively processing during the
last sample period, indicating TPU utilization."
metric.data()
["0.00", "0.00", "0.00", "0.00"]
# accelerator_0-3
Use metrics to check TPU utilization
The following examples show how to use metrics from the TPU Monitoring Library to track TPU utilization during training and inference.
Monitor TPU duty cycle during training
JAX
Scenario: You are running a JAX training script and want to monitor the
TPU's duty_cycle_pct metric throughout training to confirm your TPUs are
effectively utilized. You can log this metric periodically during training
to track TPU utilization.
The following code sample shows how to monitor TPU duty cycle during JAX training:
import jax
import jax.numpy as jnp
from libtpu.sdk import tpumonitoring
import time
# --- Your JAX model and training setup would go here ---
# --- Example placeholder model and data (replace with your actual setup) ---
def simple_model(x):
return jnp.sum(x)
def loss_fn(params, x, y):
preds = simple_model(x)
return jnp.mean((preds - y)**2)
def train_step(params, x, y, optimizer):
grads = jax.grad(loss_fn)(params, x, y)
return optimizer.update(grads, params)
key = jax.random.PRNGKey(0)
params = jnp.array([1.0, 2.0]) # Example params
optimizer = ... # Your optimizer (for example, optax.adam)
data_x = jnp.ones((10, 10))
data_y = jnp.zeros((10,))
num_epochs = 10
log_interval_steps = 2 # Log duty cycle every 2 steps
for epoch in range(num_epochs):
for step in range(5): # Example steps per epoch
params = train_step(params, data_x, data_y, optimizer)
if (step + 1) % log_interval_steps == 0:
# --- Integrate TPU Monitoring Library here to get duty_cycle ---
duty_cycle_metric = tpumonitoring.get_metric("duty_cycle_pct")
duty_cycle_data = duty_cycle_metric.data()
print(f"Epoch {epoch+1}, Step {step+1}: TPU Duty Cycle Data:")
print(f" Description: {duty_cycle_metric.description()}")
print(f" Data: {duty_cycle_data}")
# --- End TPU Monitoring Library Integration ---
# --- Rest of your training loop logic ---
time.sleep(0.1) # Simulate some computation
print("Training complete.")
TorchTPU
Scenario: You are running a PyTorch training script with torch_tpu and
want to monitor the TPU's duty_cycle_pct metric throughout training to
confirm your TPUs are effectively utilized. Because torch_tpu uses LibTPU
underneath, you can query tpumonitoring directly inside your PyTorch
training loop.
The following code sample shows how to monitor TPU duty cycle during PyTorch
(torch_tpu) training:
import time
import torch
import torch.nn as nn
import torch.optim as optim
from libtpu.sdk import tpumonitoring
# Initialize the TPU device (torch_tpu autoloads on `import torch`)
device = torch.device("tpu")
# --- Your PyTorch model and training setup would go here ---
# --- Example placeholder model and data (replace with your actual setup) ---
class SimpleModel(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(10, 1)
def forward(self, x):
return self.linear(x).squeeze(-1)
model = SimpleModel().to(device)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)
data_x = torch.ones((10, 10), device=device)
data_y = torch.zeros((10,), device=device)
num_epochs = 10
log_interval_steps = 2 # Log duty cycle every 2 steps
for epoch in range(num_epochs):
model.train()
for step in range(5): # Example steps per epoch
optimizer.zero_grad()
preds = model(data_x)
loss = criterion(preds, data_y)
loss.backward()
optimizer.step()
if (step + 1) % log_interval_steps == 0:
# --- Integrate TPU Monitoring Library here to get duty_cycle ---
duty_cycle_metric = tpumonitoring.get_metric("duty_cycle_pct")
duty_cycle_data = duty_cycle_metric.data()
print(f"Epoch {epoch+1}, Step {step+1}: TPU Duty Cycle Data:")
print(f" Description: {duty_cycle_metric.description()}")
print(f" Data: {duty_cycle_data}")
# --- End TPU Monitoring Library Integration ---
# --- Rest of your training loop logic ---
time.sleep(0.1) # Simulate some computation
print("Training complete.")
Check HBM utilization before running inference
JAX
Scenario: Before running inference with your JAX model, check the current HBM (High Bandwidth Memory) utilization on the TPU to confirm that you have enough memory available and to get a baseline measurement before inference starts.
# The following code sample shows how to check HBM utilization before JAX inference:
import jax
import jax.numpy as jnp
from libtpu.sdk import tpumonitoring
# --- Your JAX model and inference setup would go here ---
# --- Example placeholder model (replace with your actual model loading/setup) ---
def simple_model(x):
return jnp.sum(x)
key = jax.random.PRNGKey(0)
params = ... # Load your trained parameters
# Integrate the TPU Monitoring Library to get HBM utilization before inference
hbm_util_metric = tpumonitoring.get_metric("hbm_util")
hbm_util_data = hbm_util_metric.data()
print("HBM Utilization Before Inference:")
print(f" Description: {hbm_util_metric.description()}")
print(f" Data: {hbm_util_data}")
# End TPU Monitoring Library Integration
# Your Inference Logic
input_data = jnp.ones((1, 10)) # Example input
predictions = simple_model(input_data)
print("Inference Predictions:", predictions)
print("Inference complete.")
TorchTPU
Scenario: Before running inference with your PyTorch model on
torch_tpu, check the current HBM (High Bandwidth Memory) utilization on
the TPU to confirm that you have enough memory available and to get a
baseline measurement before inference starts.
# The following code sample shows how to check HBM utilization before PyTorch (torch_tpu) inference:
import torch
import torch.nn as nn
from libtpu.sdk import tpumonitoring
device = torch.device("tpu")
# --- Your PyTorch model and inference setup would go here ---
# --- Example placeholder model (replace with your actual model loading/setup) ---
class SimpleModel(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(10, 1)
def forward(self, x):
return self.linear(x)
model = SimpleModel().to(device)
model.eval()
# Integrate the TPU Monitoring Library to get HBM utilization before inference
hbm_util_metric = tpumonitoring.get_metric("hbm_util")
hbm_util_data = hbm_util_metric.data()
print("HBM Utilization Before Inference:")
print(f" Description: {hbm_util_metric.description()}")
print(f" Data: {hbm_util_data}")
# End TPU Monitoring Library Integration
# Your Inference Logic
input_data = torch.ones((1, 10), device=device) # Example input
with torch.no_grad():
predictions = model(input_data)
print("Inference Predictions:", predictions.cpu())
print("Inference complete.")
Check network metrics
Scenario: You are running a multi-host and multislice workload and want to connect to one of the GKE pods or TPUs using SSH to view network metrics while the workload is running. You can also incorporate these commands directly into your multi-host workload.
JAX
import jax
import jax.numpy as jnp
from libtpu.sdk import tpumonitoring
# --- Your JAX model and multi-host training setup goes here ---
metric_name_rate = "tcp_delivery_rate"
metric_name_rtt = "tcp_min_rtt"
# ==============================================================================
# Metric 1: TCP Delivery Rate
# ==============================================================================
# This metric reflects the delivery rate of the TCP connection (bytes delivered / elapsed time).
# The output is a list of strings representing delivery rate statistics:
# [mean, p50, p90, p95, p99.9]
# Example: ['100.00', '200.00', '300.00', '400.00', '500.00'] (Values in Mbps)
delivery_rate_metric = tpumonitoring.get_metric(metric_name_rate)
print("Description:", delivery_rate_metric.description())
print("Data:", delivery_rate_metric.data())
# ==============================================================================
# Metric 2: TCP Minimum Round Trip Time (RTT)
# ==============================================================================
# This metric reflects the minimum RTT measured between sending a TCP packet
# and receiving the acknowledgement.
# The output is a list of strings representing latency statistics:
# [mean, p50, p90, p95, p99.9]
# Example: ['100.00', '200.00', '300.00', '400.00', '500.00'] (Values in us - microseconds)
min_rtt_metric = tpumonitoring.get_metric(metric_name_rtt)
print("Description:", min_rtt_metric.description())
print("Data:", min_rtt_metric.data())
TorchTPU
import torch
from libtpu.sdk import tpumonitoring
device = torch.device("tpu")
# --- Your distributed PyTorch (torch_tpu) model and training setup goes here ---
metric_name_rate = "tcp_delivery_rate"
metric_name_rtt = "tcp_min_rtt"
# ==============================================================================
# Metric 1: TCP Delivery Rate
# ==============================================================================
delivery_rate_metric = tpumonitoring.get_metric(metric_name_rate)
print("Description:", delivery_rate_metric.description())
print("Data:", delivery_rate_metric.data())
# ==============================================================================
# Metric 2: TCP Minimum Round Trip Time (RTT)
# ==============================================================================
min_rtt_metric = tpumonitoring.get_metric(metric_name_rtt)
print("Description:", min_rtt_metric.description())
print("Data:", min_rtt_metric.data())
Refresh frequency of TPU metrics
The refresh frequency of TPU metrics is constrained to a minimum of one second. Host metric data is exported at a fixed frequency of 1 Hz. The latency introduced by this export process is negligible. Runtime metrics from LibTPU are not subject to the same frequency constraint. However, for consistency, these metrics are also sampled at 1 Hz or 1 sample per second.
TPU-Z module
TPU-Z is a telemetry and debugging facility for TPUs. It provides detailed
runtime status information for all TPU cores attached to a host. The
functionality is provided through the tpuz module, which is part of the
libtpu.sdk module in the libtpu Python SDK. The module provides a snapshot
of each core's state.
The primary use case for TPU-Z is diagnosing hangs or deadlocks in distributed TPU workloads. You can query the TPU-Z service on hosts to capture the state of every core, comparing the Program Counters, HLO locations, and Run IDs across all cores to identify anomalies.
Use the get_core_state_summary() function within the libtpu.sdk library to
display the TPU-Z metrics:
from libtpu import sdk
summary = sdk.tpuz.get_core_state_summary()
The output for the TPU-Z metrics is provided as a dictionary. The following is a truncated example for a single core:
{
"host_name": "my-tpu-host-vm",
"core_states": {
"1": {
"core_id": {
"global_core_id": 1,
"chip_id": 0,
"core_on_chip": {
"type": "TPU_CORE_TYPE_TENSOR_CORE",
"index": 1
}
},
"sequencer_info": [
{
"sequencer_type": "TPU_SEQUENCER_TYPE_TENSOR_CORE_SEQUENCER",
"sequencer_index": 0,
"pc": 4490,
"program_id": 3274167277388825310,
"run_id": 3
}
],
"program_fingerprint": "b'\\xbefB\\xc6\\x1eb\\xc1#\\xd0...'",
"queued_program_info": [],
"error_message": ""
}
// ...
}
}
To retrieve information about the High-Level Optimizers (HLO) on each core, set
the include_hlo_info parameter to True:
summary = sdk.tpuz.get_core_state_summary(include_hlo_info=True)
The output includes additional HLO Information:
"1": {
"core_id": {
"global_core_id": 1,
"chip_id": 0,
"core_on_chip": {
"type": "TPU_CORE_TYPE_TENSOR_CORE",
"index": 1
}
},
"sequencer_info": [
{
"sequencer_type": "TPU_SEQUENCER_TYPE_TENSOR_CORE_SEQUENCER",
"sequencer_index": 0,
"pc": 17776,
"tag": 3,
"tracemark": 2147483646,
"program_id": 3230481660274331500,
"run_id": 81,
"hlo_location": "HLO: fusion.11; HLO computation: main.126_spmd",
"hlo_detailed_info": "[{\"details\":\"HloModule fusion.11, entry_computation_layout={(bf16[20>..."
}
],
"program_fingerprint": "\ufffdU\ufffd4j\u7c6e\ufffd\ufffd{\u0017\ufffd\ufffdHHV\ufffdD\ufffde\uff>",
"launch_id": 1394130914,
"queued_program_info": [
{
"run_id": 81,
"launch_id": 1394130914,
"program_fingerprint": "\ufffdU\ufffd4j\u7c6e\ufffd\ufffd{\u0017\ufffd\ufffdHHV\ufffdD\ufffde>"
}
]
}
TPU-Z metrics
The get_core_state_summary function returns TPU-Z metrics in the form of a
dictionary with the following structure.
CurrentCoreStateSummary
The CurrentCoreStateSummary dictionary provides detailed summary of an
individual TPU core's state.
| Field | Type | Description |
|---|---|---|
core_id |
dictionary | A TpuCoreIdentifier dictionary that contains ID information about the TPU core. |
sequencer_info |
list of dictionaries | A list of SequencerInfo dictionaries, describing the state of each sequencer on the core. |
program_fingerprint |
bytes | The fingerprint of the program executing on this core. |
launch_id |
integer | The launch ID of the current or most recent program. |
queued_program_info |
list of dictionaries | A list of QueuedProgramInfo dictionaries for programs queued for execution. |
error_message |
string | Any error messages for this core. |
TpuCoreIdentifier
The TpuCoreIdentifier dictionary provides ID information for cores within the
TPU system.
| Field | Type | Description |
|---|---|---|
global_core_id |
integer | The ID of the core. |
chip_id |
integer | The ID of the chip that the core belongs to. |
core_on_chip |
dictionary | A TpuCoreOnChip dictionary describing the core's type and its index on the chip. |
TpuCoreOnChip
The TpuCoreOnChip dictionary contains information about a core's properties
within a specific chip.
| Field | Type | Description |
|---|---|---|
type |
string | The type of the TPU core. For example: TPU_CORE_TYPE_TENSOR_CORE. |
index |
integer | The index of the core on the chip. |
SequencerInfo
The SequencerInfo dictionary contains information about the state of a single
sequencer on a core.
| Field | Type | Description |
|---|---|---|
sequencer_type |
string | The type of sequencer. For example: TPU_SEQUENCER_TYPE_TENSOR_CORE_SEQUENCER. |
sequencer_index |
integer | The index of the sequencer (if there are multiple of the same type). |
pc |
integer | The current Program Counter value. |
program_id |
integer | The ID associated with a specific instance of a program being launched for execution on a TPU core. |
run_id |
integer | The Run ID associated with a specific instance of a program's execution on a TPU core. |
hlo_location |
string | High Level Optimizer location information. |
hlo_detailed_info |
string | Detailed High Level Optimizer information. |
QueuedProgramInfo
The QueuedProgramInfo dictionary contains information about programs queued
for execution on a core.
| Field | Type | Description |
|---|---|---|
run_id |
integer | The Run ID for the queued program. |
launch_id |
integer | The Launch ID for the queued program. |
program_fingerprint |
bytes | The fingerprint of the queued program. |
Use TPU-Z in workloads
You can access TPU-Z metrics in your workloads using either JAX or TorchTPU
(torch_tpu).
JAX
You can access TPU-Z metrics in JAX workloads through the libtpu.sdk
library. The following Python script uses JAX for tensor computation while
querying the libtpu SDK in a background thread to monitor the state and
activity of the underlying TPU hardware.
Include the following Python packages and constants:
import jax
import jax.numpy as jnp
import time
import threading
from functools import partial
from libtpu import sdk
MONITORING_INTERVAL_SECONDS = 5
JOB_DURATION_SECONDS = 30
monitoring_active = True
The monitor_tpu_status function uses a background thread to continuously
show the operational status of the TPU cores while the main application
executes a JAX workload.
def monitor_tpu_status():
"""Monitors TPU status in a background thread."""
while monitoring_active:
try:
summary = sdk.tpuz.get_core_state_summary(include_hlo_info=True)
if summary and 'core_states' in summary:
print(summary)
else:
print('WARNING: Call returned an empty or invalid summary.')
except RuntimeError as e:
print(f'FAIL: Error calling API: {e}')
except Exception as e:
print(f'FAIL: Unexpected error in monitor thread: {e}')
for _ in range(MONITORING_INTERVAL_SECONDS * 2):
if not monitoring_active:
break
time.sleep(0.5)
print('✅ Monitoring thread stopped.')
The transformer_block function implements a layer of the Transformer
architecture.
@partial(jax.jit, static_argnames=['num_heads'])
def transformer_block(params, x, num_heads=32):
"""A simplified but computationally intensive Transformer block."""
# Multi-head Self-Attention
qkv = jnp.dot(x, params['qkv_kernel'])
q, k, v = jnp.array_split(qkv, 3, axis=-1)
# Reshape for multi-head attention
q = q.reshape(q.shape[0], q.shape[1], num_heads, -1).transpose(0, 2, 1, 3)
k = k.reshape(k.shape[0], k.shape[1], num_heads, -1).transpose(0, 2, 1, 3)
v = v.reshape(v.shape[0], v.shape[1], num_heads, -1).transpose(0, 2, 1, 3)
# Scaled dot-product attention
attention_scores = jnp.einsum('nhqd,nhkd->nhqk', q, k) / jnp.sqrt(q.shape[-1])
attention_weights = jax.nn.softmax(attention_scores, axis=-1)
attention_output = jnp.einsum('nhqk,nhvd->nhqd', attention_weights, v)
attention_output = attention_output.transpose(0, 2, 1, 3).reshape(x.shape)
attention_output = jnp.dot(attention_output, params['o_kernel'])
# Residual connection and Layer Normalization 1
h1 = x + attention_output
h1_norm = h1 - jnp.mean(h1, axis=-1, keepdims=True)
h1_norm = h1_norm / jnp.sqrt(
jnp.mean(jnp.square(h1_norm), axis=-1, keepdims=True) + 1e-5
)
# Feed-Forward Network
ffn_hidden = jax.nn.gelu(jnp.dot(h1_norm, params['ffn1_kernel']))
ffn_output = jnp.dot(ffn_hidden, params['ffn2_kernel'])
# Residual connection and Layer Normalization 2
h2 = h1_norm + ffn_output
h2_norm = h2 - jnp.mean(h2, axis=-1, keepdims=True)
h2_norm = h2_norm / jnp.sqrt(
jnp.mean(jnp.square(h2_norm), axis=-1, keepdims=True) + 1e-5
)
return h2_norm
The main function sets up the JAX computation, starts the background TPU
monitoring thread, and runs the main workload loop.
def main():
num_devices = jax.device_count()
print(f"Running on {num_devices} devices.")
batch_size = 128 * num_devices
seq_len = 512
embed_dim = 1024
ffn_dim = embed_dim * 4
key = jax.random.PRNGKey(0)
params = {
'qkv_kernel': jax.random.normal(
key, (embed_dim, embed_dim * 3), dtype=jnp.bfloat16
),
'o_kernel': jax.random.normal(
key, (embed_dim, embed_dim), dtype=jnp.bfloat16
),
'ffn1_kernel': jax.random.normal(
key, (embed_dim, ffn_dim), dtype=jnp.bfloat16
),
'ffn2_kernel': jax.random.normal(
key, (ffn_dim, embed_dim), dtype=jnp.bfloat16
),
}
input_data = jax.random.normal(
key, (batch_size, seq_len, embed_dim), dtype=jnp.bfloat16
)
input_data = jax.device_put(input_data)
monitor_thread = threading.Thread(target=monitor_tpu_status)
monitor_thread.start()
print("Starting JAX computation loop...")
start_time = time.time()
iterations = 0
while time.time() - start_time < JOB_DURATION_SECONDS:
result = transformer_block(params, input_data)
result.block_until_ready()
iterations += 1
print(f' -> Jax iteration {iterations} complete.', end='\r')
print(f"\nCompleted {iterations} iterations in {JOB_DURATION_SECONDS} seconds.")
global monitoring_active
monitoring_active = False
monitor_thread.join()
if __name__ == '__main__':
main()
TorchTPU
You can access TPU-Z metrics in PyTorch workloads using torch_tpu and the
libtpu.sdk library. The following Python script runs a Transformer block
on TPU using torch_tpu while querying the libtpu SDK in a background
thread to monitor the state and activity of the underlying TPU cores.
Include the following Python packages and constants:
import time
import threading
import torch
import torch.nn as nn
from libtpu import sdk
MONITORING_INTERVAL_SECONDS = 5
JOB_DURATION_SECONDS = 30
monitoring_active = True
The monitor_tpu_status function uses a background thread to continuously
show the operational status of the TPU cores while the main application
executes a PyTorch (torch_tpu) workload:
def monitor_tpu_status():
"""Monitors TPU status in a background thread."""
while monitoring_active:
try:
summary = sdk.tpuz.get_core_state_summary(include_hlo_info=True)
if summary and 'core_states' in summary:
print(summary)
else:
print('WARNING: Call returned an empty or invalid summary.')
except RuntimeError as e:
print(f'FAIL: Error calling API: {e}')
except Exception as e:
print(f'FAIL: Unexpected error in monitor thread: {e}')
for _ in range(MONITORING_INTERVAL_SECONDS * 2):
if not monitoring_active:
break
time.sleep(0.5)
print('✅ Monitoring thread stopped.')
The TransformerBlock module implements an equivalent Transformer layer in
PyTorch:
class TransformerBlock(nn.Module):
"""A simplified but computationally intensive Transformer block in PyTorch."""
def __init__(self, embed_dim=1024, num_heads=32, ffn_dim=4096):
super().__init__()
self.attn = nn.MultiheadAttention(
embed_dim=embed_dim, num_heads=num_heads, batch_first=True
)
self.norm1 = nn.LayerNorm(embed_dim, eps=1e-5)
self.ffn = nn.Sequential(
nn.Linear(embed_dim, ffn_dim),
nn.GELU(),
nn.Linear(ffn_dim, embed_dim),
)
self.norm2 = nn.LayerNorm(embed_dim, eps=1e-5)
def forward(self, x):
attn_output, _ = self.attn(x, x, x, need_weights=False)
h1_norm = self.norm1(x + attn_output)
ffn_output = self.ffn(h1_norm)
h2_norm = self.norm2(h1_norm + ffn_output)
return h2_norm
The main function initializes the TPU device, starts the background TPU-Z
monitoring thread, and runs the PyTorch computation loop:
def main():
device = torch.device("tpu")
print(f"Running on device: {device}")
batch_size = 128
seq_len = 512
embed_dim = 1024
ffn_dim = embed_dim * 4
model = (
TransformerBlock(embed_dim=embed_dim, num_heads=32, ffn_dim=ffn_dim)
.to(dtype=torch.bfloat16, device=device)
.eval()
)
input_data = torch.randn(
(batch_size, seq_len, embed_dim), dtype=torch.bfloat16, device=device
)
monitor_thread = threading.Thread(target=monitor_tpu_status)
monitor_thread.start()
print("Starting PyTorch (torch_tpu) computation loop...")
start_time = time.time()
iterations = 0
with torch.no_grad():
while time.time() - start_time < JOB_DURATION_SECONDS:
result = model(input_data)
torch.accelerator.synchronize()
iterations += 1
print(f' -> PyTorch iteration {iterations} complete.', end='\r')
print(f"\nCompleted {iterations} iterations in {JOB_DURATION_SECONDS} seconds.")
global monitoring_active
monitoring_active = False
monitor_thread.join()
if __name__ == '__main__':
main()
Troubleshooting
This section provides troubleshooting information to help you identify and resolve problems you might encounter while using the TPU Monitoring Library.
Missing features or metrics
If you are unable to view some features or metrics, the most common cause is an
outdated libtpu version. The TPU Monitoring Library features and metrics are
included in the libtpu releases, and outdated versions might be missing new
features and metrics.
Check the version of libtpu that is running in your environment:
Command line:
pip show libtpu
Python:
import libtpu
print(libtpu.__version__)
If you are not using the latest version of
libtpu, use the following command to update the library:
pip install --upgrade libtpu
No TPU device or metrics when using TorchTPU
torch_tpu loads automatically when you run import torch, and it registers
the "tpu" device only if TPU devices are detected on the host. If
torch.device("tpu") is unavailable or metric values are all zero, verify that
you are running on a TPU VM, that no other process is using the TPUs, and that
the TORCH_DEVICE_BACKEND_AUTOLOAD environment variable is not set to 0.