Werkplaats / De werkplaats
In jouw eigen tempo
← AI in de wereld
MODULE 01 · LES 03

Vergelijk payload, bestandsidentiteit en apparaatprofielen

Bereken theoretische bitpayload, verifieer kleine offline bestandjes en kies passende fictieve apparaatwaarnemingen.

90 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je berekent gewicht- en KV-payload zonder ze met echte bestandsgrootte of piek-RAM te verwarren.
  • Je verifieert SHA-256-identiteit en selecteert alleen profielen met hetzelfde apparaat, dezelfde context en passende grenzen.

Een kleiner modelbestand past makkelijker op een apparaat, maar dat vertelt nog niet hoeveel geheugen de uitvoering piekt of hoe snel het eerste token verschijnt. Ook mag een oud cachebestand niet ongemerkt als een nieuwe modelvariant worden gebruikt. Je hebt daarom drie verschillende controles nodig.

De eerste telt theoretische bytes, de tweede controleert bestandsidentiteit en de derde vergelijkt passende meetprofielen. Denk aan een koffer, een bagagelabel en de werkelijke reisvoorwaarden: ieder beantwoordt een andere vraag. Je verbindt ze zonder een enkele maat alles te laten bewijzen.

Je oefent met kleine offline bestandjes en verzonnen profielen onder expliciete meetcondities. Voor een echte toepassing komen de bestanden uit een beheerde bron en de meetgegevens uit de beoogde omgeving.

1. Tel bits en bytes zorgvuldig

Laat P het aantal parameters zijn en b_w het aantal bits per gewicht. De minimale gewichtpayload W rondt het totale aantal bits naar hele bytes af. Vier bits per gewicht leveren bij drie gewichten dus twaalf bits: twee bytes, geen anderhalve byte als opslaguitkomst.

W=⌈Pbw8⌉

Een eenvoudige KV-cache bewaart keys en values voor T tokens, L lagen, H KV-heads en d waarden per head. De factor twee telt beide soorten tensors; b_c is de bitbreedte per cachewaarde. Onze cache gebruikt zestien of 32 bits, zodat deze deling hele bytes oplevert.

K=2TLHdbc8

Neem twee tokens, één laag, één KV-head, dimensie drie en zestien cachebits. Dat zijn 24 cachebytes, naast twee gewichtbytes. De minimale som is 26 bytes; activaties, metadata, uitlijning en runtimebuffers zitten daar nog niet bij.

payload = tensor_payload(
    3,4,2,1,1,3)
print(payload['weight_payload_bytes'])
print(payload['kv_payload_bytes'])
print(payload['minimum_bytes'])
# 2
# 24
# 26

Bij nul cachetokens blijft de gewichtpayload bestaan. Een MiB telt 2**20 bytes, terwijl een miljoen bytes een andere eenheid is. De rekenfunctie telt alleen: ze hoeft de geschatte tensors niet werkelijk te alloceren.

empty_cache = tensor_payload(
    3,4,0,1,1,3)
print(empty_cache['minimum_bytes'])
# 2
01
CHECKPOINTBereken de theoretische bitpayload

2. Identiteit begint bij een expliciet manifest

Een bestandsnaam alleen vertelt niet welke inhoud je krijgt. Ons manifest noemt model, vaste revisie, variant, bestandsnamen, werkelijke bytegroottes en SHA-256-hashes. De kleine revisietags hieronder zijn oefenidentifiers; de controle doet geen netwerkverzoek.

blob = b'model-demo-v1'
row = dict(
    name='weights.bin',bytes=len(blob),
    sha256=hashlib.sha256(
        blob).hexdigest())
manifest = dict(
    model_id='lesson/demo',
    revision='a'*40,dtype='q8',
    files=[row])
blobs = {'weights.bin':blob}
verified = verify_artifacts(
    manifest,blobs)
print(verified['verified_bytes'])
print(len(verified['cache_key']))
# 13
# 64

Controleer eerst de volledige begrensde set: ontbrekende of extra bestanden zijn ook een afwijking. Daarna moeten lengte en hash overeenkomen. De cache-identiteit bevat de canoniek gesorteerde metadata, zodat alleen een andere bestandsvolgorde geen nieuwe identiteit veroorzaakt.

Een andere revisie of variant hoort juist wel een andere cachekey te geven. Een matchende hash bewijst overeenkomst met het opgegeven manifest; de betrouwbaarheid van de uitgever blijft een aparte ontwerpkeuze. De functie opent of voert de bestandjes niet uit.

updated = dict(
    manifest,revision='b'*40)
again = verify_artifacts(updated,blobs)
print(again['cache_key'] ==
      verified['cache_key'])
