Runtime¶
The runtime API is available inside a function running on a compute node. Calling instance_info() on the dispatching process raises an error.
Node information¶
sky.instance_info() returns an Info value. It contains the node id, compute id, rank, peer addresses, local worker slot, and worker concurrency.
import skyward as sky
@sky.function
def report():
info = sky.instance_info()
return {
"rank": info.rank,
"nodes": info.nodes,
"worker": info.worker,
"total_workers": info.total_workers,
"head": info.head_addr,
}
The derived properties are:
| Property | Meaning |
|---|---|
nodes |
Number of peer nodes |
total_workers |
nodes * workers_per_node |
global_worker_index |
Position of this worker across all node slots |
host |
Address of this node in peers |
head / head_addr |
Address of rank zero |
head_port |
Worker rendezvous port on rank zero |
job_id |
Compute id |
is_head |
Whether rank == 0 |
sky.is_head() is the shorthand for sky.instance_info().is_head.
Sharding¶
sky.shard() gives the current node its contiguous slice of one or more sequences. All nodes receive the full input and calculate the same rank split locally. With shuffle=True, the same deterministic permutation is applied to each input; seed controls the permutation. drop_last=True truncates the input so every node receives the same number of elements.
@sky.function
def train_epoch(features, labels):
features, labels = sky.shard(features, labels, shuffle=True, seed=7, drop_last=True)
return len(features)
Pass node and total_nodes explicitly when testing a shard outside a node. Otherwise they are read from Info.
Output and callbacks¶
stdout, stderr, and silent control which task output is sent back to the client. redirect_output(callback) temporarily sends both streams to a callback and yields callback writers.
skyward.Info
dataclass
¶
The node, as the code running on it sees it.
Attributes:
| Name | Type | Description |
|---|---|---|
node |
str
|
The node's id, the same one the daemon and the store use for it. |
compute |
str
|
The compute it belongs to. |
rank |
int
|
Its position among the compute's nodes, from zero. Stable for as long as the node lives — it is the rank a broadcast froze when it was admitted. |
peers |
tuple[str, ...]
|
Every node's address, rank-ordered, this one included. The addresses peers reach each other on, not the ones the daemon dials: the tunnels are the daemon's problem and exist nowhere out here. |
worker |
int
|
The task's index among the worker's concurrent slots. Zero in the
worker/thread process, distinct per subprocess under |
workers_per_node |
int
|
How many concurrent slots the worker runs, the node's |
node
instance-attribute
¶
compute
instance-attribute
¶
rank
instance-attribute
¶
peers
instance-attribute
¶
worker = 0
class-attribute
instance-attribute
¶
workers_per_node = 1
class-attribute
instance-attribute
¶
nodes
property
¶
total_workers
property
¶
Slots across the whole compute, every node's workers_per_node summed.
Uniform here — every node of a compute runs the same concurrency — so it is the node count times the per-node count.
global_worker_index
property
¶
This slot's position among all of the compute's slots, from zero.
The rank's block of workers_per_node offset by the local slot, so the
slots of rank zero come first, then rank one's, and so on without a gap.
host
property
¶
head
property
¶
Rank zero's address.
A compute has no head. This is one, by convention, for the libraries that insist on being told where the rendezvous is.
head_addr
property
¶
Rank zero's address, under the name the training libraries expect.
head_port
property
¶
The port rank zero answers on, the same one every node's worker listens on.
job_id
property
¶
The compute's id, under the name the libraries that want a job id expect.
is_head
property
¶
__init__(node, compute, rank, peers, worker=0, workers_per_node=1)
¶
skyward.instance_info()
¶
skyward.shard(*data, shuffle=False, seed=None, drop_last=False, node=None, total_nodes=None)
¶
shard(
data: Sequence[T],
/,
*,
shuffle: bool = False,
seed: int | None = None,
drop_last: bool = False,
node: int | None = None,
total_nodes: int | None = None,
) -> Sequence[T]
This node's slice of the data, without the data ever being split anywhere else.
Every node is handed the whole sequence and keeps the part that is its own, which is the cheap way round when the data is already on the machines — a dataset mounted from S3, a file baked into the image — and the only way round that does not put the daemon in the path of every byte.
The split is by rank, contiguous, and identical on every node: they agree
because they each compute the same thing, not because anybody told them. Passing
several sequences shards each the same way — the same rank split and, under
shuffle, the same permutation — so row i of one lines up with row i
of the next.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*data
|
Sequence
|
One or more sequences. Anything with a length and a slice: a list stays a list, a tuple a tuple, a numpy array or torch tensor its own type — the contiguous slice and the fancy index both preserve it. |
()
|
shuffle
|
bool
|
Permute before splitting. Deterministic given |
False
|
seed
|
int | None
|
The permutation. |
None
|
drop_last
|
bool
|
Truncate to a multiple of the node count, so every node gets the same number of elements. What a training step usually wants, and what an unbalanced last batch usually breaks. |
False
|
node
|
int | None
|
Override this node's rank instead of reading it off the node. For testing, or for sharding against a topology the node is not itself part of. |
None
|
total_nodes
|
int | None
|
Override the node count. Given together with |
None
|
Returns:
| Type | Description |
|---|---|
Sequence | tuple[Sequence, ...]
|
This rank's elements — one sequence for one argument, a tuple of them for several. Possibly empty: eight nodes and three items is three nodes with work and five without, not an error. |
skyward.is_head()
¶
skyward.stdout(fn=None, *, only=None)
¶
Forward stdout and drop stderr, optionally from only some of the nodes.
only is a rank, a sequence of ranks, "head" for rank zero, or a predicate
on the node's own :class:Info. only="head" is what turns a broadcast onto
sixty-four nodes back into something a human can read.
skyward.stderr(fn=None, *, only=None)
¶
Forward stderr and drop stdout, optionally from only some of the nodes.
skyward.silent(fn)
¶
Say nothing. The result still comes back; the printing does not.
skyward.CallbackWriter
¶
skyward.redirect_output(callback)
¶
Send the running function's stdout and stderr to callback for the block.
Both streams are redirected into a :class:CallbackWriter, so print and
anything writing to sys.stderr reach callback a write at a time. Runs on
the node, inside the task; the redirection ends with the block.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
Callable[[str], None]
|
Called with each string written to either stream. |
required |
Yields:
| Type | Description |
|---|---|
tuple[CallbackWriter, CallbackWriter]
|
The |