aiml
SearchMy learning
Menu

Lesson 19 of 21Module 5

Admit decoding requests between token steps

Compute last-position logits for unequal-length prefixes in one padded tensor batch. Execute a focused local CPU experiment and inspect its raw result.

Prerequisites

The trained story-model generation pipeline and PyTorch tensor bridge; autoregressive decoding, causal attention and padded tensor shapes.

By the end, you can

  • Compute last-position logits for unequal-length prefixes in one padded tensor batch.
  • Admit waiting requests into freed capacity between decoding iterations.
  • Compare deterministic generated tokens and logical schedules without inferring wall-clock throughput.
Runtime
Python 3.12+ and CPU PyTorch
Dependencies
PyTorch 2.14.1, pinned in requirements.txt

Lab code, tests and fixtures (ZIP, 1.8 MB)Quiz check your understanding

Progress is saved in this browser.

Autoregressive requests can need different numbers of output tokens. If the active batch is locked until every request finishes, one early completion leaves a slot unused while another request waits. This activity executes padded CPU decoding under fixed-batch and per-token admission rules.

When you need this. You need to distinguish a scheduling mechanism from a serving benchmark. Trace which requests occupy the next model call and verify their generated tokens before interpreting a schedule with fewer logical iterations.

A decoding iteration runs one next-token step for each active request. The scheduler removes finished jobs and may admit waiting ones before the next iteration. Prefixes have different lengths, so the tensor batch is right-padded. A causal mask blocks future positions, a validity mask blocks padded keys, and the predictor gathers the last real position for each row.

Fixed batch6 logical ticks · rows show active jobs0ab1ab2b3b4b5cabcContinuous admission5 logical ticks · rows show active jobs0ab1ab2bc3b4b5abcFixed batch6 logical ticks · active jobs0ab1ab2b3b4b5cabcContinuous admission5 logical ticks · active jobs0ab1ab2bc3b4b5abc
Each column is one logical decoding tick. Filled cells show the actual active-job trace. The comparison has no process arrows between scheduling policies; it measures neither wall-clock latency nor throughput.

The experiment reuses the byte-preserved trained checkpoint and tokenizer. At each logical tick it builds a tensor batch of complete current prefixes, computes one forward pass, selects argmax tokens and updates per-job completion budgets. Continuous admission fills available slots at the next iteration. The fixed policy waits until its entire active set is empty. Both recompute prefixes; neither uses a KV cache.

The copied trained parameters and tokenizer come from the existing story-model training lesson. Their original files remain unchanged.

Download the complete lab and enter quantization-and-batching/. Create an isolated Python environment and install its pinned dependency with python -m pip install -r requirements.txt. The computation uses CPU tensors; no pretrained model download, GPU or paid service is needed. The PyTorch installation guide covers platform-specific installation choices. Another library version requires rerunning the checks.

1. Keep padding out of the prediction

batched_logits() forms (batch, max_length, width) tensors. Masking padded keys prevents artificial padding tokens from influencing real positions. Gathering index length-1 prevents a padded query position from becoming the returned prediction. The two masks and the gather solve different problems.

test_batched_logits_match_individual_with_padding compares each returned vocabulary-logit vector with an independent decoder.forward call on its unpadded prefix. It uses absolute tolerance 1e-10 and relative tolerance 1e-8. Comparing only the two scheduler outputs would not detect a batching defect shared by both.

2. Follow the freed slot

Execute the batching experimentTerminal · bash
python run.py batching

All jobs in this fixture arrive at logical tick zero. Capacity is 2. Their token budgets are a: 2, b: 5, c: 1. The fixed schedule finishes at tick 6; continuous admission finishes at tick 5. These are authored-clock iterations, not seconds.

Request c is admitted at tick 5 in the fixed schedule and tick 2 in continuous admission. Its completion ticks are 6 and 3. Inspect the trace for the slot freed when request a finishes. The complete generated token lists match: true.