# False
02
CHECKPOINTVerifieer identiteit en maak een vaste cachekey

3. Vergelijk dezelfde meetcondities

Een snelle warme meting is geen vervanger voor een koude start op een ander apparaat. Vereis dezelfde promptlengte, generatielengte en cachetoestand, plus het beoogde fysieke apparaat en een beschikbare backend. Daarna pas vergelijk je download, piekgeheugen, snelheid en kwaliteit.

VERKEN HET ZELFEen profiel moet bij het apparaat én de grenzen passen

Kies grenzen voor fictieve waarnemingen onder dezelfde prompt-, generatie- en cachecondities. Bekijk daarna een afzonderlijke payloadberekening; die voorspelt de gemeten piek-RAM niet.

Gekozen: q8. 2 geschikt. Rangorde: kwaliteit, tokens/s, tijd tot eerste token, ID.

q8 · gekozen

WASM · demo-pc · 128 invoer / 16 uitvoertokens · koude cache

Download
8 MiB
Piek-RAM
160 MiB
Eerste token
220 ms
Tokens/s
20
Kwaliteit
0,95

Voldoet aan alle voorwaarden.

q4

WEBGPU · demo-pc · 128 invoer / 16 uitvoertokens · koude cache

Download
4 MiB
Piek-RAM
90 MiB
Eerste token
80 ms
Tokens/s
55
Kwaliteit
0,9

Voldoet aan alle voorwaarden.

ander-apparaat

WEBGPU · other-pc · 128 invoer / 16 uitvoertokens · koude cache

Download
2 MiB
Piek-RAM
60 MiB
Eerste token
30 ms
Tokens/s
80
Kwaliteit
0,99

Afgewezen: ander apparaat.

warme-cache

WASM · demo-pc · 128 invoer / 16 uitvoertokens · warme cache

Download
0 MiB
Piek-RAM
120 MiB
Eerste token
20 ms
Tokens/s
30
Kwaliteit
0,95

Afgewezen: andere prompt-, generatie- of cachecondities.

Afzonderlijke bitpayload, zonder tensors te alloceren

1.000.000 parameters, 4 lagen, 2 KV-koppen, kopdimensie 32 en 16-bitcache. De profielwaarnemingen hierboven blijven afzonderlijke fictieve gegevens.

Gewichten · KV-cache
Gewichtpayload
1.000.000 B
KV-payload
131.072 B
Samen minimaal
1,079 MiB

De balk toont alleen de onderlinge verhouding en schaalt mee. Metadata, activaties en runtimebuffers ontbreken. Werkelijke bestanden en piek-RAM kunnen groter zijn.

Alle profielwaarden zijn verzonnen onderwijsdata. Prompt: 128 tokens; uitvoer: 16 tokens; koude cache; apparaat demo-pc. Download maximaal 10 MiB, minimaal 15 tokens/s. Geen benchmark van je eigen pc of een bestaand model. Een hashcontrole uit de les bevestigt alleen identiteit tegenover het gegeven manifest.

TTFT betekent tijd tot het eerste token (time to first token). Tokens per seconde beschrijft de gemeten generatiesnelheid; kwaliteit is hier een gegeven oefenscore waarbij hoger beter is. Dit fictieve q8-profiel gebruikt WASM.

context = dict(
    input_tokens=128,
    output_tokens=32,cold=True)
measurements = dict(
    download_bytes=8*2**20,
    peak_bytes=160*2**20,ttft_ms=220,
    tokens_per_second=20,quality=.95)
profile = dict(
    id='q8',device='demo-a',
    backend='wasm',context=context,
    measurements=measurements)
fast = dict(
    profile,id='q4',backend='webgpu',
    measurements=dict(
        measurements,
        download_bytes=4*2**20,
        peak_bytes=90*2**20,ttft_ms=80,
        tokens_per_second=55,
        quality=.90))

Beide profielen passen hieronder binnen de gekozen grenzen. Onze selectieregel kiest eerst de hoogste kwaliteit, daarna throughput, daarna lagere TTFT en ten slotte ID. Daarom wint q8 hier, ook al is het fictieve q4-profiel sneller.

limits = dict(
    max_download_bytes=10*2**20,
    max_peak_bytes=200*2**20,
    max_ttft_ms=250,
    min_tokens_per_second=15,
    min_quality=.85)
budget = dict(
    device='demo-a',context=context,
    available_backends=[
        'wasm','webgpu'],
    limits=limits)
decision = select_profile(
    [profile,fast],budget)
print(decision['chosen_id'])
# q8

