Basic Distillation

This notebook demonstrates the full softs pipeline:

  • Broker routes orders between clients and suppliers

  • Supplier generates batches of soft labels

  • Client trains a student model on those labels

We use random data here to keep it fast. For real LLM distillation, see the examples/distillation/ examples — e.g. logit_topp/ (broker.py / supplier.py / client.py).

import time
import torch
import torch.nn as nn

from softs import (
    Broker, Supplier, Client, ShmMedium,
    BatchConfig, TensorSpec, EndpointConfig,
)

1. Define the batch layout

Each slot holds one batch: x (inputs) and y (soft targets).

BATCH_SIZE = 8
HIDDEN = 256
NUM_CLASSES = 100

config = BatchConfig([
    TensorSpec("x", (BATCH_SIZE, HIDDEN), "float32"),
    TensorSpec("y", (BATCH_SIZE, NUM_CLASSES), "float32"),
])

print(f"Slot size: {config.nbytes() / 1e6:.1f} MB")

2. Start the broker

endpoints = EndpointConfig()
broker = Broker(endpoints=endpoints)
broker.start()
time.sleep(0.2)

3. Start a supplier

The supplier generates random soft labels. In practice, this would run a teacher model.

def teacher_generator(product_id: str) -> bytes:
    """Simulate teacher: random soft targets."""
    x = torch.randn(BATCH_SIZE, HIDDEN)
    logits = torch.randn(BATCH_SIZE, NUM_CLASSES)
    y = torch.softmax(logits, dim=-1)  # soft targets
    return config.encode(x=x, y=y)

supplier = Supplier(
    generator_fn=teacher_generator,
    product_ids=["teacher_v1"],
    endpoint=endpoints.backend,
    medium_cls=ShmMedium,
    slot_size=config.nbytes(),
)
supplier.start()
time.sleep(0.2)

4. Create a client and train

The client orders batches by product_id, reads them from shared memory, and trains a small student.

student = nn.Sequential(
    nn.Linear(HIDDEN, 128),
    nn.ReLU(),
    nn.Linear(128, NUM_CLASSES),
)
optimizer = torch.optim.Adam(student.parameters(), lr=1e-3)

client = Client(
    endpoint=endpoints.frontend,
    medium_cls=ShmMedium,
    slot_size=config.nbytes(),
    num_slots=4,
)
client.hello()

for step in range(10):
    slot = client.request_sample("teacher_v1", timeout_ms=5000)
    assert slot is not None, f"Timeout at step {step}"

    batch = config.decode(client.medium.read(slot))
    client.release_slot(slot)

    x, y_teacher = batch["x"], batch["y"]
    y_student = torch.log_softmax(student(x), dim=-1)
    loss = -(y_teacher * y_student).sum(dim=-1).mean()

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    print(f"Step {step:2d}: loss={loss.item():.4f}")

5. Check broker stats

stats = broker.get_stats()
print(f"Suppliers: {stats.connected_suppliers}, Clients: {stats.connected_clients}, Completed: {stats.total_completed}")

6. Graceful shutdown

Order matters: close the client first, then stop the supplier (sends GOODBYE), then stop the broker.

client.close()
supplier.stop()
time.sleep(0.2)
broker.stop()
print("Shutdown complete.")