Read the complete implementation
experiment.pySource · pythonDownload
"""Actual CPU weight quantization and token-step scheduling experiments.

The scheduler uses logical arrival ticks, not measured network latency.
"""
import copy
import json
import math
from pathlib import Path
import torch
import torch.nn.functional as F
import decoder
from bpe import Tokenizer

HERE=Path(__file__).resolve().parent
MATRICES=['Wq','Wk','Wv','Wo','W1','W2','W_out']


def parameters():
    raw=json.loads((HERE/'params.json').read_text())
    decoder.CONFIG['d']=len(raw['tok_emb'][0])
    return {name:torch.tensor(value,dtype=torch.float64) for name,value in raw.items()}


def quantize(weight,bits):
    if bits not in (4,8) or weight.ndim!=2 or min(weight.shape)<1 or not torch.isfinite(weight).all():
        raise ValueError('finite matrix and 4 or 8 bits required')
    qmax=2**(bits-1)-1
    maximum=weight.abs().amax(1)
    scales=torch.where(maximum==0,torch.ones_like(maximum),maximum/qmax)
    q=(weight/scales[:,None]).round().clamp(-qmax,qmax).to(torch.int8)
    if bits==8:
        payload=bytes(q.view(torch.uint8).flatten().tolist())
    else:
        shifted=(q.flatten().to(torch.int16)+8).to(torch.uint8)
        if len(shifted)%2:shifted=torch.cat((shifted,torch.tensor([8],dtype=torch.uint8)))
        payload=bytes((shifted[::2] | (shifted[1::2]<<4)).tolist())
    return {'bits':bits,'shape':list(weight.shape),'scales':scales.tolist(),'payload_hex':payload.hex()}


def dequantize(artifact):
    bits=artifact['bits'];rows,cols=artifact['shape']
    if bits not in (4,8) or type(rows) is not int or type(cols) is not int or rows<1 or cols<1:
        raise ValueError('invalid quantized shape or width')
    scales=torch.tensor(artifact['scales'],dtype=torch.float64)
    if scales.shape!=(rows,) or not torch.isfinite(scales).all() or (scales<=0).any():
        raise ValueError('invalid row scales')
    data=bytearray.fromhex(artifact['payload_hex'])
    required=rows*cols if bits==8 else (rows*cols+1)//2
    if len(data)!=required:raise ValueError('quantized payload size mismatch')
    packed=torch.tensor(list(data),dtype=torch.uint8)
    if bits==8:q=packed.view(torch.int8).to(torch.float64)
    else:q=torch.stack((packed&15,packed>>4),1).flatten()[:rows*cols].to(torch.float64)-8
    return q.reshape(rows,cols)*scales[:,None]


def batched_logits(p,sequences):
    if not sequences:raise ValueError('nonempty batch required')
    for ids in sequences:decoder.inputs(p,ids,[0]*len(ids))
    length=max(map(len,sequences));d=p['tok_emb'].shape[1]
    ids=torch.zeros((len(sequences),length),dtype=torch.long)
    sizes=torch.tensor([len(s) for s in sequences])
    for i,sequence in enumerate(sequences):ids[i,:len(sequence)]=torch.tensor(sequence)
    x=p['tok_emb'][ids]+p['pos_emb'][:length]
    norm=lambda value,name:F.layer_norm(value,(d,),p[name+'_g'],p[name+'_b'],eps=decoder.model.EPS)
    h=norm(x,'ln1');q,k,v=(h@p[name] for name in ['Wq','Wk','Wv'])
    scores=(q@k.transpose(-1,-2))/math.sqrt(d)
    causal=torch.ones(length,length,dtype=torch.bool).tril()
    valid=torch.arange(length)[None,:]<sizes[:,None]
    allowed=causal[None,:,:]&valid[:,None,:]
    mixed=scores.masked_fill(~allowed,float('-inf')).softmax(-1)@v
    x=x+mixed@p['Wo'];h=norm(x,'ln2')
    x=x+F.relu(h@p['W1']+p['b1'])@p['W2']+p['b2']
    logits=norm(x,'lnf')@p['W_out']+p['b_out']
    return torch.stack([logits[i,n-1] for i,n in enumerate(sizes)])


