Skip to content

NeuroSMT solver #130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 53 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
3549fe8
implement stub for neurosmt
stephen-ostapenko Jul 5, 2023
dcce69b
set up gradle
stephen-ostapenko Jul 19, 2023
5f06f9d
SMT formula graph extractor
stephen-ostapenko Jul 19, 2023
4ae03cc
some fixes for extractor
stephen-ostapenko Jul 20, 2023
c5c7e71
another fix
stephen-ostapenko Jul 20, 2023
0236f09
fuck python
stephen-ostapenko Jul 21, 2023
13de179
yet another fix
stephen-ostapenko Jul 21, 2023
a6999eb
more fixes
stephen-ostapenko Jul 24, 2023
98dc958
aaaaaaaaaaaaa
stephen-ostapenko Jul 25, 2023
58791a4
train baseline
stephen-ostapenko Jul 26, 2023
fab60e8
train baseline
stephen-ostapenko Jul 26, 2023
deb0343
Merge remote-tracking branch 'origin/neurosmt-training' into neurosmt…
stephen-ostapenko Jul 26, 2023
829c45d
fix major bug
stephen-ostapenko Jul 27, 2023
9091a71
add roc-auc and more layers in decoder
stephen-ostapenko Jul 27, 2023
c5fdbc1
some refactoring
stephen-ostapenko Jul 27, 2023
dcb2c1e
sandboxing
stephen-ostapenko Jul 28, 2023
ce00214
add pytorch-lightning and tensorboard
stephen-ostapenko Aug 1, 2023
2ffa9b4
i forgot model
stephen-ostapenko Aug 1, 2023
3a689f8
adjust floating point precision
stephen-ostapenko Aug 1, 2023
09ef95a
add checkpointing
stephen-ostapenko Aug 1, 2023
6bfe3b6
refactor
stephen-ostapenko Aug 1, 2023
c787a29
fix backdoor
stephen-ostapenko Aug 1, 2023
ad9d65d
align sat/unsat sizes
stephen-ostapenko Aug 1, 2023
4ce4ad8
fix checkpointing
stephen-ostapenko Aug 1, 2023
ccf7177
add validation script
stephen-ostapenko Aug 1, 2023
02a959b
new logic of data loading
stephen-ostapenko Aug 2, 2023
0bdd180
adjust train/val scripts to new data loading process
stephen-ostapenko Aug 2, 2023
bc590f9
refactoring
stephen-ostapenko Aug 2, 2023
bbc3c5e
add ksmt binary files processing
stephen-ostapenko Aug 2, 2023
e0b644d
refactoring
stephen-ostapenko Aug 2, 2023
5546453
refactoring 2
stephen-ostapenko Aug 3, 2023
5c6bb72
train/test/val split with groups to avoid data leaks
stephen-ostapenko Aug 9, 2023
7893f94
commit for main split file
stephen-ostapenko Aug 10, 2023
40a483a
small refactor
stephen-ostapenko Aug 10, 2023
da66e8f
add confusion matrix for val and test
stephen-ostapenko Aug 10, 2023
24b933a
modify model input to save in onnx format
stephen-ostapenko Aug 11, 2023
7cb1af4
add kotlin runtime for model
stephen-ostapenko Aug 17, 2023
59f7839
fix bug in runtime and change conv type
stephen-ostapenko Aug 18, 2023
65d8bba
move python files to new module
stephen-ostapenko Aug 18, 2023
13b85fd
add node-wise features
stephen-ostapenko Aug 22, 2023
6b8a120
refactor
stephen-ostapenko Aug 22, 2023
9b777cb
update target type + additional metrics
stephen-ostapenko Aug 23, 2023
ff4dc62
refactor graph extractor and some other stuff
stephen-ostapenko Aug 23, 2023
8460715
add standalone model runner for hand benchmarking
stephen-ostapenko Aug 24, 2023
8b42598
add model export script
stephen-ostapenko Aug 24, 2023
5cbfbb8
add type hints, some comments and global constants in external file
stephen-ostapenko Aug 24, 2023
ce79d98
add model run time stats
stephen-ostapenko Aug 25, 2023
0cda4ac
add requirements.txt
stephen-ostapenko Aug 25, 2023
42ed4c5
add basic ksmt integration
stephen-ostapenko Aug 25, 2023
9320f96
add comments for kotlin code
stephen-ostapenko Aug 25, 2023
dd840ef
add model resource files
stephen-ostapenko Aug 25, 2023
63740f7
update code for usvm-formulas export
stephen-ostapenko Aug 25, 2023
9fd38c4
remove sandboxes
stephen-ostapenko Aug 31, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions ksmt-neurosmt/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
plugins {
id("io.ksmt.ksmt-base")
}

