Your first training run¶
In this tutorial you will connect a Jax or Torch training job to DVBench to attest what datasets you used during training.
1. Install DVBench¶
2. Connect a Client¶
Begin by connecting to DVBench.
What's API_URL?
As we are currently in (probably very broken) alpha testing, you will
receive API_URL from someone, presumably whomever you found out about
this from... Sorry for the wet paint.
3. Wire up a Neural Network¶
We can't help you with this part, but we trust you've got it :)
import jax
import jax.numpy as jnp
import optax
from flax import linen as nn
class TinyClassifier(nn.Module):
@nn.compact
def __call__(self, tokens):
return nn.Dense(2)(tokens.astype(jnp.float32))
model = TinyClassifier()
params = model.init(
jax.random.key(0),
jnp.ones((1, 4), dtype=jnp.uint32),
)["params"]
optimizer = optax.adam(1e-2)
opt_state = optimizer.init(params)
Using Flax TrainState?
This tutorial follows ZKDV's basic explicit transaction API. A separate
how-to guide will show how zkdv.jax.contrib.TrainState declares the
same optimizer transition through Flax's familiar apply_gradients()
workflow.
3. Configure your proof tape¶
This is where you configure the proof tape, and you'll have to make some decisions.
- name: run names must be kebab-case, with a dash
like/this-example. - rolling_window: every
rolling_windownumber of tokens is considered a sample.
Tap for more on rolling_window
A rolling_window of 4 means that every 4 tokens is considered a "sample."
For instance, the quick brown fox and brown fox the quick are considered
two distinct samples when rolling_window=4, but considered a shuffling of
the same sample when rolling_window=2. A longer window results in a shorter
tape, but offers less flexibility in shuffling.
3. Declare the replayable update¶
We need a deterministic description of how you got from one parameter to the next.
@proof.attest
def attested_step(params, opt_state, batch): # you must keep this signature
def loss_fn(current_params):
logits, loss = model.apply({"params": current_params}, batch)
return loss
gradients = jax.grad(loss_fn)(params)
# manually compute parameter updates and next state
deltas, next_state = optimizer.update(gradients, opt_state, params)
# return a tuple of PyTrees: (updates, next optimization state)
return deltas, next_state
This function has to be deterministically serializable. Common culprits that prevent this include custom kernels. Rest assured, we don't call this function that many times (usually <1% of training), so you can definitely wire up a "slow-path" here without loosing MFUs.
4. Annotate your training¶
Your training loop remains largely the same, and can contain any custom logic:
@proof.jit(donate_argnames="params") # instead of jax.jit, same signature
def train_step(params, opt_state, batch):
# open a transaction
transaction = zkdv.jax.Transaction(
batch=batch, # declare what data is used
params=params, # current parameters
opt_state=opt_state, # current optimizer state
)
# train normally
deltas, next_opt_state = compute_update(params, opt_state, batch)
next_params = optax.apply_updates(params, deltas)
# update transaction with the information produced
transaction.update(
deltas=deltas,
params=next_params,
opt_state=next_opt_state,
)
return next_params, next_opt_state
# train loop stays the same
for _ in range(...):
batch = ...
params, opt_state = train_step(params, opt_state, batch)
5. Finish the proof¶
Call finish() before evaluation.
A complete working example is in examples/jax/proof_jax.py and examples/torch/proof.py.
Next: evaluate the checkpoint.