def schedule(p,jobs,continuous=True,capacity=2):
    if type(capacity) is not int or capacity<1:raise ValueError('positive capacity required')
    if not isinstance(jobs,list) or not jobs or len(jobs)>64:
        raise ValueError('1..64 jobs required')
    if any(not isinstance(job,dict) or set(job)!={'id','arrival','prompt','tokens'} or not isinstance(job['id'],str) or not job['id'] for job in jobs):
        raise ValueError('named jobs with arrival, prompt and tokens required')
    if len({job['id'] for job in jobs})!=len(jobs):raise ValueError('unique job IDs required')
    for job in jobs:
        if type(job['arrival']) is not int or job['arrival']<0 or type(job['tokens']) is not int or job['tokens']<1:
            raise ValueError('nonnegative arrival and positive token budget required')
        decoder.inputs(p,job['prompt'],[0]*len(job['prompt']))
        if len(job['prompt'])+job['tokens']>len(p['pos_emb']):raise ValueError('context budget exceeded')
    pending=sorted(copy.deepcopy(jobs),key=lambda j:(j['arrival'],j['id']));active=[];done=[];ticks=[];tick=0
    with torch.no_grad():
        while pending or active:
            if continuous or not active:
                while pending and pending[0]['arrival']<=tick and len(active)<capacity:
                    job=pending.pop(0);job['generated']=[];job['admitted']=tick;active.append(job)
            if not active:
                tick=pending[0]['arrival'];continue
            logits=batched_logits(p,[j['prompt']+j['generated'] for j in active])
            tokens=logits.argmax(-1).tolist();ticks.append({'tick':tick,'active':[j['id'] for j in active]})
            keep=[]
            for job,token in zip(active,tokens):
                job['generated'].append(token)
                if len(job['generated'])==job['tokens']:
                    job['finished']=tick+1;done.append(job)
                else:keep.append(job)
            active=keep;tick+=1
    return {'policy':'continuous admission' if continuous else 'fixed batch until all finish',
            'capacity':capacity,'logical_ticks':tick,'batch_forward_calls':len(ticks),
            'jobs':sorted(done,key=lambda j:j['id']),'trace':ticks}


def quantization_run():
    p=parameters();tok=Tokenizer.load(HERE/'tokenizer.json');text='mia has a red hat.';ids=tok.encode(text)
    baseline,_=decoder.forward(p,ids,[0]*len(ids));rows=[]
    full=sum(value.numel()*value.element_size() for value in p.values())
    for bits in [8,4]:
        changed={name:value.clone() for name,value in p.items()};packed={}
        for name in MATRICES:
            packed[name]=quantize(p[name],bits);changed[name]=dequantize(packed[name])
        logits,_=decoder.forward(changed,ids,[0]*len(ids))
        payload=sum(len(bytes.fromhex(a['payload_hex']))+len(a['scales'])*8 for a in packed.values())
        untouched=sum(value.numel()*value.element_size() for name,value in p.items() if name not in MATRICES)
        rows.append({'bits':bits,'selected_matrices':MATRICES,'numeric_model_payload_bytes':payload+untouched,
                     'packed_matrix_bytes':payload,'unquantized_parameter_bytes':untouched,
                     'serialized_quantized_artifacts_bytes':len(json.dumps(packed).encode()),
                     'max_logit_error':(logits-baseline).abs().max().item(),
                     'changed_argmax_positions':int((logits.argmax(-1)!=baseline.argmax(-1)).sum()),
                     'positions':len(ids)})
    return {'mode':'actual local CPU quantization, packing and dequantized inference on an existing trained checkpoint',
            'input_text':text,'input_ids':ids,'full_precision_payload_bytes':full,'results':rows,
            'scope':'Selected linear matrices only; scales remain float64. No integer kernel, AWQ, GPU speed or quality benchmark.'}


