mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
173 lines
5.5 KiB
Go
173 lines
5.5 KiB
Go
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
// module.go — the AI-inference EVM precompile (0x0303), shared by the Lux/Zoo/Hanzo
|
|
// EVMs. It runs a deterministic int8 transformer (inference.go) entirely in pure Go on
|
|
// the consensus path — every validator computes byte-identical logits/tokens with no GPU
|
|
// required. The lux-private GPU kernels are a KAT-proven byte-equal accelerator; the
|
|
// canonical result is always this Go reference, so consensus can never fork on hardware.
|
|
//
|
|
// MVP scope: the model is the built-in frozen reference (fixtureModel). On-chain model
|
|
// provisioning (a model registry keyed by weight-commitment hash) is the next step; the
|
|
// ABI is selector-based so it extends cleanly.
|
|
|
|
package inference
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/precompile/contract"
|
|
"github.com/luxfi/precompile/modules"
|
|
"github.com/luxfi/precompile/precompileconfig"
|
|
)
|
|
|
|
var _ contract.Configurator = (*configurator)(nil)
|
|
var _ contract.StatefulPrecompiledContract = (*InferenceContract)(nil)
|
|
|
|
// ConfigKey is the json key for this precompile's upgrade config.
|
|
const ConfigKey = "aiInferenceConfig"
|
|
|
|
// ContractAddress — AI Mining reserved range is 0x0300…0000 .. 0x0300…00FF (the
|
|
// significant byte is 0x03 at the front; the slot is the LAST byte). 0x0300…0000 is
|
|
// AI_MINING; AI_INFERENCE takes slot 0x03 → 0x0300…0003.
|
|
var ContractAddress = common.HexToAddress("0x0300000000000000000000000000000000000003")
|
|
|
|
// InferencePrecompile is the singleton.
|
|
var InferencePrecompile = &InferenceContract{}
|
|
|
|
// Module registers the precompile.
|
|
var Module = modules.Module{
|
|
ConfigKey: ConfigKey,
|
|
Address: ContractAddress,
|
|
Contract: InferencePrecompile,
|
|
Configurator: &configurator{},
|
|
}
|
|
|
|
// Method selectors (first 4 bytes).
|
|
const (
|
|
// generate(uint32 nNew, uint32[] promptTokens) -> uint32[] (prompt+generated)
|
|
SelectorGenerate uint32 = 0x01000000
|
|
)
|
|
|
|
// Gas costs. Inference is heavy and deterministic; gas is a pure function of the
|
|
// requested new-token count (the built-in model's dims are fixed in the MVP).
|
|
const (
|
|
GasBaseInference uint64 = 100_000
|
|
GasPerNewToken uint64 = 50_000
|
|
MaxNewTokens uint32 = 64
|
|
MaxSeqLen int = 256
|
|
)
|
|
|
|
type configurator struct{}
|
|
|
|
func init() {
|
|
if err := modules.RegisterModule(Module); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (*configurator) MakeConfig() precompileconfig.Config { return new(Config) }
|
|
|
|
func (*configurator) Configure(
|
|
_ precompileconfig.ChainConfig,
|
|
_ precompileconfig.Config,
|
|
_ contract.StateDB,
|
|
_ contract.ConfigurationBlockContext,
|
|
) error {
|
|
return nil // no state initialization
|
|
}
|
|
|
|
// Config is the upgrade config for the precompile.
|
|
type Config struct {
|
|
Upgrade precompileconfig.Upgrade `json:"upgrade"`
|
|
}
|
|
|
|
func (c *Config) Key() string { return ConfigKey }
|
|
func (c *Config) Timestamp() *uint64 { return c.Upgrade.Timestamp() }
|
|
func (c *Config) IsDisabled() bool { return c.Upgrade.Disable }
|
|
func (c *Config) Verify(precompileconfig.ChainConfig) error { return nil }
|
|
|
|
func (c *Config) Equal(cfg precompileconfig.Config) bool {
|
|
other, ok := cfg.(*Config)
|
|
if !ok {
|
|
return false
|
|
}
|
|
return c.Upgrade.Equal(&other.Upgrade)
|
|
}
|
|
|
|
// InferenceContract implements the AI-inference precompile.
|
|
type InferenceContract struct{}
|
|
|
|
// RequiredGas is a deterministic function of the input (selector + requested nNew).
|
|
func (c *InferenceContract) RequiredGas(input []byte) uint64 {
|
|
if len(input) < 8 {
|
|
return GasBaseInference
|
|
}
|
|
nNew := binary.BigEndian.Uint32(input[4:8])
|
|
if nNew > MaxNewTokens {
|
|
nNew = MaxNewTokens
|
|
}
|
|
return GasBaseInference + uint64(nNew)*GasPerNewToken
|
|
}
|
|
|
|
// Run executes the precompile (pure compute; no state access).
|
|
func (c *InferenceContract) Run(
|
|
_ contract.AccessibleState,
|
|
_ common.Address,
|
|
_ common.Address,
|
|
input []byte,
|
|
suppliedGas uint64,
|
|
_ bool,
|
|
) (ret []byte, remainingGas uint64, err error) {
|
|
gas := c.RequiredGas(input)
|
|
if suppliedGas < gas {
|
|
return nil, 0, fmt.Errorf("out of gas: need %d have %d", gas, suppliedGas)
|
|
}
|
|
remainingGas = suppliedGas - gas
|
|
|
|
if len(input) < 4 {
|
|
return nil, remainingGas, fmt.Errorf("input too short")
|
|
}
|
|
selector := binary.BigEndian.Uint32(input[:4])
|
|
if selector != SelectorGenerate {
|
|
return nil, remainingGas, fmt.Errorf("unknown selector %#x", selector)
|
|
}
|
|
if len(input) < 12 { // selector(4) + nNew(4) + >=1 token(4)
|
|
return nil, remainingGas, fmt.Errorf("input too short for generate")
|
|
}
|
|
nNew := binary.BigEndian.Uint32(input[4:8])
|
|
body := input[8:]
|
|
if len(body)%4 != 0 {
|
|
return nil, remainingGas, fmt.Errorf("prompt not a multiple of 4 bytes")
|
|
}
|
|
promptLen := len(body) / 4
|
|
if promptLen < 1 {
|
|
return nil, remainingGas, fmt.Errorf("empty prompt")
|
|
}
|
|
if nNew > MaxNewTokens {
|
|
return nil, remainingGas, fmt.Errorf("nNew %d exceeds max %d", nNew, MaxNewTokens)
|
|
}
|
|
if promptLen+int(nNew) > MaxSeqLen {
|
|
return nil, remainingGas, fmt.Errorf("sequence %d exceeds max %d", promptLen+int(nNew), MaxSeqLen)
|
|
}
|
|
|
|
model := fixtureModel() // MVP: built-in reference model
|
|
prompt := make([]int32, promptLen)
|
|
for i := 0; i < promptLen; i++ {
|
|
tok := binary.BigEndian.Uint32(body[i*4 : i*4+4])
|
|
if tok >= model.Cfg.Vocab {
|
|
return nil, remainingGas, fmt.Errorf("token %d out of vocab %d", tok, model.Cfg.Vocab)
|
|
}
|
|
prompt[i] = int32(tok)
|
|
}
|
|
|
|
out := model.Generate(prompt, nNew)
|
|
ret = make([]byte, len(out)*4)
|
|
for i, t := range out {
|
|
binary.BigEndian.PutUint32(ret[i*4:i*4+4], uint32(t))
|
|
}
|
|
return ret, remainingGas, nil
|
|
}
|