IO in Haskell (and presumably Lwt in OCaml, though I'm less familiar with it) doesn't have much to do with fibers.
An IO value is just something that the Haskell runtime is able to invoke somehow. Haskell functions can not directly run IO values (ignoring unsafePerformIO). A fairly elegant implementation would probably simply make IO values be asynchronous operations (procedures that take an "on complete" callback that receives the result)—again, since there's no way for a Haskell function to actually run the operation, all it can do is return such an operation to the runtime to be called.