def batching_run():
    p=parameters();tok=Tokenizer.load(HERE/'tokenizer.json')
    jobs=[{'id':'a','arrival':0,'prompt':tok.encode('mia has'),'tokens':2},
          {'id':'b','arrival':0,'prompt':tok.encode('zoe has'),'tokens':5},
          {'id':'c','arrival':0,'prompt':tok.encode('ben has'),'tokens':1}]
    fixed=schedule(p,jobs,False);continuous=schedule(p,jobs,True)
    same=[j['generated'] for j in fixed['jobs']]==[j['generated'] for j in continuous['jobs']]
    if not same:raise AssertionError('scheduling changed deterministic output')
    return {'mode':'actual local CPU batched token decoding under authored logical arrival ticks',
            'fixed':fixed,'continuous':continuous,'outputs_equal':same,
            'scope':'Logical ticks are not wall-clock latency or throughput. Full-prefix recomputation; no paged KV cache or vLLM claim.'}
Actual saved batching executionRecorded output · textDownload
{
  "mode": "actual local CPU batched token decoding under authored logical arrival ticks",
  "fixed": {
    "policy": "fixed batch until all finish",
    "capacity": 2,
    "logical_ticks": 6,
    "batch_forward_calls": 6,
    "jobs": [
      {
        "id": "a",
        "arrival": 0,
        "prompt": [
          58,
          36
        ],
        "tokens": 2,
        "generated": [
          33,
          89
        ],
        "admitted": 0,
        "finished": 2
      },
      {
        "id": "b",
        "arrival": 0,
        "prompt": [
          61,
          36
        ],
        "tokens": 5,
        "generated": [
          33,
          89,
          79,
          4,
          67
        ],
        "admitted": 0,
        "finished": 5
      },
      {
        "id": "c",
        "arrival": 0,
        "prompt": [
          6,
          39,
          36
        ],
        "tokens": 1,
        "generated": [
          33
        ],
        "admitted": 5,
        "finished": 6
      }
    ],
    "trace": [
      {
        "tick": 0,
        "active": [
          "a",
          "b"
        ]
      },
      {
        "tick": 1,
        "active": [
          "a",
          "b"
        ]
      },
      {
        "tick": 2,
        "active": [
          "b"
        ]
      },
      {
        "tick": 3,
        "active": [
          "b"
        ]
      },
      {
        "tick": 4,
        "active": [
          "b"
        ]
      },
      {
        "tick": 5,
        "active": [
          "c"
        ]
      }
    ]
  },
  "continuous": {
    "policy": "continuous admission",
    "capacity": 2,
    "logical_ticks": 5,
    "batch_forward_calls": 5,
    "jobs": [
      {
        "id": "a",
        "arrival": 0,
        "prompt": [
          58,
          36
        ],
        "tokens": 2,
        "generated": [
          33,
          89
        ],
        "admitted": 0,
        "finished": 2
      },
      {
        "id": "b",
        "arrival": 0,
        "prompt": [
          61,
          36
        ],
        "tokens": 5,
        "generated": [
          33,
          89,
          79,
          4,
          67
        ],
        "admitted": 0,
        "finished": 5
      },
      {
        "id": "c",
        "arrival": 0,
        "prompt": [
          6,
          39,
          36
        ],
        "tokens": 1,
        "generated": [
          33
        ],
        "admitted": 2,
        "finished": 3
      }
    ],
    "trace": [
      {
        "tick": 0,
        "active": [
          "a",
          "b"
        ]
      },
      {
        "tick": 1,
        "active": [
          "a",
          "b"
        ]
      },
      {
        "tick": 2,
        "active": [
          "b",
          "c"
        ]
      },
      {
        "tick": 3,
        "active": [
          "b"
        ]
      },
      {
        "tick": 4,
        "active": [
          "b"
        ]
      }
    ]
  },
  "outputs_equal": true,
  "scope": "Logical ticks are not wall-clock latency or throughput. Full-prefix recomputation; no paged KV cache or vLLM claim.",
  "refusal": "positive capacity required",
  "command": [
    "python",
    "run.py",
    "batching",
    "--record"
  ],
  "exit_status": 0,
  "environment": {
    "python": "3.12.8",
    "torch": "2.14.1",
    "platform": "macOS-27.0.1-arm64-arm-64bit"
  },
  "sources": {
    "experiment.py": "1306ee38f04deea2060cdbf6fa7449da38279f9fe7be2eec8893d3a2afe8a9eb",
    "decoder.py": "5706ca21af551b17ee54ca00f5a6011981b327e03b1e880b6712c06dd7d1c39a",
    "vendor/model.py": "0d3d469afd5f35ef19b04e0b0587143c39f8fbca37d6154e6435c4ed4e8b80fc",
    "run.py": "aaac2de2d29a3e5f70f654839dc454cd8ef0ac97fa14e870d245d28ab65d3022",
    "test_experiment.py": "f9c08f86d6ce9f9a5d78dd6acf572f81431711992e709ac6106c17b90f76d04d",
    "params.json": "26510c85e6e6bf6e9b1dd8766f20a062ce6da35eff4d50984f1b2aead774c1d5",
    "tokenizer.json": "7478ffcb69ca5383828956b1d6db10f2ca6bc925abab7122e8da6f5d05e61657",
    "bpe.py": "f2986e70b69224d9bae7694b085275d01d8cf0103b7f05399e8c9840a1679143"
  }
}
Actual local CPU batching report, including the computed result, scope and refusal.
Actual local CPU execution. Full-resolution capture · Raw result · Capture provenance. Opening the saved report does not execute the experiment again.