Als de latencygrens naar honderd gaat en de kwaliteitsgrens naar 0,92, past geen van beide. Geef dan None terug en bewaar de afwijsredenen. Een ongeschikt profiel aanwijzen zou de voorwaarden stilzwijgend veranderen.

strict = dict(
    budget,limits=dict(
        limits,max_ttft_ms=100,
        min_quality=.92))
print(select_profile(
    [profile,fast],strict)['chosen_id'])
# None
03
CHECKPOINTKies alleen een passend fictief profiel

Wat neem je mee?

  • Theoretische payload en werkelijk piekgeheugen zijn verschillende metingen.
  • Hashes binden inhoud aan een manifest, niet vertrouwen aan een uitgever.
  • Vergelijk passende contexten en respecteer alle expliciete budgetten.

Hierna onderzoek je privacy, fairness en de betekenis van uitleg over een model.

DE VOLGENDE STAPOnderzoek naamloze velden en herkenbare combinaties
JOUW EXPERIMENT

Probeer het zelf

Python
main.py

Python start zodra jij code uitvoert.

UITVOER

Je uitvoer verschijnt hier.
Alles rekent in je eigen browser.

Jouw checkpoints0 / 3
01
Bereken de theoretische bitpayload

Schrijf tensor_payload(parameters,weight_bits,tokens,layers,kv_heads,head_dim,cache_bits=16). Alle argumenten zijn Python-int zonder bool: parameters 1–1e9, weight_bits een van 4/8/16/32, tokens 0–32768, layers en kv_heads 1–128, head_dim 1–256, cache_bits 16 of 32. Weight_payload_bytes=ceil(parameters*weight_bits/8); kv_payload_bytes=ceil(2*tokens*layers*kv_heads*head_dim*cache_bits/8), factor twee voor keys en values. Geef die twee integerwaarden, minimum_bytes als hun som en minimum_mib=minimum_bytes/2**20 als float. Reken zonder zulke tensors te alloceren. Behoud invoer/RNG; ongeldige invoer geeft ValueError. Dit is theoretische payload, zonder metadata/activaties/runtimebuffers, geen voorspelling van bestandsgrootte of piek-RAM.

Een hint

Werk het handvoorbeeld na; houd eenheden, grenzen en behoud van de invoer expliciet.

02
Verifieer identiteit en maak een vaste cachekey

Schrijf verify_artifacts(manifest,blobs) voor kleine offline oefenbestandjes. Manifest is exact een dict met model_id,revision,dtype,files. Model_id is twee door / gescheiden IDs van elk 1–64 ASCII-tekens; elke ID begint alfanumeriek, rest alfanumeriek/underscore/punt/koppelteken. Revision is 40 kleine hextekens; dtype q4/q8/fp16/fp32. Files is een lijst van 1–8 dicts met exact name,bytes,sha256: unieke name volgens gegeven _dev_id, bytes Python-int 1–65536, sha256 64 kleine hextekens. Totaal maximaal 262144 bytes. Blobs is een dict met precies die namen en bytes/bytearraywaarden van exact de opgegeven lengte/hash. Weiger mismatch met ValueError, zonder downloaden/openen/uitvoeren. Voor de cachekey: maak een dict met model_id,revision,dtype,files (rijen op name gesorteerd); SHA-256 over UTF-8 van json.dumps(...,sort_keys=True,separators=(",",":")). Geef model_id,revision,dtype,verified_bytes,files (lexicografisch gesorteerde namen),cache_key. Behoud alle invoer/RNG. Een matchende hash bewijst overeenkomst met het opgegeven manifest, geen vertrouwen in de uitgever.

Een hint

Werk het handvoorbeeld na; houd eenheden, grenzen en behoud van de invoer expliciet.

03
Kies alleen een passend fictief profiel

Schrijf select_profile(profiles,budget). Profiles is een lijst/tuple van 1–16 dicts met unieke IDs; gebruik gegeven _dev_profile voor exacte keys, types en grenzen. Elk profiel heeft id,device,backend(wasm/webgpu),context en measurements. Context heeft input_tokens,output_tokens,cold; measurements download_bytes,peak_bytes,ttft_ms,tokens_per_second,quality. Budget is exact device,context,available_backends,limits: valideer device/context met de helpers, beschikbare backends als unieke lijst/tuple van 1–2 bekende namen. Limits heeft exact max_download_bytes,max_peak_bytes (Python-int 0–1e12), max_ttft_ms (reële scalar [0,1e9]), min_tokens_per_second ([0,1e6]), min_quality ([0,1]); geen bool voor getallen. Een profiel moet hetzelfde apparaat en alle contextvelden hebben, een beschikbare backend en aan alle vijf inclusieve grenzen voldoen. Geef chosen_id (of None), eligible_ids (lexicografisch gesorteerd) en rejected (alleen afgewezen IDs met een lijst geschonden codes device/context/backend/download/memory/ttft/throughput/quality; codevolgorde vrij, geen dubbelen). Rangschik passende profielen op hoogste quality, dan hoogste tokens_per_second, dan laagste ttft_ms, dan kleinste ID. Behoud invoer/RNG; ongeldige invoer geeft ValueError. Gebruik alleen de gegeven fictieve waarnemingen; geen echte apparaatprestatieclaim. Gegeven scalarvalidators controleren eerst het type, converteren naar Python-float en controleren daarna eindigheid en bereik van die geconverteerde waarde.

