Skip to content
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

Feat/synth extension #773

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 11 additions & 3 deletions frontends/concrete-python/concrete/fhe/extensions/bits.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def __getitem__(self, index: Union[int, np.integer, slice]) -> Tracer:

def evaluator(x, bits): # pylint: disable=redefined-outer-name
if isinstance(bits, (int, np.integer)):
return (x & (1 << bits)) >> bits
return (x >> bits) & 1

assert isinstance(bits, slice)

Expand All @@ -126,16 +126,24 @@ def evaluator(x, bits): # pylint: disable=redefined-outer-name

result = 0
for i, bit in enumerate(range(start, stop, step)):
value = (x & (1 << bit)) >> bit
value = (x >> bit) & 1
result += value << i

return result

if isinstance(self.value, Tracer):
output_value = deepcopy(self.value.output)
direct_single_bit = (
Tracer._is_direct
and isinstance(index, int)
and isinstance(output_value.dtype, Integer)
)
if direct_single_bit:
output_value.dtype.bit_width = 1 # type: ignore[attr-defined]
computation = Node.generic(
"extract_bit_pattern",
[deepcopy(self.value.output)],
deepcopy(self.value.output),
output_value,
evaluator,
kwargs={"bits": index},
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
http://yowasp.org/
rudy-6-4 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""Provide synthesis main entry points."""
from .api import lut, verilog_expression, verilog_module
125 changes: 125 additions & 0 deletions frontends/concrete-python/concrete/fhe/extensions/synthesis/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"""
EXPERIMENTAL extension to synthesize a fhe compatible function from verilog code.

The resulting object can be used directly as a python function.
For instance you can write a relu function using:

out = fhe.int5
params = {"a": out}
rudy-6-4 marked this conversation as resolved.
Show resolved Hide resolved
expression = "(a >= 0) ? a : 0"

relu = synth.verilog_expression(params, expression, out)
rudy-6-4 marked this conversation as resolved.
Show resolved Hide resolved

@fhe.circuit({"a": "encrypted"})
def circuit(a: out):
rudy-6-4 marked this conversation as resolved.
Show resolved Hide resolved
return relu(a=a)

"""
from collections import Counter

from concrete.fhe.extensions.synthesis.luts_to_fhe import tlu_circuit_to_fhe
from concrete.fhe.extensions.synthesis.luts_to_graph import to_graph
from concrete.fhe.extensions.synthesis.verilog_source import (
Ty,
verilog_from_expression,
verilog_from_tlu,
)
from concrete.fhe.extensions.synthesis.verilog_to_luts import yosys_lut_synthesis
Comment on lines +20 to +27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use relative imports:

from .module import xyz
from ..module import abc



class FheFunction:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you define properties of this class:

foo: Bar
baz: Abc

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Same with other classes)

"""Main class to synthesize verilog to tracer function."""

def __init__(
self,
*,
verilog,
name,
params=None,
yosys_dot_file=False,
verbose=False,
):
self.name = name
self.verilog = verilog
if verbose:
print()
print(f"Verilog, {name}:")
print(verilog)
print()
if verbose:
print("Synthesis")
Comment on lines +44 to +50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not how we print in verbose mode. Could you do something similar to this?

print("Computation Graph")
print("-" * columns)
print(self.graph.format())
print("-" * columns)

self.circuit = yosys_lut_synthesis(
verilog, yosys_dot_file=yosys_dot_file, circuit_name=name
)
if verbose:
print()
print(f"TLUs counts, {self.tlu_counts()}:")
print()
self.params = params
self.tracer = tlu_circuit_to_fhe(self.circuit, self.params, verbose)

def __call__(self, **kwargs):
"""Call the tracer function."""
return self.tracer(**kwargs)

def tlu_counts(self):
"""Count the number of tlus in the synthesized tracer keyed by input precision."""
counter = Counter()
for node in self.circuit.nodes:
if len(node.arguments) == 1:
print(node)
counter.update({len(node.arguments): 1})
return dict(sorted(counter.items()))

def graph(self, *, filename=None, view=True, **kwargs):
"""Render the synthesized tracer as a graph."""
graph = to_graph(self.name, self.circuit.nodes)
graph.render(filename=filename, view=view, cleanup=filename is None, **kwargs)


def lut(table, out_type=None, **kwargs):
"""Synthesize a lookup function from a table."""
# assert not signed # TODO signed case
if isinstance(out_type, list):
msg = "Multi-message output is not supported"
raise TypeError(msg)
if out_type:
out_type = Ty(
bit_width=out_type.dtype.bit_width,
is_signed=out_type.dtype.is_signed,
)
verilog, out_type = verilog_from_tlu(table, signed_input=False, out_type=out_type)
if "name" not in kwargs:
kwargs.setdefault("name", "lut")
return FheFunction(verilog=verilog, **kwargs)


def _uniformize_as_list(v):
return v if isinstance(v, (list, tuple)) else [v]


def verilog_expression(params, expression, out_type, **kwargs):
"""Synthesize a lookup function from a verilog function."""
result_name = "result"
if result_name in params:
result_name = f"{result_name}_{hash(expression)}"
params = dict(params)
params[result_name] = out_type
verilog_params = {
name: Ty(
bit_width=sum(ty.dtype.bit_width for ty in _uniformize_as_list(type_list)),
is_signed=_uniformize_as_list(type_list)[0].dtype.is_signed,
)
for name, type_list in params.items()
}
verilog = verilog_from_expression(verilog_params, expression, result_name)
if "name" not in kwargs:
kwargs.setdefault("name", expression)
return FheFunction(verilog=verilog, params=params, **kwargs)


def verilog_module(verilog, **kwargs):
"""Synthesize a lookup function from a verilog module."""
if "name" not in kwargs:
kwargs.setdefault("name", "main")
return FheFunction(verilog=verilog, **kwargs)
Loading
Loading