repositories {
mavenCentral()
}

dependencies {
implementation(project(":ksmt-core"))
implementation(project("utils"))

implementation("com.microsoft.onnxruntime:onnxruntime:1.15.1")
implementation("com.github.ajalt.clikt:clikt:3.5.2")
}

tasks {
val standaloneModelRunnerFatJar = register<Jar>("standaloneModelRunnerFatJar") {
dependsOn.addAll(listOf("compileJava", "compileKotlin", "processResources"))

archiveFileName.set("standalone-model-runner.jar")
destinationDirectory.set(File("."))
duplicatesStrategy = DuplicatesStrategy.EXCLUDE

manifest {
attributes(mapOf("Main-Class" to "io.ksmt.solver.neurosmt.runtime.standalone.StandaloneModelRunnerKt"))
}

val sourcesMain = sourceSets.main.get()
val contents = configurations.runtimeClasspath.get()
.map { if (it.isDirectory) it else zipTree(it) } + sourcesMain.output

from(contents)
}

build {
dependsOn(standaloneModelRunnerFatJar)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package io.ksmt.solver.neurosmt

import io.ksmt.KContext
import io.ksmt.expr.KExpr
import io.ksmt.solver.KModel
import io.ksmt.solver.KSolver
import io.ksmt.solver.KSolverStatus
import io.ksmt.solver.neurosmt.runtime.NeuroSMTModelRunner
import io.ksmt.sort.KBoolSort
import kotlin.time.Duration

class KNeuroSMTSolver(
private val ctx: KContext,
ordinalsPath: String, embeddingPath: String, convPath: String, decoderPath: String,
private val threshold: Double = 0.5
) : KSolver<KNeuroSMTSolverConfiguration> {

private val modelRunner = NeuroSMTModelRunner(ctx, ordinalsPath, embeddingPath, convPath, decoderPath)
private val asserts = mutableListOf<MutableList<KExpr<KBoolSort>>>(mutableListOf())

override fun configure(configurator: KNeuroSMTSolverConfiguration.() -> Unit) {
TODO("Not yet implemented")
}

override fun assert(expr: KExpr<KBoolSort>) {
asserts.last().add(expr)
}

override fun assertAndTrack(expr: KExpr<KBoolSort>) {
assert(expr)
}

override fun push() {
asserts.add(mutableListOf())
}

override fun pop(n: UInt) {
repeat(n.toInt()) {
asserts.removeLast()
}
}

override fun check(timeout: Duration): KSolverStatus {
val prob = with(ctx) {
modelRunner.run(mkAnd(asserts.flatten()))
}

return if (prob > threshold) {
KSolverStatus.SAT
} else {
KSolverStatus.UNSAT
}
}

override fun checkWithAssumptions(assumptions: List<KExpr<KBoolSort>>, timeout: Duration): KSolverStatus {
val prob = with(ctx) {
modelRunner.run(mkAnd(asserts.flatten() + assumptions))
}

return if (prob > threshold) {
KSolverStatus.SAT
} else {
KSolverStatus.UNSAT
}
}

override fun model(): KModel {
TODO("Not yet implemented")
}

override fun unsatCore(): List<KExpr<KBoolSort>> {
TODO("Not yet implemented")
}

override fun reasonOfUnknown(): String {
TODO("Not yet implemented")
}

override fun interrupt() {
TODO("Not yet implemented")
}

override fun close() {
modelRunner.close()
asserts.clear()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.ksmt.solver.neurosmt

import io.ksmt.solver.KSolverConfiguration

interface KNeuroSMTSolverConfiguration : KSolverConfiguration {
fun setOption(option: String, value: Boolean)
fun setOption(option: String, value: Int)
fun setOption(option: String, value: Double)
fun setOption(option: String, value: String)

override fun setBoolParameter(param: String, value: Boolean) {
setOption(param, value)
}

override fun setIntParameter(param: String, value: Int) {
setOption(param, value)
}

override fun setDoubleParameter(param: String, value: Double) {
setOption(param, value)
}

override fun setStringParameter(param: String, value: String) {
setOption(param, value)
}
}

class KNeuroSMTSolverConfigurationImpl(private val params: Any?) : KNeuroSMTSolverConfiguration {
override fun setOption(option: String, value: Boolean) {
TODO("Not yet implemented")
}

override fun setOption(option: String, value: Int) {
TODO("Not yet implemented")
}

override fun setOption(option: String, value: Double) {
TODO("Not yet implemented")
}

override fun setOption(option: String, value: String) {
TODO("Not yet implemented")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package io.ksmt.solver.neurosmt.runtime

import ai.onnxruntime.OnnxTensor
import ai.onnxruntime.OrtEnvironment
import io.ksmt.KContext
import io.ksmt.expr.KApp
import io.ksmt.expr.KConst
import io.ksmt.expr.KExpr
import io.ksmt.expr.KInterpretedValue
import io.ksmt.expr.transformer.KNonRecursiveTransformer
import io.ksmt.sort.*
import java.nio.FloatBuffer
import java.nio.IntBuffer
import java.nio.LongBuffer
import java.util.*

// expression encoder
// walks on an expression and calculates state for each node
// based on states for its children (which are already calculated at that moment)
class ExprEncoder(
override val ctx: KContext,
val env: OrtEnvironment,
val ordinalEncoder: OrdinalEncoder,
val embeddingLayer: ONNXModel,
val convLayer: ONNXModel
) : KNonRecursiveTransformer(ctx) {

private val exprToState = IdentityHashMap<KExpr<*>, OnnxTensor>()

fun encodeExpr(expr: KExpr<*>): OnnxTensor {
apply(expr)

return exprToState[expr] ?: error("expression state wasn't calculated yet")
}

override fun <T : KSort, A : KSort> transformApp(expr: KApp<T, A>): KExpr<T> {
val state = when (expr) {
is KConst<*> -> calcSymbolicVariableState(expr)
is KInterpretedValue<*> -> calcValueState(expr)
else -> calcAppState(expr)
}

exprToState[expr] = state

return expr
}

private fun getNodeEmbedding(key: String): OnnxTensor {
val nodeLabel = ordinalEncoder.getOrdinal(key)
val labelTensor = OnnxTensor.createTensor(
env, IntBuffer.allocate(1).put(nodeLabel).rewind(), longArrayOf(1, 1)
)

return embeddingLayer.forward(mapOf("node_labels" to labelTensor))
}

private fun createEdgeTensor(childrenCnt: Int): OnnxTensor {
val edges = listOf(
List(childrenCnt) { it + 1L },
List(childrenCnt) { 0L }
)

val buffer = LongBuffer.allocate(childrenCnt * 2)
edges.forEach { row ->
row.forEach { node ->
buffer.put(node)
}
}
buffer.rewind()

return OnnxTensor.createTensor(env, buffer, longArrayOf(2, childrenCnt.toLong()))
}

private fun <T : KSort, A : KSort> calcAppState(expr: KApp<T, A>): OnnxTensor {
val childrenStates = expr.args.map { exprToState[it] ?: error("expression state wasn't calculated yet") }
val childrenCnt = childrenStates.size

val nodeEmbedding = getNodeEmbedding(expr.decl.name)
val embeddingSize = nodeEmbedding.info.shape.reduce { acc, l -> acc * l }

val buffer = FloatBuffer.allocate((1 + childrenCnt) * embeddingSize.toInt())
buffer.put(nodeEmbedding.floatBuffer)
childrenStates.forEach {
buffer.put(it.floatBuffer)
}
buffer.rewind()

val nodeFeatures = OnnxTensor.createTensor(env, buffer, longArrayOf(1L + childrenCnt, embeddingSize))
val edges = createEdgeTensor(childrenStates.size)
val result = convLayer.forward(mapOf("node_features" to nodeFeatures, "edges" to edges))

return OnnxTensor.createTensor(env, result.floatBuffer.slice(0, embeddingSize.toInt()), longArrayOf(1L, embeddingSize))
}

private fun <T : KSort> calcSymbolicVariableState(symbol: KConst<T>): OnnxTensor {
val sort = symbol.decl.sort

val key = "SYMBOLIC;" + when (sort) {
is KBoolSort -> "Bool"
is KBvSort -> "BitVec"
is KFpSort -> "FP"
is KFpRoundingModeSort -> "FP_RM"
is KArraySortBase<*> -> "Array"
is KUninterpretedSort -> sort.name
else -> error("unknown symbolic sort: ${sort::class.simpleName}")
}

return getNodeEmbedding(key)
}

private fun <T : KSort> calcValueState(value: KInterpretedValue<T>): OnnxTensor {
val sort = value.decl.sort

val key = "VALUE;" + when (sort) {
is KBoolSort -> "Bool"
is KBvSort -> "BitVec"
is KFpSort -> "FP"
is KFpRoundingModeSort -> "FP_RM"
is KArraySortBase<*> -> "Array"
is KUninterpretedSort -> sort.name
else -> error("unknown value sort: ${value.decl.sort::class.simpleName}")
}

return getNodeEmbedding(key)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package io.ksmt.solver.neurosmt.runtime

import ai.onnxruntime.OrtEnvironment
import io.ksmt.KContext
import io.ksmt.expr.KExpr
import kotlin.math.exp

// wrapper for NeuroSMT model
// runs the whole model pipeline
class NeuroSMTModelRunner(
val ctx: KContext,
ordinalsPath: String, embeddingPath: String, convPath: String, decoderPath: String
) {
val env = OrtEnvironment.getEnvironment()

val ordinalEncoder = OrdinalEncoder(ordinalsPath)
val embeddingLayer = ONNXModel(env, embeddingPath)
val convLayer = ONNXModel(env, convPath)

val decoder = ONNXModel(env, decoderPath)

fun run(expr: KExpr<*>): Float {
val encoder = ExprEncoder(ctx, env, ordinalEncoder, embeddingLayer, convLayer)
val exprFeatures = encoder.encodeExpr(expr)
val result = decoder.forward(mapOf("expr_features" to exprFeatures))
val logit = result.floatBuffer[0]

return 1f / (1f + exp(-logit)) // sigmoid calculation
}

fun close() {
embeddingLayer.close()
convLayer.close()
decoder.close()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.ksmt.solver.neurosmt.runtime

import ai.onnxruntime.OnnxTensor
import ai.onnxruntime.OrtEnvironment

// wrapper for any exported ONNX model
class ONNXModel(env: OrtEnvironment, modelPath: String) : AutoCloseable {
val session = env.createSession(modelPath)

fun forward(input: Map<String, OnnxTensor>): OnnxTensor {
val result = session.run(input)
return result.get(0) as OnnxTensor
}

override fun close() {
session.close()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.ksmt.solver.neurosmt.runtime

import java.nio.file.Files
import java.nio.file.Path
import kotlin.streams.asSequence

const val UNKNOWN_VALUE = 1999

// wrapper for single-feature sklearn OrdinalEncoder (for each string we should provide its ordinal)
// used to convert strings to integers
class OrdinalEncoder(ordinalsPath: String, private val unknownValue: Int = UNKNOWN_VALUE) {
private val lookup = HashMap<String, Int>()

init {
Files.lines(Path.of(ordinalsPath)).asSequence().forEachIndexed { index, s ->
lookup[s] = index
}
}

fun getOrdinal(s: String): Int {
return lookup[s] ?: unknownValue
}
}
Loading