Skip to content

Commit

Permalink
opml
Browse files Browse the repository at this point in the history
  • Loading branch information
0x1cc committed Jul 4, 2023
0 parents commit 1ca4e25
Show file tree
Hide file tree
Showing 41 changed files with 7,456 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
19 changes: 19 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
node_modules
artifacts
cache
.*.swp
venv
.idea
*.log

examples/mnist/trainning/data/MNIST/raw

mlgo
mlgo.bin

examples/llama/llama

ml_mips/ml_mips
ml_mips/ml_mips.bin

examples/llama/data
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# MLGO

MLGO is tensor library for machine learning in pure Golang that can run on MIPS.

The machine learning part of this project refers to the legendary [ggml.cpp](https://github.com/ggerganov/ggml) framework.


## MNIST

1. Train the AI model. See `examples/mnist/trainning/mnist.ipynb`
2. Convert the AI model into GGML using `examples/mnist/convert-h5-to-ggml.py`
3. Build the AI inference engine for MIPS
`cd examples/mnist_mips && ./build`
``
15 changes: 15 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -e

export GOOS=linux
export GOARCH=mips
export GOMIPS=softfloat
go build -o ./mlgo

file mlgo

if [[ ! -d venv ]]; then
python3 -m venv venv
fi

./compile.py mlgo
51 changes: 51 additions & 0 deletions common/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package common

import (
"math"
"os"
"unsafe"
)

// NB! INT = 32 bits
func ReadInt32FromFile(file *os.File) uint32 {
buf := make([]byte, 4)
if count, err := file.Read(buf); err != nil || count != 4 {
return 0
}
return uint32(buf[3])<<24 | uint32(buf[2])<<16 | uint32(buf[1])<<8 | uint32(buf[0])
}

func ReadStringFromFile(file *os.File, len uint32) string {
buf := make([]byte, len)
if count, err := file.Read(buf); err != nil || count != int(len) {
return ""
}
return string(buf)
}


func ReadFP32FromFile(file *os.File) float32 {
buf := make([]byte, 4)
if count, err := file.Read(buf); err != nil || count != 4 {
return 0.0
}
bits := uint32(buf[3])<<24 | uint32(buf[2])<<16 | uint32(buf[1])<<8 | uint32(buf[0])
return math.Float32frombits(bits)
}

func min(a, b int) int {
if a <= b {
return a
}
return b
}



func DecodeFloat32List(bs []byte) []float32 {
return unsafe.Slice((*float32)(unsafe.Pointer(&bs[0])), len(bs)/4)
}

func EncodeFloat32List(fs []float32) []byte {
return unsafe.Slice((*byte)(unsafe.Pointer(&fs[0])), len(fs)*4)
}
140 changes: 140 additions & 0 deletions common/vmutils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package common

import (
"bytes"
"encoding/binary"
"os"
"reflect"
"unsafe"
)

// vm only ===================================================================================

// memory layout in MIPS
const (
INPUT_ADDR = 0x31000000
OUTPUT_ADDR = 0x32000000
MODEL_ADDR = 0x33000000
MAGIC_ADDR = 0x30000800
)

func ByteAt(addr uint64, length int) []byte {
var ret []byte
bh := (*reflect.SliceHeader)(unsafe.Pointer(&ret))
bh.Data = uintptr(addr)
bh.Len = length
bh.Cap = length
return ret
}

// reading bytes from bigEndian or littleEndian
func ReadBytes(addr uint64, isBigEndian bool) []byte {
rawSize := CopyBytes(ByteAt(addr, 4))
size := BytesToInt32(rawSize, isBigEndian)
ret := ByteAt(addr + 4, int(size))
//shoud we copy here? may not for saving memory
return ret
}

func Halt() {
//os.Stderr.WriteString("THIS SHOULD BE PATCHED OUT\n")
// the exit syscall is a jump to 0x5ead0000 now
os.Exit(0)
}

func Output(output []byte, isBigEndian bool) {
size := len(output)
rawSize := IntToBytes(size,isBigEndian)
mSize := ByteAt(OUTPUT_ADDR, 4)
copy(mSize, rawSize)
mData := ByteAt(OUTPUT_ADDR + 4, size)
copy(mData, output)
// magic code => have written the result
magic := ByteAt(MAGIC_ADDR, 4)
copy(magic, []byte{0x12, 0x34, 0x56, 0x78})
// stop everything
Halt()
}


func IntToBytes(n int, isBigEndian bool) []byte {
x := int32(n)

bytesBuffer := bytes.NewBuffer([]byte{})
if isBigEndian {
binary.Write(bytesBuffer, binary.BigEndian, x)
} else {
binary.Write(bytesBuffer, binary.LittleEndian, x)
}
return bytesBuffer.Bytes()
}

func BytesToInt32(b []byte, isBigEndian bool) int32 {
bytesBuffer := bytes.NewBuffer(b)

var x int32
if isBigEndian {
binary.Read(bytesBuffer, binary.BigEndian, &x)
} else {
binary.Read(bytesBuffer, binary.LittleEndian, &x)
}


return x
}

func Float32ToBytes(x float32, isBigEndian bool) []byte {
bytesBuffer := bytes.NewBuffer([]byte{})
if isBigEndian {
binary.Write(bytesBuffer, binary.BigEndian, x)
} else {
binary.Write(bytesBuffer, binary.LittleEndian, x)
}
return bytesBuffer.Bytes()
}

func BytesToFloat32(b []byte, isBigEndian bool) float32 {
byteBuffer := bytes.NewBuffer(b)
var x float32
if isBigEndian {
binary.Read(byteBuffer, binary.BigEndian, &x)
} else {
binary.Read(byteBuffer, binary.LittleEndian, &x)
}

return x
}

// CopyBytes returns an exact copy of the provided bytes.
func CopyBytes(b []byte) (copiedBytes []byte) {
if b == nil {
return nil
}
copiedBytes = make([]byte, len(b))
copy(copiedBytes, b)

return
}

// read from index then return the result and the next index
func ReadInt32FromBytes(data []byte, index *int, isBigEndian bool) (uint32) {
if (*index + 4 > len(data)) {
*index = len(data)
return 0
}
buf := CopyBytes(data[*index:*index+4])
ret := BytesToInt32(buf, isBigEndian)
*index = *index + 4
return uint32(ret)
}

func ReadFP32FromBytes(data []byte, index *int, isBigEndian bool) (float32) {
if (*index + 4 > len(data)) {
*index = len(data)
return 0
}
buf := CopyBytes(data[*index:*index+4])
ret := BytesToFloat32(buf, isBigEndian)
*index = *index + 4
return ret
}
49 changes: 49 additions & 0 deletions common/vmutils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package common

import (
"fmt"
"testing"
"unsafe"
)

func TestByteFloat(t *testing.T){
a := 1.234
ab := Float32ToBytes(float32(a), true)
aa := BytesToFloat32(ab, true)
fmt.Println(a, ab, aa)
}

func byteSliceToFloat32Slice(src []byte) []float32 {
if len(src) == 0 {
return nil
}

l := len(src) / 4
ptr := unsafe.Pointer(&src[0])
// It is important to keep in mind that the Go garbage collector
// will not interact with this data, and that if src if freed,
// the behavior of any Go code using the slice is nondeterministic.
// Reference: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
return (*[1 << 26]float32)((*[1 << 26]float32)(ptr))[:l:l]
}

func encodeUnsafe(fs []float32) []byte {
return unsafe.Slice((*byte)(unsafe.Pointer(&fs[0])), len(fs)*4)
}

func decodeUnsafe(bs []byte) []float32 {
return unsafe.Slice((*float32)(unsafe.Pointer(&bs[0])), len(bs)/4)
}

func TestByteSliceToFloat32Slice(t *testing.T) {
as := []float32{1.234, 2.345}
asBytes := make([]byte, 0)
for i := 0; i < len(as); i++ {
asBytes = append(asBytes, Float32ToBytes(as[i], false)...)
}
fmt.Println(asBytes)
fmt.Println(byteSliceToFloat32Slice(asBytes))
fmt.Println(encodeUnsafe(as))
fmt.Println(decodeUnsafe(encodeUnsafe(as)))
fmt.Println(decodeUnsafe(asBytes))
}
73 changes: 73 additions & 0 deletions compile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
import os
import sys
import struct
import hashlib
from rangetree import RangeTree
from elftools.elf.elffile import ELFFile

def load_minigeth(fn="mlgo"):
elf = open(fn, "rb")
data = elf.read()
elf.seek(0)

elffile = ELFFile(elf)

end_addr = 0
for seg in elffile.iter_segments():
end_addr = max(end_addr, seg.header.p_vaddr + seg.header.p_memsz)

# program memory (16 MB)
prog_size = (end_addr+0xFFF) & ~0xFFF
prog_dat = bytearray(prog_size)
print("malloced 0x%x for program" % prog_size)

for seg in elffile.iter_segments():
print(seg.header, hex(seg.header.p_vaddr))
prog_dat[seg.header.p_vaddr:seg.header.p_vaddr+len(seg.data())] = seg.data()

entry = elffile.header.e_entry
print("entrypoint: 0x%x" % entry)

# moved to MIPS
sf = os.path.join(os.path.dirname(os.path.abspath(__file__)), "startup", "startup.bin")
start = open(sf, "rb").read() + struct.pack(">I", entry)
prog_dat[:len(start)] = start
entry = 0

r = RangeTree()
found = 0
for section in elffile.iter_sections():
try:
for nsym, symbol in enumerate(section.iter_symbols()):
ss = symbol['st_value']
se = ss+symbol['st_size']
if ss != se:
try:
r[ss:se] = symbol.name
except KeyError:
continue
#print(nsym, symbol.name, symbol['st_value'], symbol['st_size'])
if symbol.name == "runtime.gcenable":
print(nsym, symbol.name)
# nop gcenable
prog_dat[symbol['st_value']:symbol['st_value']+8] = b"\x03\xe0\x00\x08\x00\x00\x00\x00"
found += 1
except Exception:
#traceback.print_exc()
pass

#assert(found == 2)
return prog_dat, prog_size, r


if __name__ == "__main__":
fn = "minigeth"
if len(sys.argv) > 1:
fn = sys.argv[1]

prog_dat, prog_size, _ = load_minigeth(fn)
print("compiled %d bytes with md5 %s" % (prog_size, hashlib.md5(prog_dat).hexdigest()))

with open(fn+".bin", "wb") as f:
f.write(prog_dat)
Loading

0 comments on commit 1ca4e25

Please sign in to comment.