Een hint

Werk het handvoorbeeld na; houd eenheden, grenzen en behoud van de invoer expliciet.

Vastgelopen? Bekijk de voorbeeldoplossing
import numpy as np

import math

import hashlib

import json

import numbers

import re

# Jouw functies

def tensor_payload(parameters,weight_bits,tokens,layers,kv_heads,head_dim,cache_bits=16):
    parameters=_dev_int(parameters,1,10**9);weight_bits=_dev_int(weight_bits,4,32)
    if weight_bits not in (4,8,16,32):raise ValueError('Ondersteunde oefenbitbreedte vereist')
    tokens=_dev_int(tokens,0,32768);layers=_dev_int(layers,1,128);kv_heads=_dev_int(kv_heads,1,128);head_dim=_dev_int(head_dim,1,256)
    cache_bits=_dev_int(cache_bits,16,32)
    if cache_bits not in (16,32):raise ValueError('De oefencache gebruikt 16 of 32 bits')
    weights=(parameters*weight_bits+7)//8;cache=(2*tokens*layers*kv_heads*head_dim*cache_bits+7)//8
    return {'weight_payload_bytes':weights,'kv_payload_bytes':cache,'minimum_bytes':weights+cache,'minimum_mib':(weights+cache)/2**20}

def verify_artifacts(manifest,blobs):
    if type(manifest) is not dict or set(manifest)!={'model_id','revision','dtype','files'}:raise ValueError('Een volledig vast manifest vereist')
    model=manifest['model_id'];revision=manifest['revision'];dtype=manifest['dtype'];files=manifest['files']
    if type(model) is not str or re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9_.-]{0,63}/[A-Za-z0-9][A-Za-z0-9_.-]{0,63}',model) is None:raise ValueError('Een vaste modeleigenaar en modelnaam vereist')
    if type(revision) is not str or re.fullmatch('[0-9a-f]{40}',revision) is None:raise ValueError('Een volledige vaste revisie vereist')
    if type(dtype) is not str or dtype not in ('q4','q8','fp16','fp32'):raise ValueError('Een ondersteunde variant vereist')
    if type(files) is not list or not 1<=len(files)<=8 or type(blobs) is not dict:raise ValueError('Een tot acht lokale bestandjes vereist')
    rows=[];names=set();total=0
    for row in files:
        if type(row) is not dict or set(row)!={'name','bytes','sha256'}:raise ValueError('Bestandsnaam, grootte en hash vereist')
        name=_dev_id(row['name']);size=_dev_int(row['bytes'],1,65536);expected=row['sha256']
        if name in names or type(expected) is not str or re.fullmatch('[0-9a-f]{64}',expected) is None:raise ValueError('Unieke namen en volledige SHA-256 vereist')
        names.add(name);total+=size;rows.append({'name':name,'bytes':size,'sha256':expected})
    if total>262144 or set(blobs)!=names:raise ValueError('Begrensde complete bestandenset vereist')
    for row in rows:
        value=blobs[row['name']]
        if type(value) not in (bytes,bytearray) or len(value)!=row['bytes']:raise ValueError('Werkelijke grootte wijkt af')
        if hashlib.sha256(value).hexdigest()!=row['sha256']:raise ValueError('Werkelijke hash wijkt af')
    canonical={'model_id':model,'revision':revision,'dtype':dtype,'files':sorted(rows,key=lambda row:row['name'])}
    key=hashlib.sha256(json.dumps(canonical,sort_keys=True,separators=(',',':')).encode('utf-8')).hexdigest()
    return {'model_id':model,'revision':revision,'dtype':dtype,'verified_bytes':total,'files':sorted(names),'cache_key':key}