run.py batching performs new local computation and leaves saved files unchanged. --record deliberately replaces this stage’s results and report. The raw record retains the command, exit status, runtime and implementation hashes.

Failure cases

Boundary checked Required behavior
Invalid job or capacity Refuse invalid identities, budgets, capacity and context before admission.
Work admitted before arrival Admit each request only when its declared logical arrival tick has been reached.
Padded output differs from individual inference Compare each padded-batch output with an independent unpadded decoder call.

A job cannot fit the context or capacity contract. The saved refusal is positive capacity required. test_invalid_job_identity_capacity_and_context_refused also checks duplicate job IDs, nonpositive token budgets, excessive context and Boolean token IDs. Refuse the request rather than allowing a malformed job to occupy the schedule.

A request has not arrived. test_idle_arrival_does_not_start_work_early uses a later-arrival fixture and checks that it is admitted only at its declared tick. The main comparison’s simultaneous arrivals do not test that boundary by themselves.

Run python -m unittest -v test_experiment.py. In a separate copy, delay request c’s arrival and predict its admission tick. Explain why changing arrival ticks can change the schedule without changing deterministic token predictions.

Interpret the result at its actual scope

Iteration-level scheduling is described in ORCA. This lab isolates the admission rule using a small padded CPU decoder; it does not reproduce ORCA’s distributed system or performance results. Logical ticks and forward-call counts are not wall-clock latency or throughput. There is no HTTP traffic, paged KV cache, GPU scheduler or vLLM implementation in this activity.

Check your understanding

One request finishes while another remains active. What changes under continuous admission?

Choose one answer, then check your reasoning.

Read the explanation

Admission is reconsidered between token steps rather than waiting for the whole active batch to finish.

References

Primary sources checked October 24, 2026. The saved local execution, rather than the cited research results, supplies the measurements above.

Written by Arjun Prabhulal.

Module 5

Compare inference work and adaptation choices