Skip to content

Commit

Permalink
Add Poplar.get_ipu_model to more easily obtain an IPU Model (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
giordano authored Apr 8, 2024
1 parent 438bd84 commit adfa0fd
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 20 deletions.
15 changes: 15 additions & 0 deletions docs/src/poplar.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ You can slice a tensor with the usual Julia notation `tensor[index1:index2]`, th
[`similar`](@ref) can be used to add to `graph` a tensor with the same shape and optionally element type as `tensor`, while [`copyto!`](@ref) can be used to copy elements of a CPU host array into an IPU tensor.
## Using `IPUToolkit.jl` without an IPU
While this package requires a physical IPU to use all the available features, you can still experiment with the IPU programming model even if you do not have access to a hardware IPU.
The Poplar SDK provides a feature called IPU Model, which is a software emulation of the behaviour of the IPU hardware.
While the IPU model comes with [some limitations](https://docs.graphcore.ai/projects/poplar-user-guide/en/latest/poplar_programs.html#programming-with-poplar), it can be useful for testing or debugging.
To use the IPU model in `IPUToolkit.jl`, define the device of your IPU program with `Poplar.IPUModelCreateDevice` (which calls [`IPUModel::createDevice`](https://docs.graphcore.ai/projects/poplar-api/en/3.4.0/poplar/profiling/IPUModel.html#_CPPv4NK6poplar8IPUModel12createDeviceE11OptionFlagsbj) under the hood):
```julia
device = Poplar.get_ipu_model()
# Then the rest of the program continues as usual
target = Poplar.DeviceGetTarget(device)
graph = Poplar.Graph(target)
# ...
```
```@autodocs
Modules = [IPUToolkit.Poplar]
```
6 changes: 1 addition & 5 deletions examples/main.jl
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
using IPUToolkit.IPUCompiler
using IPUToolkit.Poplar

device = if Poplar.SDK_VERSION < v"2.0"
Poplar.IPUModelCreateDevice(Poplar.IPUModel())
else
Poplar.get_ipu_device()
end
device = Poplar.get_ipu_device()

target = Poplar.DeviceGetTarget(device)
graph = Poplar.Graph(target)
Expand Down
3 changes: 1 addition & 2 deletions examples/tutorial1.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using IPUToolkit.Poplar

# model = Poplar.IPUModel()
# device = Poplar.IPUModelCreateDevice(model)
# device = Poplar.get_ipu_model()
device = Poplar.get_ipu_device()

target = Poplar.DeviceGetTarget(device)
Expand Down
3 changes: 1 addition & 2 deletions examples/tutorial2.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using IPUToolkit.Poplar

# model = Poplar.IPUModel()
# device = Poplar.IPUModelCreateDevice(model)
# device = Poplar.get_ipu_model()
device = Poplar.get_ipu_device()

target = Poplar.DeviceGetTarget(device)
Expand Down
41 changes: 34 additions & 7 deletions src/poplar.jl
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ const ATTACHED_DEVICES_LOCK = ReentrantLock()

# Be sure to quit all julia sessions which hold devices!!!
"""
Poplar.get_ipu_devices(n::Int, hint::Union{AbstractVector{<:Integer},Integer}=0)
Poplar.get_ipu_devices(n::Int, hint::Union{AbstractVector{<:Integer},Integer}=0) -> Vector{Poplar.DeviceAllocated}
Try to attach to `n` IPU devices, returns a vector of the pointers to the devices
successfully attached to. You can release them with `Poplar.DeviceDetach` (note that this
Expand All @@ -222,8 +222,8 @@ attach. It can have different types:
* if of type `AbstractVector`, try to attach to `n` devices from that list of
IDs.
See [`Poplar.get_ipu_device`](@ref) for requesting exactly one IPU device.
To release all devices previously attached with `Poplar.get_ipu_devices` or [`Poplar.get_ipu_device`](@ref) use [`Poplar.detach_devices`](@ref).
See [`Poplar.get_ipu_device`](@ref) for requesting exactly one IPU device, and [`Poplar.get_ipu_model`](@ref) for requesting an IPU Model.
To release all devices previously attached with `Poplar.get_ipu_devices`, [`Poplar.get_ipu_device`](@ref), or [`Poplar.get_ipu_model`](@ref) use [`Poplar.detach_devices`](@ref).
"""
function get_ipu_devices(n::Int, hint::Union{AbstractVector{<:Integer},Integer}=0)
lock(ATTACHED_DEVICES_LOCK) do
Expand Down Expand Up @@ -261,12 +261,16 @@ function get_ipu_devices(n::Int, hint::Union{AbstractVector{<:Integer},Integer}=
end

"""
Poplar.get_ipu_device(hint::Union{AbstractVector{<:Integer},Integer}=0)
Poplar.get_ipu_device(hint::Union{AbstractVector{<:Integer},Integer}=0) -> Poplar.DeviceAllocated
Similar to [`Poplar.get_ipu_devices`](@ref), but request exactly one IPU device. If it can attach
to a device, return that pointer only (not in a vector, like `get_ipu_devices`), otherwise
return `nothing`. You can release the device with `Poplar.DeviceDetach(device)`.
To release all devices previously attached with `Poplar.get_ipu_device` or [`Poplar.get_ipu_devices`](@ref) use [`Poplar.detach_devices`](@ref).
return `nothing`.
See [`Poplar.get_ipu_model`](@ref) for requesting an IPU Model.
You can release the device with `Poplar.DeviceDetach(device)`.
To release all devices previously attached with `Poplar.get_ipu_device`, [`Poplar.get_ipu_devices`](@ref), or [`Poplar.get_ipu_model`](@ref) use [`Poplar.detach_devices`](@ref).
The optional argument `hint` suggests to which device IDs to try and
attach. It can have different types:
Expand All @@ -283,10 +287,33 @@ function get_ipu_device(hint::Union{AbstractVector{<:Integer},Integer}=0)
return nothing
end

"""
Poplar.get_ipu_model(ipu_version::String="ipu2") -> Poplar.DeviceAllocated
Attach to an [IPU Model](https://docs.graphcore.ai/projects/poplar-user-guide/en/latest/poplar_programs.html#programming-with-poplar), and return the attached device.
This uses [`IPUModel::createDevice`](https://docs.graphcore.ai/projects/poplar-api/en/3.4.0/poplar/profiling/IPUModel.html#_CPPv4NK6poplar8IPUModel12createDeviceE11OptionFlagsbj) under the hood.
The optional positional argument `ipu_version::String`, `ipu2` by default`, represents the version of the IPU to emulate.
Valid values for `ipu_version` are `ipu1` and `ipu2` (for Mk1 and Mk2 IPU architectures respectively).
See [`Poplar.get_ipu_device`](@ref) and [`Poplar.get_ipu_devices`](@ref) for requesting one or mode hardware IPUs.
You can release the device with `Poplar.DeviceDetach(device)`.
To release all devices previously attached with `Poplar.get_ipu_model`, [`Poplar.get_ipu_device`](@ref) or [`Poplar.get_ipu_devices`](@ref) use [`Poplar.detach_devices`](@ref).
"""
function get_ipu_model(ipu_version::String="ipu2")
lock(ATTACHED_DEVICES_LOCK) do
model = Poplar.IPUModel(ipu_version)
device = Poplar.IPUModelCreateDevice(model)
push!(ATTACHED_DEVICES, device)
device
end
end

"""
Poplar.detach_devices() -> Nothing
Detach all devices previously attached in the current Julia session with [`Poplar.get_ipu_devices`](@ref) or [`Poplar.get_ipu_device`](@ref).
Detach all devices previously attached in the current Julia session with [`Poplar.get_ipu_devices`](@ref), [`Poplar.get_ipu_device`](@ref), or [`Poplar.get_ipu_model`](@ref).
"""
function detach_devices()
lock(ATTACHED_DEVICES_LOCK) do
Expand Down
3 changes: 1 addition & 2 deletions test/compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,7 @@ end
match_mode=:any,
Poplar.get_ipu_device())
else
model = @cxxtest Poplar.IPUModel()
Poplar.IPUModelCreateDevice(model)
Poplar.get_ipu_model()
end

# Run a test program
Expand Down
3 changes: 1 addition & 2 deletions test/poplar.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,7 @@ end

# Test a simple program using a software-emulated IPU (IPU model)
@testset "IPU Model" begin
model = @cxxtest Poplar.IPUModel()
device = @cxxtest Poplar.IPUModelCreateDevice(model)
device = @cxxtest Poplar.get_ipu_model()
test_poplar_program(device)
end

Expand Down

0 comments on commit adfa0fd

Please sign in to comment.