diff --git a/docs/src/poplar.md b/docs/src/poplar.md index 1d26744..69bec06 100644 --- a/docs/src/poplar.md +++ b/docs/src/poplar.md @@ -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] ``` diff --git a/examples/main.jl b/examples/main.jl index 99500e9..31fc46b 100644 --- a/examples/main.jl +++ b/examples/main.jl @@ -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) diff --git a/examples/tutorial1.jl b/examples/tutorial1.jl index 0d0f7a7..8d8aef6 100644 --- a/examples/tutorial1.jl +++ b/examples/tutorial1.jl @@ -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) diff --git a/examples/tutorial2.jl b/examples/tutorial2.jl index 3ea8d52..3fec293 100644 --- a/examples/tutorial2.jl +++ b/examples/tutorial2.jl @@ -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) diff --git a/src/poplar.jl b/src/poplar.jl index 6a91e5f..4a08873 100644 --- a/src/poplar.jl +++ b/src/poplar.jl @@ -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 @@ -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 @@ -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: @@ -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 diff --git a/test/compiler.jl b/test/compiler.jl index d66e513..c7c5a68 100644 --- a/test/compiler.jl +++ b/test/compiler.jl @@ -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 diff --git a/test/poplar.jl b/test/poplar.jl index d746b60..5a19163 100644 --- a/test/poplar.jl +++ b/test/poplar.jl @@ -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