Graphene CLI + quantization research (#1320)

* Implement skeleton for graphene-cli

* Configure gpu surface on non wasm32 targets

* Create window with full hd size

* Create window using the graphen-cli

* Use window size for surface creation

* Reuse surface configuration

* Reduce window size for native applications to 800x600

* Add compute pipeline test

* Poll wgpu execution externally

* Remove cache node after texture upload

* Add profiling instructions

* Add more debug markers

* Evaluate extract node before flattening the network

* Reenable hue saturation node for compilation

* Make hue saturation node work on the gpu + make f32 default for user inputs

* Add version of test files without caching

* Only dispatch each workgroup not pixel

* ICE

* Add quantization to gpu code

* Fix quantization

* Load images at graph runtime

* Fix quantization calculation

* Feature gate quantization

* Use git version of autoquant

* Add license to `graphene-cli`

* Fix graphene-cli test case

* Ignore tests on non unix platforms

* Fix flattening test
This commit is contained in:
Dennis Kobert
2023-07-04 17:04:09 +02:00
committed by GitHub
parent 19318a948c
commit f2b911b358
57 changed files with 10169 additions and 845 deletions

View File

@@ -0,0 +1,62 @@
[package]
name = "graphene-cli"
version = "0.1.0"
edition = "2021"
description = "CLI interface for the graphene language"
authors = ["Graphite Authors <contact@graphite.rs>"]
license = "MIT OR Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
gpu = [
"interpreted-executor/gpu",
"graphene-std/gpu",
"graphene-core/gpu",
"wgpu-executor",
"gpu-executor",
]
default = ["wgpu"]
wgpu = ["wgpu-executor", "gpu", "graphene-std/wgpu"]
wayland = ["graphene-std/wayland"]
profiling = ["wgpu-executor/profiling"]
passthrough = ["wgpu-executor/passthrough"]
quantization = ["graphene-std/quantization"]
[dependencies]
log = "0.4"
bitflags = "1.2.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }
bezier-rs = { path = "../../libraries/bezier-rs" }
glam = { version = "0.22", features = ["serde"] }
# Node graph
graphene-std = { path = "../gstd" }
image = { version = "0.24", default-features = false, features = [
"bmp",
"png",
] }
graph-craft = { path = "../graph-craft" }
wgpu-executor = { path = "../wgpu-executor", optional = true }
gpu-executor = { path = "../gpu-executor", optional = true }
interpreted-executor = { path = "../interpreted-executor" }
dyn-any = { path = "../../libraries/dyn-any" }
graphene-core = { path = "../gcore" }
future-executor = { path = "../future-executor", optional = true }
wasm-bindgen = { version = "0.2.86", optional = true }
futures = "0.3.28"
fern = { version = "0.6.2", features = ["colored"] }
chrono = "0.4.26"
tokio = { version = "1.28.2", features = ["macros", "rt"] }
wgpu = "0.16.1"
[dependencies.document-legacy]
path = "../../document-legacy"
package = "graphite-document-legacy"
[dev-dependencies]
env_logger = "0.8.4"
test-case = "2.1"

View File

@@ -0,0 +1,234 @@
use fern::colors::{Color, ColoredLevelConfig};
use std::{collections::HashMap, error::Error, sync::Arc};
use document_legacy::{
document::Document,
layers::layer_info::{LayerData, LayerDataType},
};
use futures::executor::block_on;
use graph_craft::{
concrete,
document::{value::TaggedValue, *},
graphene_compiler::{Compiler, Executor},
imaginate_input::ImaginatePreferences,
NodeIdentifier, Type, TypeDescriptor,
};
use graphene_core::{
application_io::{self, ApplicationIo, NodeGraphUpdateSender},
raster::ImageFrame,
text::FontCache,
Cow,
};
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
use interpreted_executor::dynamic_executor::DynamicExecutor;
struct UpdateLogger {}
impl NodeGraphUpdateSender for UpdateLogger {
fn send(&self, message: graphene_core::application_io::NodeGraphUpdateMessage) {
println!("{:?}", message);
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
init_logging();
let document_path = std::env::args().nth(1).expect("No document path provided");
let image_path = std::env::args().nth(2);
let document_string = std::fs::read_to_string(&document_path).expect("Failed to read document");
let executor = create_executor(document_string)?;
println!("creating gpu context",);
let mut application_io = block_on(WasmApplicationIo::new());
if let Some(image_path) = image_path {
application_io.resources.insert("null".to_string(), Arc::from(std::fs::read(image_path).expect("Failed to read image")));
}
let device = application_io.gpu_executor().unwrap().context.device.clone();
std::thread::spawn(move || loop {
std::thread::sleep(std::time::Duration::from_nanos(10));
device.poll(wgpu::Maintain::Poll);
});
let editor_api = WasmEditorApi {
image_frame: None,
font_cache: &FontCache::default(),
application_io: &application_io,
node_graph_message_sender: &UpdateLogger {},
imaginate_preferences: &ImaginatePreferences::default(),
};
loop {
//println!("executing");
let result = (&executor).execute(editor_api.clone()).await?;
//println!("result: {:?}", result);
std::thread::sleep(std::time::Duration::from_millis(16));
}
Ok(())
}
fn init_logging() {
let colors = ColoredLevelConfig::new().debug(Color::Magenta).info(Color::Green).error(Color::Red);
fern::Dispatch::new()
.chain(std::io::stdout())
.level_for("iced", log::LevelFilter::Trace)
.level_for("wgpu", log::LevelFilter::Debug)
.level(log::LevelFilter::Trace)
.format(move |out, message, record| {
out.finish(format_args!(
"[{}]{} {}",
// This will color the log level only, not the whole line. Just a touch.
colors.color(record.level()),
chrono::Utc::now().format("[%Y-%m-%d %H:%M:%S]"),
message
))
})
.apply()
.unwrap();
}
fn create_executor(document_string: String) -> Result<DynamicExecutor, Box<dyn Error>> {
let document: serde_json::Value = serde_json::from_str(&document_string).expect("Failed to parse document");
let document = serde_json::from_value::<Document>(document["document_legacy"].clone()).expect("Failed to parse document");
let Some(LayerDataType::Layer(ref node_graph)) = document.root.iter().find(|layer| matches!(layer.data, LayerDataType::Layer(_))).map(|x|&x.data) else { panic!("failed to extract node graph from docmuent") };
let network = &node_graph.network;
let wrapped_network = wrap_network_in_scope(network.clone());
let compiler = Compiler {};
let protograph = compiler.compile_single(wrapped_network, true)?;
let executor = block_on(DynamicExecutor::new(protograph))?;
Ok(executor)
}
#[cfg(test)]
mod test {
use super::*;
#[tokio::test]
#[cfg_attr(not(feature = "wayland"), ignore)]
async fn grays_scale() {
let document_string = include_str!("../test_files/gray.graphite");
let executor = create_executor(document_string.to_string()).unwrap();
let editor_api = WasmEditorApi {
image_frame: None,
font_cache: &FontCache::default(),
application_io: &block_on(WasmApplicationIo::new()),
node_graph_message_sender: &UpdateLogger {},
imaginate_preferences: &ImaginatePreferences::default(),
};
let result = (&executor).execute(editor_api.clone()).await.unwrap();
println!("result: {:?}", result);
}
#[tokio::test]
#[cfg_attr(not(feature = "wayland"), ignore)]
async fn hue() {
let document_string = include_str!("../test_files/hue.graphite");
let executor = create_executor(document_string.to_string()).unwrap();
let editor_api = WasmEditorApi {
image_frame: None,
font_cache: &FontCache::default(),
application_io: &block_on(WasmApplicationIo::new()),
node_graph_message_sender: &UpdateLogger {},
imaginate_preferences: &ImaginatePreferences::default(),
};
let result = (&executor).execute(editor_api.clone()).await.unwrap();
println!("result: {:?}", result);
}
}
pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork {
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
network.generate_node_paths(&[]);
for id in node_ids {
network.flatten(id);
}
let mut network_inputs = Vec::new();
let mut input_type = None;
for (id, node) in network.nodes.iter() {
for input in node.inputs.iter() {
if let NodeInput::Network(_) = input {
if input_type.is_none() {
input_type = Some(input.clone());
}
assert_eq!(input, input_type.as_ref().unwrap(), "Networks wrapped in scope must have the same input type");
network_inputs.push(*id);
}
}
}
let len = network_inputs.len();
network.inputs = network_inputs;
// if the network has no inputs, it doesn't need to be wrapped in a scope
if len == 0 {
return network;
}
let inner_network = DocumentNode {
name: "Scope".to_string(),
implementation: DocumentNodeImplementation::Network(network),
inputs: core::iter::repeat(NodeInput::node(0, 1)).take(len).collect(),
..Default::default()
};
// wrap the inner network in a scope
let nodes = vec![
begin_scope(),
inner_network,
DocumentNode {
name: "End Scope".to_string(),
implementation: DocumentNodeImplementation::proto("graphene_core::memo::EndLetNode<_>"),
inputs: vec![NodeInput::node(0, 0), NodeInput::node(1, 0)],
..Default::default()
},
];
NodeNetwork {
inputs: vec![0],
outputs: vec![NodeOutput::new(2, 0)],
nodes: nodes.into_iter().enumerate().map(|(id, node)| (id as NodeId, node)).collect(),
..Default::default()
}
}
fn begin_scope() -> DocumentNode {
DocumentNode {
name: "Begin Scope".to_string(),
implementation: DocumentNodeImplementation::Network(NodeNetwork {
inputs: vec![0],
outputs: vec![NodeOutput::new(1, 0), NodeOutput::new(2, 0)],
nodes: [
DocumentNode {
name: "SetNode".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(WasmEditorApi))],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::SomeNode")),
..Default::default()
},
DocumentNode {
name: "LetNode".to_string(),
inputs: vec![NodeInput::node(0, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::LetNode<_>")),
..Default::default()
},
DocumentNode {
name: "RefNode".to_string(),
inputs: vec![NodeInput::ShortCircut(concrete!(())), NodeInput::lambda(1, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::memo::RefNode<_, _>")),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (id as NodeId, node))
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::Network(concrete!(WasmEditorApi))],
..Default::default()
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

File diff suppressed because it is too large Load Diff