def select_profile(profiles,budget):
    if type(profiles) not in (list,tuple) or not 1<=len(profiles)<=16:raise ValueError('Een tot zestien oefenprofielen vereist')
    cleaned=[_dev_profile(profile) for profile in profiles]
    if len({row['id'] for row in cleaned})!=len(cleaned):raise ValueError('Unieke profiel-IDs vereist')
    if type(budget) is not dict or set(budget)!={'device','context','available_backends','limits'}:raise ValueError('Een volledig apparaatbudget vereist')
    device=_dev_id(budget['device']);context=_dev_context(budget['context']);backends=budget['available_backends'];limits=budget['limits']
    if type(backends) not in (list,tuple) or not 1<=len(backends)<=2 or any(type(value) is not str or value not in ('wasm','webgpu') for value in backends) or len(set(backends))!=len(backends):raise ValueError('Beschikbare bekende backends vereist')
    if type(limits) is not dict or set(limits)!={'max_download_bytes','max_peak_bytes','max_ttft_ms','min_tokens_per_second','min_quality'}:raise ValueError('Expliciete bovengrenzen en ondergrenzen vereist')
    caps={'max_download_bytes':_dev_int(limits['max_download_bytes'],0,10**12),'max_peak_bytes':_dev_int(limits['max_peak_bytes'],0,10**12),
        'max_ttft_ms':_dev_number(limits['max_ttft_ms'],0,10**9),'min_tokens_per_second':_dev_number(limits['min_tokens_per_second'],0,10**6),'min_quality':_dev_number(limits['min_quality'],0,1)}
    eligible=[];reasons={}
    for row in cleaned:
        m=row['measurements'];failed=[]
        for name,condition in [('device',row['device']!=device),('context',row['context']!=context),('backend',row['backend'] not in backends),
            ('download',m['download_bytes']>caps['max_download_bytes']),('memory',m['peak_bytes']>caps['max_peak_bytes']),
            ('ttft',m['ttft_ms']>caps['max_ttft_ms']),('throughput',m['tokens_per_second']<caps['min_tokens_per_second']),('quality',m['quality']<caps['min_quality'])]:
            if condition:failed.append(name)
        if failed:reasons[row['id']]=failed
        else:eligible.append(row)
    eligible.sort(key=lambda row:(-row['measurements']['quality'],-row['measurements']['tokens_per_second'],row['measurements']['ttft_ms'],row['id']))
    return {'chosen_id':eligible[0]['id'] if eligible else None,'eligible_ids':sorted(row['id'] for row in eligible),'rejected':reasons}

# Gegeven helpers

def _dev_int(value,low,high):
    if type(value) is not int or not low<=value<=high:raise ValueError('Begrensde Python-int vereist')
    return value

def _dev_number(value,low,high):
    if isinstance(value,bool) or not isinstance(value,numbers.Real):raise ValueError('Begrensde reële scalar vereist')
    try:number=float(value)
    except (OverflowError,ValueError,TypeError):raise ValueError('Begrensde reële scalar vereist') from None
    if not math.isfinite(number) or not low<=number<=high:raise ValueError('Begrensde reële scalar vereist')
    return number

def _dev_id(value):
    if type(value) is not str or re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9_.-]{0,63}',value) is None:raise ValueError('Korte vaste identificatie vereist')
    return value

def _dev_context(context):
    if type(context) is not dict or set(context)!={'input_tokens','output_tokens','cold'} or type(context['cold']) is not bool:raise ValueError('Gelijke gemeten prompt-, generatie- en cachecondities vereist')
    return {'input_tokens':_dev_int(context['input_tokens'],1,32768),'output_tokens':_dev_int(context['output_tokens'],1,4096),'cold':context['cold']}

def _dev_profile(profile):
    if type(profile) is not dict or set(profile)!={'id','device','backend','context','measurements'}:raise ValueError('Een volledig gemeten oefenprofiel vereist')
    name=_dev_id(profile['id']);device=_dev_id(profile['device']);backend=profile['backend'];context=_dev_context(profile['context']);m=profile['measurements']
    if type(backend) is not str or backend not in ('wasm','webgpu'):raise ValueError('Een bekende backend vereist')
    if type(m) is not dict or set(m)!={'download_bytes','peak_bytes','ttft_ms','tokens_per_second','quality'}:raise ValueError('Alle vijf waarnemingen vereist')
    measurements={'download_bytes':_dev_int(m['download_bytes'],0,10**12),'peak_bytes':_dev_int(m['peak_bytes'],1,10**12),
        'ttft_ms':_dev_number(m['ttft_ms'],0,10**9),'tokens_per_second':_dev_number(m['tokens_per_second'],.000001,10**6),'quality':_dev_number(m['quality'],0,1)}
    return {'id':name,'device':device,'backend':backend,'context':context,'measurements':measurements}

Voer de code uit en verklaar voor jezelf waarom elke stap werkt.