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

Add gray_box_device option for PytorchModel #159

Merged
merged 5 commits into from
Nov 5, 2024
Merged
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
24 changes: 19 additions & 5 deletions ext/MathOptAIPythonCallExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import MathOptAI
config::Dict = Dict{Any,Any}(),
reduced_space::Bool = false,
gray_box::Bool = false,
gray_box_hessian::Bool = false,
gray_box_device::String = "cpu",
)

Add a trained neural network from PyTorch via PythonCall.jl to `model`.
Expand All @@ -41,6 +43,8 @@ Add a trained neural network from PyTorch via PythonCall.jl to `model`.
nonlinear operator, with gradients provided by `torch.func.jacrev`.
* `gray_box_hessian`: if `true`, the gray box additionally computes the Hessian
of the output using `torch.func.hessian`.
* `gray_box_device`: device used to construct PyTorch tensors, e.g. `"cuda"`
to run on an Nvidia GPU.
"""
function MathOptAI.add_predictor(
model::JuMP.AbstractModel,
Expand All @@ -63,6 +67,7 @@ end
config::Dict = Dict{Any,Any}(),
gray_box::Bool = false,
gray_box_hessian::Bool = false,
gray_box_device::String = "cpu",
)

Convert a trained neural network from PyTorch via PythonCall.jl to a
Expand All @@ -87,18 +92,25 @@ Convert a trained neural network from PyTorch via PythonCall.jl to a
nonlinear operator, with gradients provided by `torch.func.jacrev`.
* `gray_box_hessian`: if `true`, the gray box additionally computes the Hessian
of the output using `torch.func.hessian`.
* `gray_box_device`: device used to construct PyTorch tensors, e.g. `"cuda"`
to run on an Nvidia GPU.
"""
function MathOptAI.build_predictor(
predictor::MathOptAI.PytorchModel;
config::Dict = Dict{Any,Any}(),
gray_box::Bool = false,
gray_box_hessian::Bool = false,
gray_box_device::String = "cpu",
)
if gray_box
if !isempty(config)
error("cannot specify the `config` kwarg if `gray_box = true`")
end
return MathOptAI.GrayBox(predictor; hessian = gray_box_hessian)
return MathOptAI.GrayBox(
predictor;
hessian = gray_box_hessian,
device = gray_box_device,
)
end
torch = PythonCall.pyimport("torch")
nn = PythonCall.pyimport("torch.nn")
Expand Down Expand Up @@ -132,24 +144,26 @@ end
function MathOptAI.GrayBox(
predictor::MathOptAI.PytorchModel;
hessian::Bool = false,
device::String = "cpu",
)
torch = PythonCall.pyimport("torch")
torch_model = torch.load(predictor.filename; weights_only = false)
torch_model = torch_model.to(device)
J = torch.func.jacrev(torch_model)
H = torch.func.hessian(torch_model)
# TODO(odow): I'm not sure if there is a better way to get the output
# dimension of a torch model object?
output_size(::Any) = PythonCall.pyconvert(Int, torch_model[-1].out_features)
function callback(x)
py_x = torch.tensor(collect(x))
py_value = torch_model(py_x).detach().numpy()
py_x = torch.tensor(collect(x); device = device)
py_value = torch_model(py_x).detach().cpu().numpy()
value = PythonCall.pyconvert(Vector, py_value)
py_jacobian = J(py_x).detach().numpy()
py_jacobian = J(py_x).detach().cpu().numpy()
jacobian = PythonCall.pyconvert(Matrix, py_jacobian)
if !hessian
return (; value, jacobian)
end
hessians = PythonCall.pyconvert(Array, H(py_x).detach().numpy())
hessians = PythonCall.pyconvert(Array, H(py_x).detach().cpu().numpy())
return (; value, jacobian, hessian = permutedims(hessians, (2, 3, 1)))
end
return MathOptAI.GrayBox(output_size, callback; has_hessian = hessian)
Expand Down
Loading